All checks were successful
Build and Release / Create Release (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 6m49s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 7m6s
Build and Release / Lint (push) Successful in 7m15s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Implement critical production readiness features for AI integration: per-request provider config, admin dashboard, workflow inspection, and plugin framework foundation. Per-Request Provider Config: - Add ProviderConfig struct to all AI request types - Update queue to resolve provider/model/API key from cascade (repo > org > system) - Pass resolved config to AI sidecar on every request - Fixes multi-tenant issue where all orgs shared sidecar's hardcoded config Admin AI Dashboard: - Add /admin/ai page with sidecar health status - Display global operation stats (total, 24h, success/fail/escalated counts) - Show operations by tier, top 5 repos, token usage - Recent operations table with repo, operation, status, duration - Add GetGlobalOperationStats model method Workflow Inspection: - Add InspectWorkflow client method and types - Implement workflow-inspect queue handler - Add notifier trigger on workflow file push - Analyzes YAML for syntax errors, security issues, best practices - Returns structured issues with line numbers and suggested fixes Plugin Framework (Phase 5 Foundation): - Add external plugin config loading from app.ini - Define ExternalPlugin interface and manager - Add plugin.proto contract (Initialize, Shutdown, HealthCheck, OnEvent, HandleHTTP) - Implement health monitoring with auto-restart for managed plugins - Add event routing to subscribed plugins - HTTP proxy support for plugin-served routes This completes Tasks 1-4 from the production readiness plan and establishes the foundation for managed plugin lifecycle.
97 lines
2.7 KiB
Go
97 lines
2.7 KiB
Go
// Copyright 2026 MarketAlly. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package plugins
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitcaddy.com/server/v3/modules/log"
|
|
"code.gitcaddy.com/server/v3/modules/setting"
|
|
)
|
|
|
|
// ExternalPluginConfig holds configuration for a single external plugin
|
|
type ExternalPluginConfig struct {
|
|
Name string
|
|
Enabled bool
|
|
// Managed mode: server launches the binary
|
|
Binary string
|
|
Args string
|
|
// External mode: connect to already-running process
|
|
Address string
|
|
// Common
|
|
SubscribedEvents []string
|
|
HealthTimeout time.Duration
|
|
}
|
|
|
|
// Config holds the global [plugins] configuration
|
|
type Config struct {
|
|
Enabled bool
|
|
Path string
|
|
HealthCheckInterval time.Duration
|
|
ExternalPlugins map[string]*ExternalPluginConfig
|
|
}
|
|
|
|
// LoadConfig loads plugin configuration from app.ini [plugins] and [plugins.*] sections
|
|
func LoadConfig() *Config {
|
|
cfg := &Config{
|
|
ExternalPlugins: make(map[string]*ExternalPluginConfig),
|
|
}
|
|
|
|
sec := setting.CfgProvider.Section("plugins")
|
|
cfg.Enabled = sec.Key("ENABLED").MustBool(true)
|
|
cfg.Path = sec.Key("PATH").MustString("data/plugins")
|
|
cfg.HealthCheckInterval = sec.Key("HEALTH_CHECK_INTERVAL").MustDuration(30 * time.Second)
|
|
|
|
// Load [plugins.*] sections for external plugins
|
|
for _, childSec := range sec.ChildSections() {
|
|
name := strings.TrimPrefix(childSec.Name(), "plugins.")
|
|
if name == "" {
|
|
continue
|
|
}
|
|
|
|
pluginCfg := &ExternalPluginConfig{
|
|
Name: name,
|
|
Enabled: childSec.Key("ENABLED").MustBool(true),
|
|
Binary: childSec.Key("BINARY").MustString(""),
|
|
Args: childSec.Key("ARGS").MustString(""),
|
|
Address: childSec.Key("ADDRESS").MustString(""),
|
|
HealthTimeout: childSec.Key("HEALTH_TIMEOUT").MustDuration(5 * time.Second),
|
|
}
|
|
|
|
// Parse subscribed events
|
|
if eventsStr := childSec.Key("SUBSCRIBED_EVENTS").MustString(""); eventsStr != "" {
|
|
pluginCfg.SubscribedEvents = splitAndTrim(eventsStr)
|
|
}
|
|
|
|
// Validate: must have either binary or address
|
|
if pluginCfg.Binary == "" && pluginCfg.Address == "" {
|
|
log.Warn("Plugin %q has neither BINARY nor ADDRESS configured, skipping", name)
|
|
continue
|
|
}
|
|
|
|
cfg.ExternalPlugins[name] = pluginCfg
|
|
log.Info("Loaded external plugin config: %s (managed=%v)", name, pluginCfg.IsManaged())
|
|
}
|
|
|
|
return cfg
|
|
}
|
|
|
|
// IsManaged returns true if the server manages the plugin's lifecycle (has a binary)
|
|
func (c *ExternalPluginConfig) IsManaged() bool {
|
|
return c.Binary != ""
|
|
}
|
|
|
|
// splitAndTrim splits a comma-separated string and trims whitespace
|
|
func splitAndTrim(s string) []string {
|
|
var result []string
|
|
for part := range strings.SplitSeq(s, ",") {
|
|
part = strings.TrimSpace(part)
|
|
if part != "" {
|
|
result = append(result, part)
|
|
}
|
|
}
|
|
return result
|
|
}
|