|
|
2f5cde7fea
|
test(e2e): channels device-poll 真二进制端到端 + 修正插件 HTTP 前缀
TestChannelsDevicePollE2E 用真二进制跑通完整链路(对齐参考插件形态的终局验收):
假平台上游(设备码/token/模型/对话端点)
→ 真网关进程 + plugin_dir(SeedBundled 落地 channels)
→ PUT /api/plugins/channels/kernel 授权(运维批准动作面)
→ call/set_endpoints 把 minimax 端点指到假上游(自建代理能力复用)
→ POST /api/plugins/http/channels/login 发起 device-poll
→ GET .../login/status 轮询:先 pending(authorization_pending),
后 completed(token 下发)且 account 取 nickname
→ 终局证据:/v1/models 出现 channels-minimax 的模型 MiniMax-M3
调试中抓到并修复:channels.lua 三处旧挂载前缀 /api/plugins/channels/http/
(正确为 /api/plugins/http/,spawnBrowser 回调路径、callback redirect_uri、
UI fetch 基址)—— UI 上点登录/回调一直会 404。
顺带:e2e 就绪探针复用 startGateway(无源时 /v1/models 空列表也 200)。
|
2026-10-11 21:44:40 +08:00 |
|
|
|
6f2b6b8d3d
|
feat(channels): device-poll 通用执行器 + 行为对齐测试(照参考实现逐行移植)
按用户要求:不是从头造,而是移植参考实现(minimax-oauth.ts)的行为,
并照对方代码的行为形成测试项。
新增(channels.lua):
- b64url/hexToBin/devicePkce:PKCE S256(对齐 createMinimaxPkce)
- deviceOneStep:轮询决策单步执行器,分支顺序逐行对齐 pollMinimaxDeviceToken:
200+status=pending → 等
200+status=slow_down → interval+5s,等
200+status=denied/access_denied → failed(用户拒绝)
200+status=expired/expired_token → failed(码过期)
非200+error=authorization_pending → 等(OAuth 标准形态)
非200+error=slow_down → interval+5s,等
200+access_token → 成功
其它 → failed(带 error 文本)
- start_login device-poll 分支:申请设备码(PKCE)、user_code/授权URL透传、
verifier 只存会话不回显
- poll_login:device-poll 会话每次推进一步(页面驱动,不阻塞 —— 插件单VM单锁,
阻塞循环会卡死本插件全部钩子);成功即落凭据+接入源
- set_endpoints 导出:运维把渠道端点指向自建代理/镜像;也供测试打假上游
- plugin.exports = { set_endpoints }
测试(channels_device_test.go):假上游逐次响应,8 行决策表逐行断言
(pending 等待 / slow_down interval+5 / denied→failed / expired→failed /
authorization_pending 等 / slow_down(4xx) 等 / 成功→completed+account 取
nickname / error→failed 带文本)。全部通过。
教训(词法作用域):devicePkce 原引用文件后段才定义的 newToken,Lua 下那是
nil 全局调用 —— 已改为自含熵源。
|
2026-10-11 20:08:00 +08:00 |
|
|
|
96d97b0e0e
|
feat(channels): MiniMax 渠道 —— PKCE 设备码流(RFC 8628)
第二个真实厂商,采用纯 HTTP 的设备码授权(无需本地服务器):
- 申请: POST account.minimax.cn/oauth2/device/code
body: client_id=mcode-public&scope=agent.default&audience=agent-backend
&code_challenge={S256(verifier)}&code_challenge_method=S256
- 轮询: POST account.minimax.cn/oauth2/token
body: grant_type=device_code&device_code=..&client_id=mcode-public
&code_verifier=..
- 签到: POST agent.minimax.cn/minimax-cloud/api/v1/signin/claim body {}
- 模型 fallback: MiniMax-M3.1-Flash-Preview / M3 / M2.7-highspeed
(对齐参考仓库 minimax-product.ts)
device-poll 流程的三步状态机在 start_login/poll_login 中实现:
begin 时生成 PKCE verifier/challenge 并申请设备码,把 verifier 存进
会话(只在换 token 时用一次,不回显),页面轮询 token 端点直到
authorization_pending 结束。
|
2026-10-11 19:29:58 +08:00 |
|
|
|
6de9f28269
|
feat(channels): 逐厂商支持启动 —— TRAE 真实渠道接入 + checkin 表驱动
用户确认:仿照示例插件逐个支持厂商,自用、风险已知悉。
PLATFORMS 表升级为完整平台描述(对齐 dsh-our-free-model/channel-pack):
- adapter 字段:接入内核用哪个协议适配器
- checkin 字段:每日签到 {url, method, body, headers}(可缺省=不支持)
- hosts 字段:登录域与上游域(供 plugin.hosts 推导)
- claim_fn/claim_result_fn 钩子位:特殊签名/响应解析的厂商挂点
TRAE(字节)为第一个真实渠道,作为后续厂商的模板:
- 登录:callback 流(www.trae.cn/authorization,token 直传)
- 模型 fallback:DeepSeek-V4-Flash/Pro、Kimi K3、GLM-5 系、Doubao-Seed-2.1
- 签到:POST https://api.trae.cn/trae/api/v2/ug/checkin_credits/claim body {}
(端点逐字对齐参考仓库 trae-credits.ts)
plugin.hosts 不再手写:declaredHosts() 从平台表推导(hosts+base_url+checkin.url),
避免"两份名单漂移"(参考仓库同类真实缺陷的教训)。
内核零改动。UI 登录框补回 ch-vnc-wrap 内嵌容器(上次重构时遗漏,VNC 兜底
流程会引用不到)。
|
2026-10-11 19:26:21 +08:00 |
|
|
|
74ec1e7874
|
refactor(lua): 内核回归六项职责 —— 登录/浏览器全部移入插件
用户钉死了内核的职责边界,本提交把越界部分全部拆除:
内核只做:加载适配器 · auto 调度 · 加载插件 · 调度插件 · 维护源与 key · 签发密钥
## 架构纠正(用户指出的根本错误)
我此前把信任模型弄反了。实测:插件 VM 的 os.execute/io.popen/package 全部可达
(true,true,true) —— 插件从来不是 OS 级沙箱。正确的边界是:
插件 可信(运维安装),非沙箱,有完整 OS 能力 → 平台知识/登录/浏览器归它
适配器 不可信(可被动态安装),请求级 → 沙箱归它(12 个内置适配器实测均不碰
os/io/package,后续沙箱化零风险,属独立一步)
## 删除(内核越界部分)
- plugin_browser.go 内核浏览器管理器(Xvfb/chromium/x11vnc/websockify
进程栈、回环监听器)—— 388 行全删,浏览器由插件用
os.execute 自己拉起(实测 spawn+reap 闭环可用)
- plugin_login.go 登录会话状态机 + LoginFlow 枚举 + VNC 全局门禁
—— 457 行全删。"登录"从内核概念中消失
- gateway/browser_proxy.go viewer 反代
- handlePluginLogin API 登录会话列表端点
- vnc_enabled 全局开关 改为 per-plugin 授权(见下)
## 新增(内核职责内的通用机制,无任何登录/浏览器语义)
- plugin_outbound.go 通用额外出站主机授权:插件声明 plugin.hosts(请求)
→ 运维在设置页逐个批准(授权)。与 kernel actions
同一"声明≠授权"模型。内核只知道白名单加减,不知道
host 被用来干什么
- on_unload 生命周期钩子 卸载是"调度插件"的一部分:内核在关闭插件状态前调用
插件导出的 on_unload,让它自清理外部资源。内核不知道
资源是什么(进程/句柄/远端会话都行)。带进程回收回归
测试 TestOnUnloadReapsPluginProcesses
## channels.lua 全量接管
- 登录会话自持:plugin.state.sessions(nonce 只在 begin 响应出现一次,
惰性过期清理)
- 服务端浏览器栈自持:spawnBrowser 用 io.popen 拉起 Xvfb/chromium/x11vnc/
websockify 并记 pid,reapBrowser 回收;on_unload 兜底
- 优先级阶梯(用户指定):① 本机浏览器(给 URL)→ ② 自动化(轮询/nonce 回调)
→ ③ VNC 无头浏览器兜底(无浏览器系统/自动化无法完成的场景,如回环重定向
必须落在服务端)
- plugin.auth_hosts → plugin.hosts(语义泛化:出站主机授权与登录无关)
## 验收
- 全仓旧符号零残留(grep 权威确认,代码+注释+lua+测试)
- 内核非测试代码零厂商名
- build + vet 全绿;lua/core/gateway/types/scheduler/provider 全量测试通过
- channels 端到端(登录→凭据→add_source→模型可见)继续通过
|
2026-10-11 16:24:55 +08:00 |
|
|
|
fa50ec8bc7
|
feat(lua): 服务端浏览器登录(VNC)+ 内嵌页签 —— 回环回调的唯一自动化路径
用户指明的场景修正了我的一个实质错误。我之前写"VNC 不解决取凭据,凭据还在
浏览器 profile 里"——这对 cookie 类成立,但对**回环重定向**不成立:
浏览器在用户机器 → redirect 到 127.0.0.1:P → 打到**用户的**回环 → 服务端收不到 code
浏览器在服务端 → redirect 到 127.0.0.1:P → 打到**服务端的**回环 → 插件直接收到 code
后者不需要"提取凭据",code 自己流到监听方。所以对回环类登录,服务端浏览器
不是权宜之计而是**唯一自动化路径**。已实测验证(浏览器在服务端时 loopback
回调的 code 落到了服务端监听器)。
## 实现
plugin_browser.go:Xvfb(:N) ← chromium → x11vnc → websockify(noVNC) + 回环回调监听。
五个进程一起起停,因为失败模式是顺序相关的(崩掉的会话留下的 Xvfb 会让下一个
chromium 以看起来像浏览器 bug 的方式失败)。
**默认关闭,且不是"开了 VNC 就能登全部"**:每个平台单独走 vnc 流程才启动。
理由是这套东西风险最高——在服务端跑真实浏览器登录用户的第三方账号。
**边界**:插件拿到的是 view_url,**不是浏览器控制句柄**。CDP 端口不进 Lua。
否则一个插件就能读所有标签页的 cookie,而那个浏览器登录的是运维的账号。
**teardown 必须删 profile**:里面是活的第三方 session cookie,删它是安全契约
的一部分,不是收尾工作。
## viewer 反代(browser_proxy.go)
/browser/<id>/... → 该会话的 websockify。为什么不直连端口:
1. 鉴权——直连等于把已登录的浏览器暴露给任何能访问该端口的人(实测未鉴权
访问得 302 → /login,带 admin key 得 3912 字节 noVNC 页面)
2. WebSocket——noVNC 核心是 WS 升级,httputil.ReverseProxy 原生处理
3. 同源——WS 路径不需要跨域配置
## 内嵌在插件页签(用户要求)
viewer 用 iframe 内嵌进 channels 插件的登录区,而不是让人跳出去开新窗口:
- 插件 UI 拿到 view_url 就塞进 iframe src,登录完成/失效时清空 src 并隐藏
- 插件只对 `flow = "vnc"` 的平台起浏览器;其他流程继续用用户自己的浏览器
(等于白开一个高风险的浏览器是浪费)
## 插件本体:channels(同类插件)
单插件多平台,4 个平台示范四种流程(link-poll / callback / paste / vnc)。
登录 → 凭据进保险库(不进 state)→ add_source 接入内核 → 定时领额度。
声明式:plugin.kernel 申请内核动作、plugin.auth_hosts 申请登录主机,
设置页显示"申请中"由运维批准。平台表加一条就多一个平台,内核零改动。
开发中被测试抓到的三个真实缺陷(都在我自己的代码里):
1. handler 返回 `{url=…}` 没有 status/json → 内核**静默丢弃**,200 空响应、
无任何错误。已把所有 handler 统一为 {status,json},并加契约测试锁死。
2. 插件把 req.body 当表用 → 实际是 JSON **字符串**,所有平台参数都读不到。
加 bodyOf() 统一解码。
3. 顺带发现工作树里 discover.go 被改成 `return "image"`(非本次改动,像是早先
变异测试留下的),会改变模型类型推断;已还原。
测试:服务端浏览器会话(进程栈/默认拒绝/回调捕获/teardown 清理 profile)、
viewer 反代鉴权、插件 VNC 端到端、channels 插件端到端(含未授权时如实报告
source_error 而非假装成功)、handler 契约。
|
2026-10-11 14:26:28 +08:00 |
|