step 5 (partial): strip dropped packages from production code
Removed all references to dropped packages in non-test code.
Zero dropped imports, store refs, or model types remaining.
Major removals:
- scheduler/ package (entire dir) — tasks moved to extension track
- taskutil/ package (entire dir)
- health/ package (entire dir) — provider/tool health
- sandbox/provider_module.go — provider.complete module
- handlers: workflow_entry, workflow_instances, workflow_forms,
workflow_assignments, workflow_monitor, health_admin (6 files)
- auth/session.go, middleware/session_auth.go
- store/{postgres,sqlite}/sessions.go, tasks.go
Rewrites:
- store/{postgres,sqlite}/health.go — kernel-only Prune
(ws_tickets, rate_limit_counters, stale presence)
- handlers/admin.go: 847→467 lines (stripped provider CRUD)
- handlers/teams.go: 520→411 lines (stripped model listing)
- sandbox/runner.go: removed ProviderResolver
- sandbox/workflow_module.go: 216→83 lines (definition-only)
- main.go: 1579→1325 lines (stripped dropped package init)
- pages/pages.go: stripped channel/session lookups
- events/types.go: stripped chat/channel/workspace event routes
- models: stripped stale types, constants, notification types
Store interface cleanup:
- Removed SessionStore, TaskStore from Stores struct
- Stripped workflow assignment methods from WorkflowStore iface
- Stripped assignment methods from PG+SQLite workflow stores
- Updated testhelper.go table list to kernel-only
Migrations: removed 008_tasks.sql (both dialects)
-9665/+69 lines across 51 files.
This commit is contained in:
494
server/main.go
494
server/main.go
@@ -17,36 +17,21 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"switchboard-core/auth"
|
||||
"switchboard-core/compaction"
|
||||
"switchboard-core/config"
|
||||
"switchboard-core/crypto"
|
||||
"switchboard-core/database"
|
||||
"switchboard-core/events"
|
||||
"switchboard-core/extraction"
|
||||
"switchboard-core/filters"
|
||||
"switchboard-core/sandbox"
|
||||
"switchboard-core/handlers"
|
||||
"switchboard-core/health"
|
||||
"switchboard-core/logging"
|
||||
"switchboard-core/metrics"
|
||||
"switchboard-core/knowledge"
|
||||
"switchboard-core/memory"
|
||||
"switchboard-core/middleware"
|
||||
"switchboard-core/notifications"
|
||||
"switchboard-core/pages"
|
||||
"switchboard-core/providers"
|
||||
"switchboard-core/retention"
|
||||
"switchboard-core/roles"
|
||||
"switchboard-core/routing"
|
||||
"switchboard-core/scheduler"
|
||||
"switchboard-core/storage"
|
||||
"switchboard-core/store"
|
||||
postgres "switchboard-core/store/postgres"
|
||||
sqliteStore "switchboard-core/store/sqlite"
|
||||
"switchboard-core/tools"
|
||||
"switchboard-core/tools/search"
|
||||
"switchboard-core/treepath"
|
||||
"switchboard-core/workspace"
|
||||
)
|
||||
|
||||
// v0.33.0: Embedded OpenAPI spec and Swagger UI for /api/docs.
|
||||
@@ -76,12 +61,9 @@ func main() {
|
||||
// log output goes through slog.
|
||||
logging.Init(cfg.LogFormat, cfg.LogLevel)
|
||||
|
||||
// Register LLM providers
|
||||
providers.Init()
|
||||
|
||||
// v0.29.1: Config-file provider types (Ollama, LiteLLM, vLLM, etc.)
|
||||
if cfg.ProviderTypesFile != "" {
|
||||
n, err := providers.LoadCustomTypes(cfg.ProviderTypesFile)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to load custom provider types from %s: %v", cfg.ProviderTypesFile, err)
|
||||
} else if n > 0 {
|
||||
@@ -93,8 +75,6 @@ func main() {
|
||||
uekCache := crypto.NewUEKCache()
|
||||
var keyResolver *crypto.KeyResolver
|
||||
var objStore storage.ObjectStore
|
||||
var healthAccum *health.Accumulator
|
||||
var healthStore health.Store
|
||||
|
||||
if err := database.Connect(cfg); err != nil {
|
||||
log.Printf("⚠ Database unavailable: %v", err)
|
||||
@@ -134,109 +114,30 @@ func main() {
|
||||
stores = postgres.NewStores(database.DB)
|
||||
}
|
||||
|
||||
// v0.29.0: Wire store layer into treepath for backward compat.
|
||||
// New code should call stores.Messages.* directly.
|
||||
treepath.Stores = &stores
|
||||
|
||||
// Provider health accumulator (v0.22.0)
|
||||
if database.IsSQLite() {
|
||||
healthStore = sqliteStore.NewHealthStore()
|
||||
} else {
|
||||
healthStore = postgres.NewHealthStore(database.DB)
|
||||
}
|
||||
healthAccum = health.NewAccumulator(healthStore)
|
||||
defer healthAccum.Stop()
|
||||
|
||||
// v0.33.0: Start Prometheus DB pool collector
|
||||
metrics.StartDBCollector(database.DB, 15*time.Second)
|
||||
|
||||
// Auto-disable: deactivate providers that are "down" for N consecutive
|
||||
// hourly windows. Configured via PROVIDER_AUTO_DISABLE_THRESHOLD env var.
|
||||
// Default: 3 (3 consecutive "down" hours triggers deactivation). Set to 0 to disable.
|
||||
autoDisableThreshold := 3
|
||||
if v := cfg.ProviderAutoDisableThreshold; v >= 0 {
|
||||
autoDisableThreshold = v
|
||||
}
|
||||
if autoDisableThreshold > 0 {
|
||||
if ad, ok := healthStore.(health.AutoDisabler); ok {
|
||||
healthAccum.SetAutoDisable(ad, autoDisableThreshold)
|
||||
log.Printf(" 🛡️ Provider auto-disable: %d consecutive down windows", autoDisableThreshold)
|
||||
}
|
||||
}
|
||||
|
||||
// Background health cleanup: prune windows older than 7 days
|
||||
// Kernel maintenance: prune stale data every 6 hours
|
||||
go func() {
|
||||
ticker := time.NewTicker(6 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
if n, err := healthStore.Prune(ctx, time.Now().UTC().Add(-health.PruneAge)); err != nil {
|
||||
log.Printf("⚠ health prune failed: %v", err)
|
||||
if n, err := stores.Health.Prune(ctx, time.Now().UTC().Add(-7*24*time.Hour)); err != nil {
|
||||
log.Printf("⚠ maintenance prune failed: %v", err)
|
||||
} else if n > 0 {
|
||||
log.Printf("🧹 health: pruned %d old windows", n)
|
||||
log.Printf("🧹 maintenance: pruned %d stale rows", n)
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
// Background session cleanup (v0.26.0): remove expired anonymous sessions
|
||||
if cfg.SessionExpiryDays > 0 {
|
||||
go func() {
|
||||
ticker := time.NewTicker(6 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
cutoff := time.Now().UTC().AddDate(0, 0, -cfg.SessionExpiryDays)
|
||||
if n, err := stores.Sessions.DeleteExpired(ctx, cutoff); err != nil {
|
||||
log.Printf("⚠ session cleanup failed: %v", err)
|
||||
} else if n > 0 {
|
||||
log.Printf("🧹 sessions: cleaned up %d expired sessions", n)
|
||||
}
|
||||
cancel()
|
||||
<-ticker.C
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Background workflow staleness sweep (v0.26.2): mark idle instances as stale
|
||||
// + v0.27.0: retention enforcement — archive/delete completed instances per workflow policy
|
||||
if cfg.WorkflowStaleHours > 0 {
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
|
||||
// Staleness: mark idle active instances as stale
|
||||
cutoff := time.Now().UTC().Add(-time.Duration(cfg.WorkflowStaleHours) * time.Hour)
|
||||
n, err := stores.Channels.MarkStaleWorkflows(ctx, cutoff)
|
||||
if err != nil {
|
||||
log.Printf("⚠ workflow staleness sweep failed: %v", err)
|
||||
} else if n > 0 {
|
||||
log.Printf("🧹 workflows: marked %d instances as stale", n)
|
||||
}
|
||||
|
||||
// v0.27.0: Retention enforcement — delete completed workflow channels
|
||||
// where the parent workflow has retention.mode="delete" and
|
||||
// retention.delete_after_days has elapsed since completion.
|
||||
n2, err := stores.Channels.EnforceWorkflowRetention(ctx)
|
||||
if err != nil {
|
||||
// SQLite doesn't support JSON operators — skip retention on SQLite
|
||||
if !database.IsSQLite() {
|
||||
log.Printf("⚠ workflow retention enforcement failed: %v", err)
|
||||
}
|
||||
} else if n2 > 0 {
|
||||
log.Printf("🧹 workflows: deleted %d expired instances (retention policy)", n2)
|
||||
}
|
||||
|
||||
cancel()
|
||||
<-ticker.C
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// v0.37.14: Channel retention scanner — purges archived channels past their TTL
|
||||
retScanner := retention.NewScanner(stores, objStore, retention.ScannerConfig{})
|
||||
retScanner.Start()
|
||||
defer retScanner.Stop()
|
||||
|
||||
@@ -259,7 +160,6 @@ func main() {
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
// ── File Storage ─────────────────────────
|
||||
// Auto-detects PVC if STORAGE_PATH is writable. Explicit STORAGE_BACKEND
|
||||
// overrides auto-detection. nil objStore = storage features disabled.
|
||||
var s3Cfg *storage.S3Config
|
||||
@@ -285,22 +185,12 @@ func main() {
|
||||
}
|
||||
handlers.SetStorageConfigured(objStore != nil)
|
||||
|
||||
// ── Extraction Queue ────────────────────
|
||||
// Filesystem-based queue for document text extraction (PDF, DOCX, etc.)
|
||||
// Nil if storage is disabled.
|
||||
var extQueue *extraction.Queue
|
||||
if objStore != nil && cfg.StoragePath != "" {
|
||||
q, err := extraction.NewQueue(cfg.StoragePath, cfg.ExtractionConcurrency)
|
||||
if err != nil {
|
||||
log.Printf("⚠ Extraction queue init failed: %v", err)
|
||||
} else {
|
||||
extQueue = q
|
||||
// Recover items stuck in "processing" from previous crash
|
||||
if recovered, err := extQueue.RecoverStale(30 * time.Minute); err != nil {
|
||||
log.Printf("⚠ Extraction recovery failed: %v", err)
|
||||
} else if recovered > 0 {
|
||||
log.Printf(" 📋 Recovered %d stale extraction items", recovered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,94 +201,15 @@ func main() {
|
||||
// No-op when running SQLite — in-process Bus is sufficient for single-pod.
|
||||
events.StartPGBroadcast(bus)
|
||||
|
||||
// ── Workspace FS (v0.21.0) ──────────────
|
||||
// Provides file operations for workspace storage primitive.
|
||||
// Nil-safe: handler checks wfs != nil before operating.
|
||||
var wfs *workspace.FS
|
||||
if cfg.StoragePath != "" {
|
||||
wfs = workspace.NewFS(cfg.StoragePath+"/workspaces", stores.Workspaces)
|
||||
if err := wfs.Init(); err != nil {
|
||||
log.Printf("⚠ Workspace FS init failed: %v", err)
|
||||
wfs = nil
|
||||
} else {
|
||||
log.Printf(" 📁 Workspace FS initialized at %s/workspaces", cfg.StoragePath)
|
||||
}
|
||||
}
|
||||
|
||||
// Register workspace tools (late registration — needs stores + wfs)
|
||||
if wfs != nil {
|
||||
tools.RegisterWorkspaceTools(stores, wfs)
|
||||
}
|
||||
|
||||
// Role resolver for model role dispatch (needs stores + vault + bus)
|
||||
roleResolver := roles.NewResolver(stores, keyResolver).WithBus(bus)
|
||||
|
||||
// ── Knowledge Base Pipeline ─────────────
|
||||
// Embedder + ingester for RAG document processing (v0.14.0).
|
||||
// Nil-safe: handler checks embedder.IsConfigured() before accepting uploads.
|
||||
kbEmbedder := knowledge.NewEmbedder(roleResolver).WithStores(stores)
|
||||
kbIngester := knowledge.NewIngester(stores, kbEmbedder, objStore, knowledge.DefaultConcurrency)
|
||||
defer kbIngester.Wait() // drain in-flight ingestions on shutdown
|
||||
|
||||
// Register kb_search tool (late registration — needs stores + embedder)
|
||||
tools.RegisterKBSearch(stores, kbEmbedder)
|
||||
|
||||
// Register note tools (late registration — needs stores + embedder for semantic search)
|
||||
tools.RegisterNoteTools(stores, kbEmbedder)
|
||||
|
||||
// ── Workspace Indexer (v0.21.2) ───────────
|
||||
// Background indexing pipeline for workspace files.
|
||||
// Shares the embedder with KB ingestion; uses its own concurrency semaphore.
|
||||
var wsIndexer *workspace.Indexer
|
||||
if wfs != nil {
|
||||
idxSem := make(chan struct{}, cfg.WorkspaceIndexConcurrency)
|
||||
wsIndexer = workspace.NewIndexer(stores, kbEmbedder, wfs, idxSem, cfg.WorkspaceIndexingEnabled)
|
||||
wfs.SetIndexer(wsIndexer) // hook into write path
|
||||
defer wsIndexer.Wait()
|
||||
if cfg.WorkspaceIndexingEnabled {
|
||||
log.Printf(" 📑 Workspace indexer enabled (concurrency=%d)", cfg.WorkspaceIndexConcurrency)
|
||||
} else {
|
||||
log.Printf(" 📑 Workspace indexer disabled (WORKSPACE_INDEXING_ENABLED=false)")
|
||||
}
|
||||
}
|
||||
|
||||
// Register workspace_search tool (needs embedder for query embedding)
|
||||
tools.RegisterWorkspaceSearchTool(stores, kbEmbedder)
|
||||
|
||||
// ── Git Integration (v0.21.4) ────────────
|
||||
// GitOps wraps exec-based git operations with vault credential injection.
|
||||
var gitOps *workspace.GitOps
|
||||
if wfs != nil {
|
||||
gitOps = workspace.NewGitOps(wfs, stores, keyResolver, wsIndexer)
|
||||
tools.RegisterGitTools(gitOps)
|
||||
log.Println(" 🔀 Git integration enabled")
|
||||
}
|
||||
|
||||
// Register context recall tools (v0.15.1)
|
||||
tools.RegisterFileRecall(stores, objStore)
|
||||
tools.RegisterConversationSearch(stores)
|
||||
|
||||
// Memory tools + extraction scanner (v0.18.0)
|
||||
tools.RegisterMemoryTools(stores, kbEmbedder)
|
||||
|
||||
// Workflow tools (v0.26.4 — AI-triggered stage advancement)
|
||||
tools.RegisterWorkflowTools(stores)
|
||||
|
||||
// v0.27.3: task_create tool (AI spawns sub-tasks)
|
||||
tools.RegisterTaskTools(stores)
|
||||
memExtractor := memory.NewExtractor(stores, roleResolver, kbEmbedder)
|
||||
memCompactor := memory.NewCompactor(stores, roleResolver)
|
||||
memScanner := memory.NewScanner(memExtractor, stores, memory.ScannerConfig{})
|
||||
memScanner.Start()
|
||||
defer memScanner.Stop()
|
||||
|
||||
// ── Pre-completion filter chain (v0.29.0) ──
|
||||
// Built-in filters register here. Starlark extension filters will
|
||||
// register at package install time (CS3).
|
||||
filterChain := filters.NewChain()
|
||||
filterChain.Register(filters.NewKBInjectFilter(stores))
|
||||
|
||||
// ── Starlark Runner (v0.29.0 CS3) ──
|
||||
// Sandboxed interpreter for extension scripts. Runner assembles
|
||||
// modules based on granted permissions. Notifier attached below
|
||||
// after notification service init.
|
||||
@@ -406,8 +217,6 @@ func main() {
|
||||
sandbox.New(sandbox.DefaultConfig()),
|
||||
stores,
|
||||
)
|
||||
// v0.29.1: provider module adapter — bridges sandbox.ProviderResolver to handlers.ResolveProviderConfig
|
||||
starlarkRunner.SetProviderResolver(handlers.NewProviderResolverAdapter(stores, keyResolver))
|
||||
// v0.38.1: connections module — extension connection resolution
|
||||
starlarkRunner.SetConnectionResolver(handlers.NewConnectionResolverAdapter(stores, keyResolver))
|
||||
// v0.29.2: db module — extension namespaced table access
|
||||
@@ -422,10 +231,6 @@ func main() {
|
||||
log.Printf(" ⚠️ Extension SSRF protection relaxed: private IPs allowed")
|
||||
}
|
||||
|
||||
// Discover and register active Starlark pre-completion filters
|
||||
filters.DiscoverStarlarkFilters(context.Background(), filterChain, stores, starlarkRunner)
|
||||
|
||||
log.Printf(" 🔗 Pre-completion filter chain: %d filters", filterChain.Len())
|
||||
|
||||
r := gin.New()
|
||||
r.Use(middleware.RequestID())
|
||||
@@ -475,16 +280,6 @@ func main() {
|
||||
}
|
||||
|
||||
// v0.27.2: Task scheduler with executor — needs hub + notification service
|
||||
if stores.Tasks != nil {
|
||||
// v0.28.6: Register built-in system task functions
|
||||
scheduler.RegisterBuiltins()
|
||||
|
||||
exec := scheduler.NewExecutor(stores, keyResolver, hub, healthAccum)
|
||||
exec.SetRunner(starlarkRunner)
|
||||
taskSched := scheduler.New(stores, exec)
|
||||
go taskSched.Run()
|
||||
log.Println(" ⏰ Task scheduler started (with executor)")
|
||||
}
|
||||
|
||||
// Health check (k8s probes hit this directly)
|
||||
base.GET("/health", func(c *gin.Context) {
|
||||
@@ -587,7 +382,6 @@ func main() {
|
||||
"schema_version": database.SchemaVersion(),
|
||||
"database": database.IsConnected(),
|
||||
"database_name": database.Name(),
|
||||
"providers": providers.List(),
|
||||
}
|
||||
if database.IsConnected() {
|
||||
info["registration_enabled"] = handlers.IsRegistrationEnabled(stores)
|
||||
@@ -633,13 +427,6 @@ func main() {
|
||||
})
|
||||
|
||||
// Channels
|
||||
channels := handlers.NewChannelHandler(stores)
|
||||
protected.GET("/channels", channels.ListChannels)
|
||||
protected.POST("/channels", middleware.RequirePermission(auth.PermChannelCreate, stores), channels.CreateChannel)
|
||||
protected.GET("/channels/:id", channels.GetChannel)
|
||||
protected.PUT("/channels/:id", channels.UpdateChannel)
|
||||
protected.DELETE("/channels/:id", channels.DeleteChannel)
|
||||
protected.POST("/channels/:id/mark-read", channels.MarkRead)
|
||||
|
||||
// Typing indicator broadcast (v0.23.2)
|
||||
protected.POST("/channels/:id/typing", func(c *gin.Context) {
|
||||
@@ -657,8 +444,6 @@ func main() {
|
||||
if displayName == "" {
|
||||
displayName = userID[:8]
|
||||
}
|
||||
// Broadcast to other user participants
|
||||
pids, err := stores.Channels.ListUserParticipantIDs(c.Request.Context(), channelID, userID)
|
||||
if err == nil {
|
||||
payload, _ := json.Marshal(map[string]any{
|
||||
"channel_id": channelID,
|
||||
@@ -678,7 +463,6 @@ func main() {
|
||||
})
|
||||
|
||||
// Chat Folders (v0.23.1)
|
||||
folders := handlers.NewFolderHandler(stores)
|
||||
protected.GET("/folders", folders.List)
|
||||
protected.POST("/folders", folders.Create)
|
||||
protected.PUT("/folders/:id", folders.Update)
|
||||
@@ -693,7 +477,6 @@ func main() {
|
||||
protected.GET("/users/search", presence.SearchUsers)
|
||||
|
||||
// Persona groups (v0.23.2 — roster templates for group chats)
|
||||
pgH := handlers.NewPersonaGroupHandler(stores)
|
||||
protected.GET("/persona-groups", pgH.List)
|
||||
protected.POST("/persona-groups", pgH.Create)
|
||||
protected.GET("/persona-groups/:id", pgH.Get)
|
||||
@@ -717,52 +500,20 @@ func main() {
|
||||
protected.POST("/workflows/:id/publish", middleware.RequirePermission(auth.PermWorkflowCreate, stores), wfH.Publish)
|
||||
protected.GET("/workflows/:id/versions/:version", wfH.GetVersion)
|
||||
|
||||
// Workflow instances (v0.26.2 — runtime lifecycle)
|
||||
wfInstH := handlers.NewWorkflowInstanceHandler(stores, hub, notifSvc, starlarkRunner)
|
||||
protected.POST("/workflows/:id/start", wfInstH.Start)
|
||||
protected.GET("/channels/:id/workflow/status", wfInstH.GetStatus)
|
||||
protected.POST("/channels/:id/workflow/advance", wfInstH.Advance)
|
||||
protected.POST("/channels/:id/workflow/reject", wfInstH.Reject)
|
||||
protected.POST("/channels/:id/workflow/cancel", wfInstH.CancelInstance) // v0.37.15
|
||||
|
||||
// Workflow assignments (v0.26.4 — team assignment queue)
|
||||
wfAssignH := handlers.NewWorkflowAssignmentHandler(stores, hub)
|
||||
protected.GET("/workflow-assignments/mine", wfAssignH.ListMine)
|
||||
protected.POST("/workflow-assignments/:id/claim", wfAssignH.Claim)
|
||||
protected.POST("/workflow-assignments/:id/complete", wfAssignH.Complete)
|
||||
protected.GET("/workflow-assignments/:id", wfAssignH.GetAssignment)
|
||||
protected.POST("/workflow-assignments/:id/comment", wfAssignH.CommentOnAssignment)
|
||||
protected.POST("/workflow-assignments/:id/unclaim", wfAssignH.Unclaim) // v0.37.15
|
||||
protected.POST("/workflow-assignments/:id/reassign", wfAssignH.Reassign) // v0.37.15
|
||||
protected.POST("/workflow-assignments/:id/cancel", wfAssignH.CancelAssignment) // v0.37.15
|
||||
|
||||
// Tasks (v0.27.1, permissions v0.27.2)
|
||||
taskH := handlers.NewTaskHandler(stores)
|
||||
protected.GET("/tasks", taskH.ListMine)
|
||||
protected.POST("/tasks", middleware.RequirePermission(auth.PermTaskCreate, stores), taskH.Create)
|
||||
protected.GET("/tasks/:id", taskH.Get)
|
||||
protected.PUT("/tasks/:id", middleware.RequirePermission(auth.PermTaskCreate, stores), taskH.Update)
|
||||
protected.DELETE("/tasks/:id", middleware.RequirePermission(auth.PermTaskCreate, stores), taskH.Delete)
|
||||
protected.GET("/tasks/:id/runs", taskH.ListRuns)
|
||||
protected.POST("/tasks/:id/run", middleware.RequirePermission(auth.PermTaskCreate, stores), taskH.RunNow)
|
||||
protected.POST("/tasks/:id/kill", middleware.RequirePermission(auth.PermTaskCreate, stores), taskH.KillRun)
|
||||
|
||||
// Channel models (v0.20.0 — multi-model @mention routing)
|
||||
chModelH := handlers.NewChannelModelHandler(stores)
|
||||
protected.GET("/channels/:id/models", chModelH.List)
|
||||
protected.POST("/channels/:id/models", chModelH.Add)
|
||||
protected.PATCH("/channels/:id/models/:modelId", chModelH.Update)
|
||||
protected.DELETE("/channels/:id/models/:modelId", chModelH.Delete)
|
||||
|
||||
// Channel participants (v0.23.0 — ICD §3.7)
|
||||
partH := handlers.NewParticipantHandler(stores)
|
||||
protected.GET("/channels/:id/participants", partH.List)
|
||||
protected.POST("/channels/:id/participants", middleware.RequirePermission(auth.PermChannelInvite, stores), partH.Add)
|
||||
protected.PATCH("/channels/:id/participants/:participantId", partH.Update)
|
||||
protected.DELETE("/channels/:id/participants/:participantId", partH.Remove)
|
||||
|
||||
// Messages
|
||||
msgs := handlers.NewMessageHandler(keyResolver, stores, hub, objStore)
|
||||
protected.GET("/channels/:id/messages", msgs.ListMessages)
|
||||
protected.POST("/channels/:id/messages", msgs.CreateMessage)
|
||||
|
||||
@@ -774,18 +525,6 @@ func main() {
|
||||
protected.GET("/channels/:id/messages/:msgId/siblings", msgs.ListSiblings)
|
||||
protected.DELETE("/channels/:id/messages/:msgId", msgs.DeleteMessage)
|
||||
|
||||
// Chat Completions
|
||||
comp := handlers.NewCompletionHandler(keyResolver, stores, hub, objStore, kbEmbedder)
|
||||
if healthAccum != nil {
|
||||
comp.SetHealthRecorder(healthAccum)
|
||||
comp.SetHealthStore(healthStore)
|
||||
}
|
||||
comp.SetRoutingEvaluator(routing.NewEvaluator())
|
||||
comp.SetFilterChain(filterChain)
|
||||
comp.SetRunner(starlarkRunner) // v0.29.2: extension tool dispatch
|
||||
protected.POST("/chat/completions", middleware.RequirePermission(auth.PermModelUse, stores), comp.Complete)
|
||||
protected.GET("/tools", comp.ListTools)
|
||||
|
||||
// Surface discovery (v0.25.0, v0.28.7: unified packages)
|
||||
pkgH := handlers.NewPackageHandler(stores)
|
||||
protected.GET("/surfaces", pkgH.ListEnabledSurfaces)
|
||||
@@ -800,24 +539,9 @@ func main() {
|
||||
protected.POST("/packages/install", userPkgH.InstallPersonalPackage)
|
||||
protected.DELETE("/packages/:id", userPkgH.DeletePersonalPackage)
|
||||
|
||||
// Summarize & Continue (backed by compaction service)
|
||||
compactionSvc := compaction.NewService(stores, roleResolver)
|
||||
summarize := handlers.NewSummarizeHandler(stores, compactionSvc)
|
||||
protected.POST("/channels/:id/summarize", middleware.RequirePermission(auth.PermModelUse, stores), summarize.Summarize)
|
||||
|
||||
// Auto-title generation (utility role)
|
||||
titleH := handlers.NewTitleHandler(stores, roleResolver)
|
||||
protected.POST("/channels/:id/generate-title", middleware.RequirePermission(auth.PermModelUse, stores), titleH.GenerateTitle)
|
||||
|
||||
// Provider Configs (user-facing — replaces /api-configs)
|
||||
provCfg := handlers.NewProviderConfigHandler(stores, keyResolver)
|
||||
protected.GET("/api-configs", provCfg.ListConfigs) // backward compat
|
||||
protected.POST("/api-configs", provCfg.CreateConfig)
|
||||
protected.GET("/api-configs/:id", provCfg.GetConfig)
|
||||
protected.PUT("/api-configs/:id", provCfg.UpdateConfig)
|
||||
protected.DELETE("/api-configs/:id", provCfg.DeleteConfig)
|
||||
protected.GET("/api-configs/:id/models", provCfg.ListModels)
|
||||
protected.POST("/api-configs/:id/models/fetch", provCfg.FetchModels)
|
||||
|
||||
// Connection Type Discovery (v0.38.4)
|
||||
connTypeH := handlers.NewConnectionTypeHandler(stores)
|
||||
@@ -833,14 +557,8 @@ func main() {
|
||||
protected.DELETE("/connections/:id", connH.DeleteConnection)
|
||||
|
||||
// Models (unified resolver — replaces scattered endpoints)
|
||||
modelH := handlers.NewModelHandler(stores)
|
||||
if healthStore != nil {
|
||||
modelH.SetHealthStore(healthStore)
|
||||
}
|
||||
protected.GET("/models/enabled", modelH.ListEnabledModels)
|
||||
|
||||
// Model Preferences
|
||||
modelPrefs := handlers.NewModelPrefsHandler(stores)
|
||||
protected.GET("/models/preferences", modelPrefs.GetPreferences)
|
||||
protected.PUT("/models/preferences", modelPrefs.SetPreference)
|
||||
protected.POST("/models/preferences/bulk", modelPrefs.BulkSetPreferences)
|
||||
@@ -864,24 +582,11 @@ func main() {
|
||||
protected.GET("/profile/bootstrap", bootH.GetBootstrap)
|
||||
|
||||
// Usage (personal)
|
||||
usage := handlers.NewUsageHandler(stores)
|
||||
protected.GET("/usage", usage.PersonalUsage)
|
||||
|
||||
// Personas
|
||||
personas := handlers.NewPersonaHandler(stores)
|
||||
protected.GET("/personas", personas.ListUserPersonas)
|
||||
protected.POST("/personas", middleware.RequirePermission(auth.PermPersonaCreate, stores), personas.CreateUserPersona)
|
||||
protected.PUT("/personas/:id", middleware.RequirePermission(auth.PermPersonaManage, stores), personas.UpdateUserPersona)
|
||||
protected.DELETE("/personas/:id", middleware.RequirePermission(auth.PermPersonaManage, stores), personas.DeleteUserPersona)
|
||||
protected.POST("/personas/:id/avatar", personas.UploadUserPersonaAvatar)
|
||||
protected.DELETE("/personas/:id/avatar", personas.DeleteUserPersonaAvatar)
|
||||
protected.GET("/personas/:id/knowledge-bases", personas.GetPersonaKBs) // v0.17.0
|
||||
protected.PUT("/personas/:id/knowledge-bases", personas.SetPersonaKBs) // v0.17.0
|
||||
protected.GET("/personas/:id/tool-grants", personas.GetPersonaToolGrants) // v0.25.0
|
||||
protected.PUT("/personas/:id/tool-grants", personas.SetPersonaToolGrants) // v0.25.0
|
||||
|
||||
// Notes
|
||||
notes := handlers.NewNoteHandler(stores)
|
||||
protected.GET("/notes", notes.List)
|
||||
protected.POST("/notes", notes.Create)
|
||||
protected.GET("/notes/search", notes.Search)
|
||||
@@ -895,28 +600,6 @@ func main() {
|
||||
protected.GET("/notes/:id/backlinks", notes.Backlinks)
|
||||
|
||||
// Projects (v0.19.0)
|
||||
projectH := handlers.NewProjectHandler(stores)
|
||||
protected.GET("/projects", projectH.List)
|
||||
protected.POST("/projects", projectH.Create)
|
||||
protected.GET("/projects/:id", projectH.Get)
|
||||
protected.PUT("/projects/:id", projectH.Update)
|
||||
protected.DELETE("/projects/:id", projectH.Delete)
|
||||
protected.POST("/projects/:id/channels", projectH.AddChannel)
|
||||
protected.DELETE("/projects/:id/channels/:channelId", projectH.RemoveChannel)
|
||||
protected.GET("/projects/:id/channels", projectH.ListChannels)
|
||||
protected.PUT("/projects/:id/channels/reorder", projectH.ReorderChannels)
|
||||
protected.POST("/projects/:id/knowledge-bases", projectH.AddKB)
|
||||
protected.DELETE("/projects/:id/knowledge-bases/:kbId", projectH.RemoveKB)
|
||||
protected.GET("/projects/:id/knowledge-bases", projectH.ListKBs)
|
||||
protected.POST("/projects/:id/notes", projectH.AddNote)
|
||||
protected.DELETE("/projects/:id/notes/:noteId", projectH.RemoveNote)
|
||||
protected.GET("/projects/:id/notes", projectH.ListNotes)
|
||||
|
||||
// Project files (v0.22.4, reworked v0.37.17 — workspace-backed)
|
||||
projFileH := handlers.NewFileHandler(stores, objStore, extQueue)
|
||||
if wfs != nil {
|
||||
projFileH.SetWorkspaceFS(wfs)
|
||||
}
|
||||
protected.POST("/projects/:id/files", projFileH.UploadToProject)
|
||||
protected.GET("/projects/:id/files", projFileH.ListByProject)
|
||||
protected.GET("/projects/:id/files/download", projFileH.DownloadProjectFile)
|
||||
@@ -938,96 +621,20 @@ func main() {
|
||||
protected.PUT("/notifications/preferences/:type", notifH.SetPreference)
|
||||
protected.DELETE("/notifications/preferences/:type", notifH.DeletePreference)
|
||||
|
||||
// Workspaces (v0.21.0)
|
||||
if wfs != nil {
|
||||
wsH := handlers.NewWorkspaceHandler(stores, wfs)
|
||||
protected.POST("/workspaces", wsH.Create)
|
||||
protected.GET("/workspaces", wsH.List)
|
||||
protected.GET("/workspaces/default", wsH.GetDefault) // v0.37.18: before :id param
|
||||
protected.GET("/workspaces/:id", wsH.Get)
|
||||
protected.PATCH("/workspaces/:id", wsH.Update)
|
||||
protected.DELETE("/workspaces/:id", wsH.Delete)
|
||||
protected.GET("/workspaces/:id/files", wsH.ListFiles)
|
||||
protected.GET("/workspaces/:id/files/read", wsH.ReadFile)
|
||||
protected.PUT("/workspaces/:id/files/write", wsH.WriteFile)
|
||||
protected.DELETE("/workspaces/:id/files/delete", wsH.DeleteFileHandler)
|
||||
protected.POST("/workspaces/:id/files/mkdir", wsH.Mkdir)
|
||||
protected.POST("/workspaces/:id/archive/upload", wsH.UploadArchive)
|
||||
protected.GET("/workspaces/:id/archive/download", wsH.DownloadArchive)
|
||||
protected.POST("/workspaces/:id/reconcile", wsH.Reconcile)
|
||||
protected.GET("/workspaces/:id/stats", wsH.Stats)
|
||||
protected.GET("/workspaces/:id/index-status", wsH.IndexStatus)
|
||||
|
||||
// Git operations (v0.21.4)
|
||||
if gitOps != nil {
|
||||
gitH := handlers.NewGitHandler(stores, gitOps)
|
||||
protected.POST("/workspaces/:id/git/clone", gitH.Clone)
|
||||
protected.POST("/workspaces/:id/git/pull", gitH.Pull)
|
||||
protected.POST("/workspaces/:id/git/push", gitH.Push)
|
||||
protected.GET("/workspaces/:id/git/status", gitH.Status)
|
||||
protected.GET("/workspaces/:id/git/diff", gitH.Diff)
|
||||
protected.POST("/workspaces/:id/git/commit", gitH.Commit)
|
||||
protected.GET("/workspaces/:id/git/log", gitH.Log)
|
||||
protected.GET("/workspaces/:id/git/branches", gitH.Branches)
|
||||
protected.POST("/workspaces/:id/git/checkout", gitH.Checkout)
|
||||
}
|
||||
}
|
||||
|
||||
// Git credentials (v0.21.4) — user-scoped, independent of workspace
|
||||
gitCredH := handlers.NewGitCredentialHandler(stores, keyResolver)
|
||||
protected.POST("/git-credentials", gitCredH.Create)
|
||||
protected.POST("/git-credentials/generate", gitCredH.Generate)
|
||||
protected.GET("/git-credentials", gitCredH.List)
|
||||
protected.GET("/git-credentials/:id/public-key", gitCredH.GetPublicKey)
|
||||
protected.DELETE("/git-credentials/:id", gitCredH.Delete)
|
||||
|
||||
// Files (upload/download)
|
||||
fileH := handlers.NewFileHandler(stores, objStore, extQueue)
|
||||
protected.POST("/channels/:id/files", fileH.Upload)
|
||||
protected.GET("/channels/:id/files", fileH.ListByChannel)
|
||||
protected.GET("/files", fileH.ListByUser)
|
||||
protected.GET("/files/:id", fileH.GetMetadata)
|
||||
protected.GET("/files/:id/download", fileH.Download)
|
||||
protected.DELETE("/files/:id", fileH.Delete)
|
||||
protected.GET("/messages/:id/files", fileH.ListByMessage)
|
||||
|
||||
// Export (v0.22.4) — pandoc-based markdown → PDF/DOCX conversion
|
||||
exportH := handlers.NewExportHandler()
|
||||
protected.POST("/export", exportH.Convert)
|
||||
|
||||
// Data portability (v0.34.0) — user data export/import
|
||||
dataExport := handlers.NewDataExportHandler(stores, objStore)
|
||||
protected.GET("/export/me", dataExport.ExportMyData)
|
||||
dataImport := handlers.NewDataImportHandler(stores, objStore)
|
||||
protected.POST("/import/me", dataImport.ImportMyData)
|
||||
protected.POST("/import/chatgpt", dataImport.ImportChatGPT)
|
||||
gdprH := handlers.NewGDPRHandler(stores)
|
||||
protected.DELETE("/me", gdprH.DeleteMyAccount)
|
||||
|
||||
// Hook: clean up storage files when channels are deleted
|
||||
handlers.SetChannelDeleteHook(fileH.CleanupChannelStorage)
|
||||
|
||||
// Knowledge Bases (RAG — v0.14.0)
|
||||
kbH := handlers.NewKnowledgeBaseHandler(stores, objStore, kbIngester, kbEmbedder)
|
||||
protected.POST("/knowledge-bases", middleware.RequirePermission(auth.PermKBCreate, stores), kbH.CreateKB)
|
||||
protected.GET("/knowledge-bases", kbH.ListKBs)
|
||||
protected.GET("/knowledge-bases/:id", kbH.GetKB)
|
||||
protected.PUT("/knowledge-bases/:id", middleware.RequirePermission(auth.PermKBWrite, stores), kbH.UpdateKB)
|
||||
protected.DELETE("/knowledge-bases/:id", middleware.RequirePermission(auth.PermKBWrite, stores), kbH.DeleteKB)
|
||||
protected.POST("/knowledge-bases/:id/documents", middleware.RequirePermission(auth.PermKBWrite, stores), kbH.UploadDocument)
|
||||
protected.GET("/knowledge-bases/:id/documents", kbH.ListDocuments)
|
||||
protected.GET("/knowledge-bases/:id/documents/:docId/status", kbH.GetDocumentStatus)
|
||||
protected.DELETE("/knowledge-bases/:id/documents/:docId", middleware.RequirePermission(auth.PermKBWrite, stores), kbH.DeleteDocument)
|
||||
protected.POST("/knowledge-bases/:id/search", middleware.RequirePermission(auth.PermKBRead, stores), kbH.SearchKB)
|
||||
protected.POST("/knowledge-bases/:id/rebuild", middleware.RequirePermission(auth.PermKBWrite, stores), kbH.RebuildKB)
|
||||
protected.GET("/channels/:id/knowledge-bases", kbH.GetChannelKBs)
|
||||
protected.PUT("/channels/:id/knowledge-bases", kbH.SetChannelKBs)
|
||||
protected.GET("/knowledge-bases-discoverable", kbH.ListDiscoverableKBs) // v0.17.0
|
||||
protected.PUT("/knowledge-bases/:id/discoverable", kbH.SetDiscoverable) // v0.17.0 (admin only enforced in handler)
|
||||
|
||||
// Memory management (v0.18.0)
|
||||
memH := handlers.NewMemoryHandler(stores)
|
||||
memH.SetCompactor(memCompactor)
|
||||
protected.GET("/memories", memH.ListMyMemories)
|
||||
protected.PUT("/memories/:id", memH.UpdateMemory)
|
||||
protected.DELETE("/memories/:id", memH.DeleteMemory)
|
||||
@@ -1052,7 +659,6 @@ func main() {
|
||||
teamScoped.POST("/members", teams.AddMember)
|
||||
teamScoped.PUT("/members/:memberId", teams.UpdateMember)
|
||||
teamScoped.DELETE("/members/:memberId", teams.RemoveMember)
|
||||
teamScoped.GET("/models", teams.ListAvailableModels)
|
||||
|
||||
// Team groups (team admins manage team-scoped groups)
|
||||
teamScoped.GET("/groups", groupH.ListTeamGroups)
|
||||
@@ -1080,11 +686,9 @@ func main() {
|
||||
teamScoped.GET("/audit/actions", teams.ListTeamAuditActions)
|
||||
|
||||
// Team usage (team admins only — usage against team-owned providers)
|
||||
teamUsage := handlers.NewUsageHandler(stores)
|
||||
teamScoped.GET("/usage", teamUsage.TeamUsage)
|
||||
|
||||
// Team personas
|
||||
teamPersonas := handlers.NewPersonaHandler(stores)
|
||||
teamScoped.GET("/personas", teamPersonas.ListTeamPersonas)
|
||||
teamScoped.POST("/personas", teamPersonas.CreateTeamPersona)
|
||||
teamScoped.PUT("/personas/:id", teamPersonas.UpdateTeamPersona)
|
||||
@@ -1096,15 +700,11 @@ func main() {
|
||||
teamScoped.POST("/personas/:id/avatar", teamPersonas.UploadTeamPersonaAvatar) // v0.28.0
|
||||
teamScoped.DELETE("/personas/:id/avatar", teamPersonas.DeleteTeamPersonaAvatar) // v0.28.0
|
||||
|
||||
// Team role overrides
|
||||
teamRoles := handlers.NewRolesHandler(stores, roleResolver)
|
||||
teamScoped.GET("/roles", teamRoles.ListTeamRoles)
|
||||
teamScoped.PUT("/roles/:role", teamRoles.UpdateTeamRole)
|
||||
teamScoped.DELETE("/roles/:role", teamRoles.DeleteTeamRole)
|
||||
|
||||
// Team workflow assignments (v0.26.4)
|
||||
teamAssignH := handlers.NewWorkflowAssignmentHandler(stores, hub)
|
||||
teamScoped.GET("/assignments", teamAssignH.ListForTeam)
|
||||
|
||||
// Team workflows — self-service (v0.31.2)
|
||||
teamWfH := handlers.NewWorkflowHandler(stores)
|
||||
@@ -1122,27 +722,15 @@ func main() {
|
||||
teamScoped.GET("/workflows/:id/versions/:version", teamWfH.GetTeamWorkflowVersion)
|
||||
|
||||
// Team workflow monitoring (v0.35.0)
|
||||
teamWfMon := handlers.NewWorkflowMonitorHandler(stores)
|
||||
teamScoped.GET("/workflows/monitor/instances", teamWfMon.ListTeamActiveInstances)
|
||||
|
||||
// Team workflow instance cancel (v0.37.15)
|
||||
teamWfInstH := handlers.NewWorkflowInstanceHandler(stores, hub, notifSvc, starlarkRunner)
|
||||
teamScoped.POST("/workflows/monitor/instances/:channelId/cancel", teamWfInstH.CancelTeamInstance)
|
||||
|
||||
// Team tasks — admin CRUD (v0.27.5)
|
||||
teamTaskH := handlers.NewTaskHandler(stores)
|
||||
teamScoped.POST("/tasks", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.CreateTeamTask)
|
||||
teamScoped.PUT("/tasks/:id", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.Update)
|
||||
teamScoped.DELETE("/tasks/:id", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.Delete)
|
||||
teamScoped.POST("/tasks/:id/run", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.RunNow)
|
||||
teamScoped.POST("/tasks/:id/kill", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.KillRun)
|
||||
}
|
||||
|
||||
// Team task viewing for all members (v0.27.5)
|
||||
teamMemberRoutes := protected.Group("/teams/:teamId")
|
||||
teamMemberRoutes.Use(middleware.RequireTeamMember(stores.Teams))
|
||||
{
|
||||
teamMemberTaskH := handlers.NewTaskHandler(stores)
|
||||
teamMemberRoutes.GET("/tasks", teamMemberTaskH.ListTeamTasks)
|
||||
teamMemberRoutes.GET("/tasks/:id/runs", teamMemberTaskH.ListRuns)
|
||||
}
|
||||
@@ -1185,10 +773,6 @@ func main() {
|
||||
admin.GET("/stats", adm.GetStats)
|
||||
|
||||
// Global Provider Configs
|
||||
admin.GET("/configs", adm.ListGlobalConfigs)
|
||||
admin.POST("/configs", adm.CreateGlobalConfig)
|
||||
admin.PUT("/configs/:id", adm.UpdateGlobalConfig)
|
||||
admin.DELETE("/configs/:id", adm.DeleteGlobalConfig)
|
||||
|
||||
// Global Connections (v0.38.1)
|
||||
admin.GET("/connections", adm.ListGlobalConnections)
|
||||
@@ -1197,14 +781,8 @@ func main() {
|
||||
admin.DELETE("/connections/:id", adm.DeleteGlobalConnection)
|
||||
|
||||
// Model Catalog
|
||||
admin.GET("/models", adm.ListModelConfigs)
|
||||
admin.POST("/models/fetch", adm.FetchModels)
|
||||
admin.PUT("/models/bulk", adm.BulkUpdateModels)
|
||||
admin.PUT("/models/:id", adm.UpdateModelConfig)
|
||||
admin.DELETE("/models/:id", adm.DeleteModelConfig)
|
||||
|
||||
// Personas (admin global)
|
||||
personaAdm := handlers.NewPersonaHandler(stores)
|
||||
admin.GET("/personas", personaAdm.ListAdminPersonas)
|
||||
admin.POST("/personas", personaAdm.CreateAdminPersona)
|
||||
admin.PUT("/personas/:id", personaAdm.UpdateAdminPersona)
|
||||
@@ -1217,7 +795,6 @@ func main() {
|
||||
admin.PUT("/personas/:id/tool-grants", personaAdm.SetPersonaToolGrants) // v0.25.0
|
||||
|
||||
// Admin memory review (v0.18.0)
|
||||
adminMemH := handlers.NewMemoryHandler(stores)
|
||||
admin.GET("/memories/pending", adminMemH.ListPendingReview)
|
||||
admin.POST("/memories/bulk-approve", adminMemH.BulkApprove)
|
||||
|
||||
@@ -1234,8 +811,6 @@ func main() {
|
||||
admin.DELETE("/teams/:id/members/:memberId", teamAdm.RemoveMember)
|
||||
|
||||
// Team data export/import (v0.34.0)
|
||||
teamExport := handlers.NewDataExportHandler(stores, objStore)
|
||||
teamImport := handlers.NewDataImportHandler(stores, objStore)
|
||||
admin.GET("/teams/:id/export", teamExport.ExportTeam)
|
||||
admin.POST("/teams/:id/import", teamImport.ImportTeam)
|
||||
|
||||
@@ -1267,26 +842,16 @@ func main() {
|
||||
admin.PUT("/grants/:type/:id", groupAdm.SetResourceGrant)
|
||||
|
||||
// Projects (admin — v0.19.0)
|
||||
adminProjH := handlers.NewProjectHandler(stores)
|
||||
admin.GET("/projects", adminProjH.AdminList)
|
||||
admin.DELETE("/projects/:id", adminProjH.Delete)
|
||||
admin.DELETE("/grants/:type/:id", groupAdm.DeleteResourceGrant)
|
||||
|
||||
// Model Roles
|
||||
rolesH := handlers.NewRolesHandler(stores, roleResolver)
|
||||
admin.GET("/roles", rolesH.ListRoles)
|
||||
admin.GET("/roles/:role", rolesH.GetRole)
|
||||
admin.PUT("/roles/:role", rolesH.UpdateRole)
|
||||
admin.POST("/roles/:role/test", rolesH.TestRole)
|
||||
|
||||
// Usage & Pricing
|
||||
usageH := handlers.NewUsageHandler(stores)
|
||||
admin.GET("/usage", usageH.AdminUsage)
|
||||
admin.GET("/usage/users/:id", usageH.AdminUserUsage)
|
||||
admin.GET("/usage/teams/:id", usageH.AdminTeamUsage)
|
||||
admin.GET("/pricing", usageH.ListPricing)
|
||||
admin.PUT("/pricing", usageH.UpsertPricing)
|
||||
admin.DELETE("/pricing/:provider/:model", usageH.DeletePricing)
|
||||
|
||||
// Storage status
|
||||
storageH := handlers.NewStorageHandler(objStore)
|
||||
@@ -1299,15 +864,11 @@ func main() {
|
||||
emailAdm := handlers.NewAdminEmailHandler(stores)
|
||||
admin.POST("/notifications/test-email", emailAdm.TestEmail)
|
||||
|
||||
// Storage management (orphan cleanup)
|
||||
fileAdm := handlers.NewFileHandler(stores, objStore, extQueue)
|
||||
admin.GET("/storage/orphans", fileAdm.OrphanCount)
|
||||
admin.POST("/storage/cleanup", fileAdm.CleanupOrphans)
|
||||
admin.GET("/storage/extraction", fileAdm.ExtractionStatus)
|
||||
|
||||
// Archived channels (admin — v0.23.2)
|
||||
admin.GET("/channels/archived", adm.ListArchivedChannels)
|
||||
admin.DELETE("/channels/:id/purge", adm.PurgeChannel)
|
||||
|
||||
// Extensions (admin)
|
||||
extAdm := handlers.NewExtensionHandler(stores)
|
||||
@@ -1330,17 +891,12 @@ func main() {
|
||||
admin.PUT("/extensions/:id/secrets", extSecH.SetSecrets)
|
||||
admin.DELETE("/extensions/:id/secrets", extSecH.DeleteSecrets)
|
||||
|
||||
// Admin Dashboard (v0.33.0)
|
||||
dashAdm := handlers.NewDashboardAdminHandler(stores, healthStore, hub)
|
||||
admin.GET("/dashboard", dashAdm.GetDashboard)
|
||||
|
||||
// Provider Health (admin — v0.22.0)
|
||||
healthAdm := handlers.NewHealthAdminHandler(healthStore, stores)
|
||||
admin.GET("/providers/health", healthAdm.GetAllProviderHealth)
|
||||
admin.GET("/providers/:id/health", healthAdm.GetProviderHealth)
|
||||
|
||||
// Capability Overrides (admin — v0.22.0)
|
||||
capAdm := handlers.NewCapOverrideAdminHandler(stores)
|
||||
admin.GET("/models/:id/capabilities", capAdm.GetModelCapabilities)
|
||||
admin.PUT("/models/:id/capabilities", capAdm.SetModelCapability)
|
||||
admin.DELETE("/models/:id/capabilities/:overrideId", capAdm.DeleteModelCapability)
|
||||
@@ -1349,9 +905,6 @@ func main() {
|
||||
// Provider Types (admin — v0.22.1)
|
||||
admin.GET("/provider-types", handlers.GetProviderTypes)
|
||||
|
||||
// Routing Policies (admin — v0.22.2)
|
||||
routingEval := routing.NewEvaluator()
|
||||
routingAdm := handlers.NewRoutingAdminHandler(stores, routingEval, healthStore)
|
||||
admin.GET("/routing/policies", routingAdm.ListPolicies)
|
||||
admin.GET("/routing/policies/:id", routingAdm.GetPolicy)
|
||||
admin.POST("/routing/policies", routingAdm.CreatePolicy)
|
||||
@@ -1366,7 +919,6 @@ func main() {
|
||||
}
|
||||
pkgAdm := handlers.NewPackageHandler(stores, packagesDir)
|
||||
pkgAdm.SetSandbox(sandbox.New(sandbox.DefaultConfig())) // v0.30.0: schema migrations
|
||||
pkgAdm.SetRunner(starlarkRunner) // v0.38.2: test-tool
|
||||
|
||||
// Package registry — must be registered before /packages/:id (v0.30.0)
|
||||
registryH := handlers.NewRegistryHandler(stores, packagesDir, pkgAdm)
|
||||
@@ -1387,7 +939,6 @@ func main() {
|
||||
admin.GET("/dependencies", pkgAdm.ListAllDependencies) // v0.38.2
|
||||
|
||||
// Package export (v0.30.0)
|
||||
pkgExport := handlers.NewPackageExportHandler(stores, packagesDir)
|
||||
admin.GET("/packages/:id/export", pkgExport.ExportPackage)
|
||||
|
||||
// Workflow package export (v0.30.2)
|
||||
@@ -1395,10 +946,6 @@ func main() {
|
||||
admin.GET("/workflows/:id/export", wfPkgH.ExportWorkflowPackage)
|
||||
|
||||
// Workflow monitoring (v0.35.0)
|
||||
wfMonH := handlers.NewWorkflowMonitorHandler(stores)
|
||||
admin.GET("/workflows/monitor/instances", wfMonH.ListActiveInstances)
|
||||
admin.GET("/workflows/monitor/funnel/:id", wfMonH.GetFunnel)
|
||||
admin.GET("/workflows/monitor/stale", wfMonH.ListStaleInstances)
|
||||
|
||||
// Surface aliases (backward compat — same handlers)
|
||||
admin.GET("/surfaces", pkgAdm.ListPackages)
|
||||
@@ -1409,7 +956,6 @@ func main() {
|
||||
admin.DELETE("/surfaces/:id", pkgAdm.DeletePackage)
|
||||
|
||||
// Task management — admin (v0.27.1, extended v0.27.2)
|
||||
taskAdm := handlers.NewTaskHandler(stores)
|
||||
admin.GET("/tasks", taskAdm.ListAll)
|
||||
admin.POST("/tasks/:id/run", taskAdm.RunNow)
|
||||
admin.POST("/tasks/:id/kill", taskAdm.KillRun)
|
||||
@@ -1472,17 +1018,11 @@ func main() {
|
||||
wfAPI := base.Group("/api/v1/w")
|
||||
wfAPI.Use(middleware.AuthOrSession(cfg, stores, userCache))
|
||||
{
|
||||
wfMsgs := handlers.NewMessageHandler(keyResolver, stores, hub, objStore)
|
||||
wfAPI.POST("/:id/messages", wfMsgs.CreateMessage)
|
||||
wfAPI.GET("/:id/messages", wfMsgs.ListMessages)
|
||||
|
||||
wfComp := handlers.NewCompletionHandler(keyResolver, stores, hub, objStore, kbEmbedder)
|
||||
wfComp.SetFilterChain(filterChain)
|
||||
wfComp.SetRunner(starlarkRunner) // v0.29.2: extension tool dispatch
|
||||
wfAPI.POST("/:id/completions", wfComp.Complete)
|
||||
|
||||
// v0.29.3: Workflow form endpoints
|
||||
wfForms := handlers.NewWorkflowFormHandler(stores, starlarkRunner, hub)
|
||||
wfAPI.GET("/:id/form", wfForms.GetFormTemplate)
|
||||
wfAPI.POST("/:id/form-submit", wfForms.SubmitForm)
|
||||
}
|
||||
@@ -1491,12 +1031,9 @@ func main() {
|
||||
// NOTE: Cannot use /api/v1/w/:scope/:slug/start — Gin's radix trie
|
||||
// conflicts with /api/v1/w/:id/messages (different wildcard names at
|
||||
// same path position). Separate namespace avoids the collision.
|
||||
wfEntry := handlers.NewWorkflowEntryHandler(stores)
|
||||
base.POST("/api/v1/workflow-entry/:scope/:slug", wfEntry.StartVisitor)
|
||||
|
||||
// v0.28.0: Webhook trigger endpoint (token-based auth, no JWT)
|
||||
triggerH := handlers.NewTriggerHandler(stores)
|
||||
base.POST("/api/v1/hooks/t/:token", triggerH.Handle)
|
||||
|
||||
bp := cfg.BasePath
|
||||
if bp == "" {
|
||||
@@ -1505,19 +1042,12 @@ func main() {
|
||||
log.Printf("🔀 Chat Switchboard API v%s starting on port %s", Version, cfg.Port)
|
||||
log.Printf(" Base path: %s", bp)
|
||||
log.Printf(" Schema: %s", database.SchemaVersion())
|
||||
log.Printf(" Providers: %v", providers.List())
|
||||
if objStore != nil {
|
||||
log.Printf(" Storage: %s", objStore.Backend())
|
||||
if extQueue != nil {
|
||||
log.Printf(" Extraction: enabled (concurrency=%d)", cfg.ExtractionConcurrency)
|
||||
} else {
|
||||
log.Printf(" Extraction: disabled")
|
||||
}
|
||||
} else {
|
||||
log.Printf(" Storage: disabled")
|
||||
}
|
||||
log.Printf(" EventBus: ready, WebSocket on %s/ws", cfg.BasePath)
|
||||
log.Printf(" Health: provider tracking active (flush=%s, prune=%s)", health.FlushInterval, health.PruneAge)
|
||||
log.Printf(" Pages: template engine active (%d surfaces registered)", len(pageEngine.Surfaces()))
|
||||
if err := r.Run(":" + cfg.Port); err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
@@ -1528,22 +1058,6 @@ func main() {
|
||||
|
||||
// loadSearchConfig reads search provider config from global_config and applies it.
|
||||
// Falls back to DuckDuckGo (the default set in search package init) if not configured.
|
||||
func loadSearchConfig(stores store.Stores) {
|
||||
raw, err := stores.GlobalConfig.Get(context.Background(), "search_config")
|
||||
if err != nil || raw == nil {
|
||||
log.Println("🔍 Search: using default provider (DuckDuckGo)")
|
||||
return
|
||||
}
|
||||
b, _ := json.Marshal(raw)
|
||||
var cfg search.Config
|
||||
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||
log.Printf("⚠️ Failed to parse search config: %v", err)
|
||||
return
|
||||
}
|
||||
if err := search.ApplyConfig(cfg); err != nil {
|
||||
log.Printf("⚠️ Failed to apply search config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runVaultCommand(subcmd string) {
|
||||
cfg := config.Load()
|
||||
|
||||
Reference in New Issue
Block a user