2
0
Files
logikonline f42c6c39f9
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
feat(ai-service): complete ai production readiness tasks
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.
2026-02-13 01:16:58 -05:00

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
}