mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-27 14:07:29 +08:00
refactor: stabilize runtime and unify config
This commit is contained in:
1306
pkg/api/server.go
1306
pkg/api/server.go
File diff suppressed because it is too large
Load Diff
@@ -269,6 +269,7 @@ func TestHandleWebUIConfigRequiresConfirmForProviderAPIBaseChange(t *testing.T)
|
||||
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
srv.SetConfigAfterHook(func() error { return nil })
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/config", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
@@ -287,6 +288,74 @@ func TestHandleWebUIConfigRequiresConfirmForProviderAPIBaseChange(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUIConfigAcceptsStringConfirmRisky(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp := t.TempDir()
|
||||
cfgPath := filepath.Join(tmp, "config.json")
|
||||
|
||||
cfg := cfgpkg.DefaultConfig()
|
||||
cfg.Logging.Enabled = false
|
||||
pc := cfg.Models.Providers["openai"]
|
||||
pc.APIBase = "https://old.example/v1"
|
||||
pc.APIKey = "test-key"
|
||||
cfg.Models.Providers["openai"] = pc
|
||||
if err := cfgpkg.SaveConfig(cfgPath, cfg); err != nil {
|
||||
t.Fatalf("save config: %v", err)
|
||||
}
|
||||
|
||||
bodyCfg := cfgpkg.DefaultConfig()
|
||||
bodyCfg.Logging.Enabled = false
|
||||
bodyPC := bodyCfg.Models.Providers["openai"]
|
||||
bodyPC.APIBase = "https://new.example/v1"
|
||||
bodyPC.APIKey = "test-key"
|
||||
bodyCfg.Models.Providers["openai"] = bodyPC
|
||||
bodyMap := map[string]interface{}{}
|
||||
raw, err := json.Marshal(bodyCfg)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal body: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(raw, &bodyMap); err != nil {
|
||||
t.Fatalf("unmarshal body map: %v", err)
|
||||
}
|
||||
bodyMap["confirm_risky"] = "true"
|
||||
body, err := json.Marshal(bodyMap)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal request body: %v", err)
|
||||
}
|
||||
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
srv.SetConfigAfterHook(func() error { return nil })
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/config", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
srv.handleWebUIConfig(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeCronJobParsesStringScheduleValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
job := normalizeCronJob(map[string]interface{}{
|
||||
"schedule": map[string]interface{}{
|
||||
"kind": "every",
|
||||
"everyMs": "60000",
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"message": "hello",
|
||||
},
|
||||
})
|
||||
if got, _ := job["expr"].(string); got == "" || !strings.Contains(got, "@every") {
|
||||
t.Fatalf("expected normalized @every expr, got %#v", job["expr"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUIConfigRequiresConfirmForCustomProviderSecretChange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -414,6 +483,127 @@ func TestHandleWebUIConfigReturnsReloadHookError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUIConfigNormalizedGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp := t.TempDir()
|
||||
cfgPath := filepath.Join(tmp, "config.json")
|
||||
cfg := cfgpkg.DefaultConfig()
|
||||
cfg.Logging.Enabled = false
|
||||
cfg.Agents.Subagents["coder"] = cfgpkg.SubagentConfig{
|
||||
Enabled: true,
|
||||
Role: "coding",
|
||||
SystemPromptFile: "agents/coder/AGENT.md",
|
||||
}
|
||||
if err := cfgpkg.SaveConfig(cfgPath, cfg); err != nil {
|
||||
t.Fatalf("save config: %v", err)
|
||||
}
|
||||
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/config?mode=normalized", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
srv.handleWebUIConfig(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["ok"] != true {
|
||||
t.Fatalf("expected ok=true, got %#v", payload)
|
||||
}
|
||||
configMap, _ := payload["config"].(map[string]interface{})
|
||||
coreMap, _ := configMap["core"].(map[string]interface{})
|
||||
if strings.TrimSpace(fmt.Sprintf("%v", coreMap["main_agent_id"])) != "main" {
|
||||
t.Fatalf("unexpected normalized config: %#v", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUIConfigNormalizedPost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp := t.TempDir()
|
||||
cfgPath := filepath.Join(tmp, "config.json")
|
||||
cfg := cfgpkg.DefaultConfig()
|
||||
cfg.Logging.Enabled = false
|
||||
if err := cfgpkg.SaveConfig(cfgPath, cfg); err != nil {
|
||||
t.Fatalf("save config: %v", err)
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"confirm_risky": true,
|
||||
"core": map[string]interface{}{
|
||||
"default_provider": "openai",
|
||||
"default_model": "gpt-5.4",
|
||||
"main_agent_id": "main",
|
||||
"subagents": map[string]interface{}{
|
||||
"reviewer": map[string]interface{}{
|
||||
"enabled": true,
|
||||
"role": "testing",
|
||||
"prompt": "agents/reviewer/AGENT.md",
|
||||
"provider": "openai",
|
||||
"tool_allowlist": []interface{}{"shell"},
|
||||
"runtime_class": "provider_bound",
|
||||
},
|
||||
},
|
||||
"tools": map[string]interface{}{"shell_enabled": true, "mcp_enabled": false},
|
||||
"gateway": map[string]interface{}{"host": "127.0.0.1", "port": float64(18790)},
|
||||
},
|
||||
"runtime": map[string]interface{}{
|
||||
"router": map[string]interface{}{
|
||||
"enabled": true,
|
||||
"strategy": "rules_first",
|
||||
"allow_direct_agent_chat": false,
|
||||
"max_hops": float64(6),
|
||||
"default_timeout_sec": float64(600),
|
||||
"default_wait_reply": true,
|
||||
"sticky_thread_owner": true,
|
||||
"rules": []interface{}{
|
||||
map[string]interface{}{"agent_id": "reviewer", "keywords": []interface{}{"review"}},
|
||||
},
|
||||
},
|
||||
"providers": map[string]interface{}{
|
||||
"openai": map[string]interface{}{
|
||||
"auth": "bearer",
|
||||
"api_base": "https://api.openai.com/v1",
|
||||
"timeout_sec": float64(30),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
raw, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal body: %v", err)
|
||||
}
|
||||
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
srv.SetConfigAfterHook(func() error { return nil })
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/config?mode=normalized", bytes.NewReader(raw))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUIConfig(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
loaded, err := cfgpkg.LoadConfig(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reload config: %v", err)
|
||||
}
|
||||
if !loaded.Agents.Router.Enabled {
|
||||
t.Fatalf("expected router to be enabled")
|
||||
}
|
||||
if _, ok := loaded.Agents.Subagents["reviewer"]; !ok {
|
||||
t.Fatalf("expected reviewer subagent, got %+v", loaded.Agents.Subagents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleNodeConnectRegistersAndHeartbeatsNode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -627,62 +817,6 @@ func TestHandleWebUISessionsHidesInternalSessionsByDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUISubagentsRuntimeLive(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := NewServer("127.0.0.1", 0, "", nodes.NewManager())
|
||||
srv.SetSubagentHandler(func(ctx context.Context, action string, args map[string]interface{}) (interface{}, error) {
|
||||
switch action {
|
||||
case "thread":
|
||||
return map[string]interface{}{
|
||||
"thread": map[string]interface{}{"thread_id": "thread-1"},
|
||||
"messages": []map[string]interface{}{
|
||||
{"message_id": "msg-1", "content": "hello"},
|
||||
},
|
||||
}, nil
|
||||
case "inbox":
|
||||
return map[string]interface{}{
|
||||
"messages": []map[string]interface{}{
|
||||
{"message_id": "msg-2", "content": "reply"},
|
||||
},
|
||||
}, nil
|
||||
case "stream":
|
||||
return map[string]interface{}{
|
||||
"task": map[string]interface{}{"id": "subagent-1"},
|
||||
"items": []map[string]interface{}{
|
||||
{"kind": "event", "message": "progress"},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
})
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/subagents_runtime/live", srv.handleWebUISubagentsRuntimeLive)
|
||||
httpSrv := httptest.NewServer(mux)
|
||||
defer httpSrv.Close()
|
||||
|
||||
wsURL := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/api/subagents_runtime/live?task_id=subagent-1&preview_task_id=subagent-1"
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial websocket: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var msg map[string]interface{}
|
||||
if err := conn.ReadJSON(&msg); err != nil {
|
||||
t.Fatalf("read live snapshot: %v", err)
|
||||
}
|
||||
payload, _ := msg["payload"].(map[string]interface{})
|
||||
thread, _ := payload["thread"].(map[string]interface{})
|
||||
inbox, _ := payload["inbox"].(map[string]interface{})
|
||||
preview, _ := payload["preview"].(map[string]interface{})
|
||||
if thread == nil || inbox == nil || preview == nil {
|
||||
t.Fatalf("expected thread/inbox/preview payload, got: %+v", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUIChatLive(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user