Merge remote-tracking branch 'upstream/dev' into dev

# Conflicts:
#	internal/cron/service.go
This commit is contained in:
Goon committed 2026-06-15 14:14:16 +07:00
commit 591d809779
39 files changed
+1705 -92

No files matched your search

+13
View File
@@ -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)
+1
View File
@@ -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
View File
@@ -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}
+44
View File
@@ -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