mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-16 10:15:12 +00:00
Merge remote-tracking branch 'upstream/dev' into dev
# Conflicts: # internal/cron/service.go
This commit is contained in:
commit
591d809779
39 files changed
+1705
-92
No files matched your search
@@ -397,6 +397,19 @@ func registerProvidersFromDB(registry *providers.Registry, provStore store.Provi
|
||||
prov := providers.NewOpenAIProvider(p.Name, p.APIKey, base, store.BytePlusDefaultModel)
|
||||
prov.WithProviderType(p.ProviderType)
|
||||
registry.RegisterForTenant(p.TenantID, prov)
|
||||
case store.ProviderKimiCoding:
|
||||
// Moonshot Kimi Coding requires a fixed User-Agent on every request.
|
||||
// OpenAI-compatible wire shape otherwise.
|
||||
base := p.APIBase
|
||||
if base == "" {
|
||||
base = store.KimiCodingDefaultAPIBase
|
||||
}
|
||||
prov := providers.NewOpenAIProvider(p.Name, p.APIKey, base, store.KimiCodingDefaultModel)
|
||||
prov.WithProviderType(p.ProviderType)
|
||||
prov.WithExtraHeaders(map[string]string{
|
||||
"User-Agent": store.KimiCodingRequiredUserAgent,
|
||||
})
|
||||
registry.RegisterForTenant(p.TenantID, prov)
|
||||
default:
|
||||
prov := providers.NewOpenAIProvider(p.Name, p.APIKey, p.APIBase, "")
|
||||
prov.WithProviderType(p.ProviderType)
|
||||
|
||||
@@ -131,6 +131,7 @@ func seedConfigForContext(ctx context.Context, sc store.SystemConfigStore, cfg *
|
||||
set("tts.auto", cfg.Tts.Auto)
|
||||
set("tts.mode", cfg.Tts.Mode)
|
||||
setInt("tts.max_length", cfg.Tts.MaxLength)
|
||||
setInt("tts.timeout_ms", cfg.Tts.TimeoutMs)
|
||||
|
||||
// Cron
|
||||
setInt("cron.max_retries", cfg.Cron.MaxRetries)
|
||||
|
||||
+32
-10
@@ -60,6 +60,20 @@ type response struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
req := request{Action: os.Args[1]}
|
||||
if len(os.Args) > 2 {
|
||||
req.Package = os.Args[2]
|
||||
}
|
||||
resp := handleRequest(req)
|
||||
out, _ := json.Marshal(resp)
|
||||
fmt.Println(string(out))
|
||||
if !resp.OK {
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("pkg-helper: starting", "socket", socketPath, "protocol", "v2")
|
||||
|
||||
// Remove stale socket.
|
||||
@@ -208,14 +222,26 @@ func handleRequest(req request) response {
|
||||
}
|
||||
}
|
||||
|
||||
var runApkFunc = runApk
|
||||
|
||||
func runApk(args ...string) ([]byte, error) {
|
||||
if os.Getuid() != 0 {
|
||||
if sudo, err := exec.LookPath("sudo"); err == nil {
|
||||
// Use -n to prevent blocking on interactive password prompts
|
||||
sudoArgs := append([]string{"-n", "apk"}, args...)
|
||||
return exec.Command(sudo, sudoArgs...).CombinedOutput()
|
||||
}
|
||||
}
|
||||
return exec.Command("apk", args...).CombinedOutput()
|
||||
}
|
||||
|
||||
func doInstall(pkg string) response {
|
||||
apkMutex.Lock()
|
||||
defer apkMutex.Unlock()
|
||||
|
||||
slog.Info("pkg-helper: installing", "package", pkg)
|
||||
|
||||
cmd := exec.Command("apk", "add", "--no-cache", pkg)
|
||||
out, err := cmd.CombinedOutput()
|
||||
out, err := runApkFunc("add", "--no-cache", pkg)
|
||||
if err != nil {
|
||||
msg, code := classifyApkOutput(string(out), err)
|
||||
slog.Error("pkg-helper: install failed", "package", pkg, "error", msg, "code", code)
|
||||
@@ -233,8 +259,7 @@ func doUninstall(pkg string) response {
|
||||
|
||||
slog.Info("pkg-helper: uninstalling", "package", pkg)
|
||||
|
||||
cmd := exec.Command("apk", "del", pkg)
|
||||
out, err := cmd.CombinedOutput()
|
||||
out, err := runApkFunc("del", pkg)
|
||||
if err != nil {
|
||||
msg, code := classifyApkOutput(string(out), err)
|
||||
slog.Error("pkg-helper: uninstall failed", "package", pkg, "error", msg, "code", code)
|
||||
@@ -255,8 +280,7 @@ func doUpgrade(pkg string) response {
|
||||
|
||||
slog.Info("pkg-helper: upgrading", "package", pkg)
|
||||
|
||||
cmd := exec.Command("apk", "add", "-u", pkg)
|
||||
out, err := cmd.CombinedOutput()
|
||||
out, err := runApkFunc("add", "-u", pkg)
|
||||
if err != nil {
|
||||
msg, code := classifyApkOutput(string(out), err)
|
||||
slog.Error("pkg-helper: upgrade failed", "package", pkg, "error", msg, "code", code)
|
||||
@@ -274,8 +298,7 @@ func doUpdateIndex() response {
|
||||
|
||||
slog.Info("pkg-helper: updating index")
|
||||
|
||||
cmd := exec.Command("apk", "update")
|
||||
out, err := cmd.CombinedOutput()
|
||||
out, err := runApkFunc("update")
|
||||
if err != nil {
|
||||
msg, code := classifyApkOutput(string(out), err)
|
||||
slog.Warn("pkg-helper: update-index failed", "error", msg, "code", code)
|
||||
@@ -292,8 +315,7 @@ func doListOutdated() response {
|
||||
apkMutex.Lock()
|
||||
defer apkMutex.Unlock()
|
||||
|
||||
cmd := exec.Command("apk", "version", "-l", "<")
|
||||
out, err := cmd.CombinedOutput()
|
||||
out, err := runApkFunc("version", "-l", "<")
|
||||
if err != nil {
|
||||
msg, code := classifyApkOutput(string(out), err)
|
||||
return response{Error: msg, Code: code}
|
||||
|
||||
@@ -12,6 +12,13 @@ import (
|
||||
// Note: Command execution tests are not included here since apk is not available
|
||||
// in unit test environments. Integration tests would handle actual execution.
|
||||
func TestHandleRequest(t *testing.T) {
|
||||
// Mock runApkFunc to avoid running actual commands or triggering sudo
|
||||
origRunApkFunc := runApkFunc
|
||||
runApkFunc = func(args ...string) ([]byte, error) {
|
||||
return []byte("mock output"), nil
|
||||
}
|
||||
defer func() { runApkFunc = origRunApkFunc }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req request
|
||||
@@ -154,6 +161,13 @@ func TestValidPkgName(t *testing.T) {
|
||||
|
||||
// TestHandleRequest_AllActionsValidated tests both install and uninstall actions.
|
||||
func TestHandleRequest_AllActionsValidated(t *testing.T) {
|
||||
// Mock runApkFunc to avoid running actual commands or triggering sudo
|
||||
origRunApkFunc := runApkFunc
|
||||
runApkFunc = func(args ...string) ([]byte, error) {
|
||||
return []byte("mock output"), nil
|
||||
}
|
||||
defer func() { runApkFunc = origRunApkFunc }()
|
||||
|
||||
tests := []struct {
|
||||
action string
|
||||
}{
|
||||
@@ -365,6 +379,12 @@ func TestValidPkgNameRegex_Compliance(t *testing.T) {
|
||||
|
||||
// TestHandleRequest_ErrorMessages tests that error messages are clear.
|
||||
func TestHandleRequest_ErrorMessages(t *testing.T) {
|
||||
origRunApkFunc := runApkFunc
|
||||
runApkFunc = func(args ...string) ([]byte, error) {
|
||||
return []byte("mock output"), nil
|
||||
}
|
||||
defer func() { runApkFunc = origRunApkFunc }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req request
|
||||
@@ -404,6 +424,12 @@ func TestHandleRequest_ErrorMessages(t *testing.T) {
|
||||
// Note: Actual apk command execution will fail in test environment (no apk available),
|
||||
// but validation should pass.
|
||||
func TestHandleRequest_SuccessPath(t *testing.T) {
|
||||
origRunApkFunc := runApkFunc
|
||||
runApkFunc = func(args ...string) ([]byte, error) {
|
||||
return []byte("mock output"), nil
|
||||
}
|
||||
defer func() { runApkFunc = origRunApkFunc }()
|
||||
|
||||
tests := []struct {
|
||||
action string
|
||||
pkg string
|
||||
@@ -438,6 +464,12 @@ func TestHandleRequest_SuccessPath(t *testing.T) {
|
||||
// TestHandleRequest_UpgradeValidation verifies that the upgrade action uses
|
||||
// the stricter validApkName regex (lowercase only, no @, no /).
|
||||
func TestHandleRequest_UpgradeValidation(t *testing.T) {
|
||||
origRunApkFunc := runApkFunc
|
||||
runApkFunc = func(args ...string) ([]byte, error) {
|
||||
return []byte("mock output"), nil
|
||||
}
|
||||
defer func() { runApkFunc = origRunApkFunc }()
|
||||
|
||||
// Valid names for upgrade (lowercase apk grammar)
|
||||
valid := []string{
|
||||
"curl",
|
||||
@@ -462,6 +494,12 @@ func TestHandleRequest_UpgradeValidation(t *testing.T) {
|
||||
|
||||
// TestHandleRequest_UpgradeInjectionPatterns verifies 5 injection patterns are rejected.
|
||||
func TestHandleRequest_UpgradeInjectionPatterns(t *testing.T) {
|
||||
origRunApkFunc := runApkFunc
|
||||
runApkFunc = func(args ...string) ([]byte, error) {
|
||||
return []byte("mock output"), nil
|
||||
}
|
||||
defer func() { runApkFunc = origRunApkFunc }()
|
||||
|
||||
injections := []string{
|
||||
"-malicious", // leading hyphen
|
||||
"pkg;evil", // semicolon
|
||||
@@ -486,6 +524,12 @@ func TestHandleRequest_UpgradeInjectionPatterns(t *testing.T) {
|
||||
// by legacy validPkgName for install/uninstall) is REJECTED by upgrade action
|
||||
// via the stricter validApkName.
|
||||
func TestHandleRequest_UpgradeRejectsLegacySymbols(t *testing.T) {
|
||||
origRunApkFunc := runApkFunc
|
||||
runApkFunc = func(args ...string) ([]byte, error) {
|
||||
return []byte("mock output"), nil
|
||||
}
|
||||
defer func() { runApkFunc = origRunApkFunc }()
|
||||
|
||||
legacySymbols := []string{
|
||||
"pkg@edge", // @ accepted by validPkgName, rejected by validApkName
|
||||
"@scope/pkg", // npm scoped — rejected by validApkName
|
||||
|
||||
Reference in new issue
Block a user