diff --git a/e2e/channels_device_e2e_test.go b/e2e/channels_device_e2e_test.go index 0d2d25c..0b12c83 100644 --- a/e2e/channels_device_e2e_test.go +++ b/e2e/channels_device_e2e_test.go @@ -63,10 +63,7 @@ func TestChannelsDevicePollE2E(t *testing.T) { // ---- 真二进制 + 配置(带 plugin_dir,让 bundled channels 落地 + 授权可持久化)---- dir := t.TempDir() addr := mustAddr(t) - pluginDir := filepath.Join(dir, "plugins") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatal(err) - } + pluginDir := filepath.Join(dir, "plugins") // 不预建:SeedBundled 见空目录才落地内置插件 cfg := "listen: " + addr + "\n" + "gateway_keys:\n - sk-e2e-0001\n" + "default_model: none\n" + @@ -89,12 +86,12 @@ func TestChannelsDevicePollE2E(t *testing.T) { } // 1) 授权内核动作(运维批准 add_source 等)+ 把 minimax 端点指到假上游。 - st, _ := call("PUT", "/api/plugins/channels/kernel", + st, m := call("PUT", "/api/plugins/channels/kernel", `{"actions":["add_source","status","list_sources","probe_all","reset_health"],"all_read":false}`) if st != 200 { - t.Fatalf("授权内核动作失败: status=%d", st) + t.Fatalf("授权内核动作失败: status=%d body=%v", st, m) } - st, m := call("POST", "/api/plugins/channels/call/set_endpoints", fmt.Sprintf( + st, m = call("POST", "/api/plugins/channels/call/set_endpoints", fmt.Sprintf( `{"platform":"minimax","base_url":%q,"device":{"code_url":%q,"token_url":%q}}`, plat.URL+"/v1", plat.URL+"/oauth2/device/code", plat.URL+"/oauth2/token")) if st != 200 { @@ -102,7 +99,7 @@ func TestChannelsDevicePollE2E(t *testing.T) { } // 2) 发起 device-poll 登录:拿到会话 + user_code。 - st, m = call("POST", "/api/plugins/channels/http/channels/login", `{"platform":"minimax"}`) + st, m = call("POST", "/api/plugins/http/channels/login", `{"platform":"minimax"}`) if st != 200 { t.Fatalf("start_login status=%d body=%v", st, m) } @@ -118,7 +115,7 @@ func TestChannelsDevicePollE2E(t *testing.T) { // 3) 第一次轮询:上游 pending → 会话仍 pending。 tokenScript.Store([]scriptStep{{code: 400, body: `{"error":"authorization_pending"}`}}) atomic.StoreInt32(&tokenHits, 0) - st, m = call("GET", "/api/plugins/channels/http/channels/login/status?id="+sid, "") + st, m = call("GET", "/api/plugins/http/channels/login/status?id="+sid, "") if m["state"] != "pending" { t.Fatalf("轮询1 应 pending, 实得 %v", m) } @@ -128,7 +125,7 @@ func TestChannelsDevicePollE2E(t *testing.T) { atomic.StoreInt32(&tokenHits, 0) deadline := time.Now().Add(5 * time.Second) for time.Now().Before(deadline) { - st, m = call("GET", "/api/plugins/channels/http/channels/login/status?id="+sid, "") + st, m = call("GET", "/api/plugins/http/channels/login/status?id="+sid, "") if m["state"] == "completed" || m["state"] == "failed" { break } diff --git a/internal/lua/plugins/channels.lua b/internal/lua/plugins/channels.lua index b50a498..c18c4a6 100644 --- a/internal/lua/plugins/channels.lua +++ b/internal/lua/plugins/channels.lua @@ -471,7 +471,7 @@ function plugin.start_login(req) } if plat.flow == "vnc" then -- ③ 兜底路径:服务端浏览器。重定向打在服务端回环上,回调命中本插件的挂载点。 - local bs, berr = spawnBrowser(string.format(plat.login_url, 0), "/api/plugins/channels/http/channels/callback") + local bs, berr = spawnBrowser(string.format(plat.login_url, 0), "/api/plugins/http/channels/callback") if berr then out.browser_error = berr out.hint = "服务端浏览器不可用;先装依赖(见插件一键初始化),或改用其他登录流程" @@ -515,7 +515,7 @@ function plugin.start_login(req) else -- ①②:URL 交给用户自己的浏览器;callback 流程的 redirect_uri 指向本插件挂载点 if plat.flow == "callback" then - out.url = plat.login_url .. "&redirect_uri=" .. "127.0.0.1:8081/api/plugins/channels/http/channels/callback&state=" .. sess.nonce + out.url = plat.login_url .. "&redirect_uri=" .. "127.0.0.1:8081/api/plugins/http/channels/callback&state=" .. sess.nonce else out.url = plat.login_url end @@ -837,7 +837,7 @@ plugin.ui = { const q = (s) => document.querySelector(s); const esc = (v) => String(v == null ? "" : v).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); async function call(path, method, body) { - const r = await fetch("/api/plugins/channels/http/" + path, { + const r = await fetch("/api/plugins/http/" + path, { method: method || "GET", headers: { "Content-Type": "application/json" }, credentials: "same-origin",