feat: add native Qwen Code hook integration
This commit is contained in:
1 parent
577e3609b5
commit
eae9015a61
18 files changed
+2138
-341
No files matched your search
@@ -0,0 +1,172 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type QwenPlugin struct {
|
||||
rename renameFunc
|
||||
}
|
||||
|
||||
type qwenRuntimeConfig struct {
|
||||
OrgID string `json:"org_id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
KID string `json:"kid"`
|
||||
BaseURL string `json:"base_url"`
|
||||
Token string `json:"token"`
|
||||
AgentName string `json:"agent_name"`
|
||||
}
|
||||
|
||||
func (p *QwenPlugin) Install(config InstallConfig) error {
|
||||
runtimeRoot, agentDirectory, err := qwenAgentDirectory(config.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
document, err := readQwenDocument()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expectedGuard := filepath.Join(agentDirectory, qwenGuardScript)
|
||||
if !sameQwenPath(config.GuardScriptPath, expectedGuard) {
|
||||
return fmt.Errorf("Elydora guard runtime must use the managed agent directory: %s", expectedGuard)
|
||||
}
|
||||
guardExists, err := regularFileExists(config.GuardScriptPath, "Elydora guard runtime")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !guardExists {
|
||||
return fmt.Errorf("Elydora guard runtime is missing: %s", config.GuardScriptPath)
|
||||
}
|
||||
nodePath, err := resolveNodeRuntime()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateQwenRegexes(nodePath, document.hooks); err != nil {
|
||||
return err
|
||||
}
|
||||
auditPath := filepath.Join(agentDirectory, qwenAuditScript)
|
||||
rendered, err := renderQwenDocument(document, "", runtimeRoot, map[string]map[string]any{
|
||||
"PreToolUse": buildQwenGroup(nodePath, config.GuardScriptPath),
|
||||
"PostToolUse": buildQwenGroup(nodePath, auditPath),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changes, err := prepareQwenInstallationChanges(config, agentDirectory, auditPath, rendered)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeChanges(changes, "Write Qwen Code installation", p.rename); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" Qwen Code: user hooks installed at %s\n", document.filePath)
|
||||
fmt.Println(" Qwen Code: run /hooks to review the Elydora hook changes.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *QwenPlugin) Uninstall(agentID string) error {
|
||||
runtimeRoot, err := qwenRuntimeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
document, err := readQwenDocument()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !document.exists {
|
||||
return nil
|
||||
}
|
||||
rendered, err := renderQwenDocument(document, agentID, runtimeRoot, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
change, err := prepareRenderedQwenChange(rendered)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeChanges([]*fileChange{change}, "Write Qwen Code settings", p.rename)
|
||||
}
|
||||
|
||||
func (p *QwenPlugin) Status() (PluginStatus, error) {
|
||||
entry := SupportedAgents[qwenAgentKey]
|
||||
status := PluginStatus{AgentName: qwenAgentKey, DisplayName: entry.Name}
|
||||
document, err := readQwenDocument()
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
status.ConfigPath = document.filePath
|
||||
if document.hooksDisabled {
|
||||
return status, nil
|
||||
}
|
||||
runtimeRoot, err := qwenRuntimeRoot()
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
contracts := qwenRuntimeContracts(document.hooks, runtimeRoot)
|
||||
status.HookConfigured = len(contracts) > 0
|
||||
if !status.HookConfigured {
|
||||
return status, nil
|
||||
}
|
||||
status.HookScriptExists, err = qwenRuntimeFilesExist(contracts)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
status.Installed = status.HookScriptExists
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func qwenAgentDirectory(agentID string) (string, string, error) {
|
||||
if agentID == "" || agentID == "." || agentID == ".." || filepath.IsAbs(agentID) || filepath.Base(agentID) != agentID {
|
||||
return "", "", fmt.Errorf("agent_id must be a single non-empty path segment")
|
||||
}
|
||||
runtimeRoot, err := qwenRuntimeRoot()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return runtimeRoot, filepath.Join(runtimeRoot, agentID), nil
|
||||
}
|
||||
|
||||
func prepareQwenInstallationChanges(
|
||||
config InstallConfig,
|
||||
agentDirectory, auditPath string,
|
||||
rendered *qwenRenderedDocument,
|
||||
) ([]*fileChange, error) {
|
||||
baseURL := config.BaseURL
|
||||
if baseURL == "" {
|
||||
baseURL = "https://api.elydora.com"
|
||||
}
|
||||
runtimeConfig, err := json.MarshalIndent(qwenRuntimeConfig{
|
||||
OrgID: config.OrgID, AgentID: config.AgentID, KID: config.KID,
|
||||
BaseURL: baseURL, Token: config.Token, AgentName: qwenAgentKey,
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode Elydora runtime config: %w", err)
|
||||
}
|
||||
runtimeConfig = append(runtimeConfig, '\n')
|
||||
items := []struct {
|
||||
path, label string
|
||||
content []byte
|
||||
mode os.FileMode
|
||||
}{
|
||||
{filepath.Join(agentDirectory, "config.json"), "Elydora runtime config", runtimeConfig, 0600},
|
||||
{filepath.Join(agentDirectory, "private.key"), "Elydora private key", []byte(config.PrivateKey + "\n"), 0600},
|
||||
{auditPath, "Elydora audit runtime", []byte(buildHookScript(qwenAgentKey, config.AgentID)), 0700},
|
||||
}
|
||||
changes := make([]*fileChange, 0, len(items)+1)
|
||||
for _, item := range items {
|
||||
change, changeErr := prepareFileChange(item.path, item.label, item.content, item.mode)
|
||||
if changeErr != nil {
|
||||
return nil, changeErr
|
||||
}
|
||||
changes = append(changes, change)
|
||||
}
|
||||
settingsChange, err := prepareRenderedQwenChange(rendered)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes = append(changes, settingsChange)
|
||||
return changes, nil
|
||||
}
|
||||
Reference in new issue
Block a user