Skip to content

技术验证:自定义工具宿主(未通过,Windows 屏幕未测) - #136

Merged
wynxing merged 12 commits into
mainfrom
cursor/toolhost-validation-a0ce
Oct 15, 2026
Merged

wynxing merged 12 commits into
mainfrom
cursor/toolhost-validation-a0ce

Conversation

@wynxing

@wynxing wynxing commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

做了什么

在 spikes/toolhost(包名 lanwork-toolhost-spike)里加了一份技术验证,用来在写产品界面之前证明自定义工具的宿主模型。它不进 lanwork 的依赖,安装包脚本仍然只编 lanwork 和 lanwork-shortcut。

验证程序按 docs/tools.md 和 docs/tool-dev-guide.md:

  • 用 mlua 嵌入 Lua 5.4。沙箱不放入 os、io、package、debug、require、load、loadfile、dofile、collectgarbage,也不放入协程(钩子按 Lua 线程安装,协程是另一条线程)。单次回调超过 1 秒中止,并带行号。pcall 接住之后钩子仍继续报错,这次回调不能接着跑完。xpcall 由宿主提供:被保护的函数出错并展开之后才调用处理函数,超时钩子仍然开着,处理函数里的死循环同样中止。
  • string.find、string.match、string.gmatch、string.gsub 由宿主移植的 Lua 5.4 lstrlib 匹配器执行,语义与原来一致。匹配循环每 4096 步对照这次回调的 1 秒截止时间。到点则与钩子一样抛出「脚本超时」并置中止标记,pcall 不能把这次回调留住。文本上限 1 MiB、模式上限 8 KiB,超过则这次调用报错,pcall 可以接住。gsub 各层还没交还的结果缓冲区加在一起,跟 Lua 已用内存计入同一块 16 MiB 临时预算;超过则这次调用报 not enough memory,不置中止标记,pcall 可以接住,回调继续。复制进结果的字节大约每 64 KiB 再对照截止时间,到点与钩子一样中止。这四个模式函数同时嵌套最多 8 层,到顶之后这次调用展开完之前不能再进。xpcall、table.sort 以及这些模式函数从 Rust 再进 Lua 时,重入最多 16 层;Lua 5.4 的 lua_setcstacklimit 改不了编译进去的 C 栈上限,所以这层限制放在宿主里。到顶是可接住的错误,不置中止标记。string.rep 的次数若是字符串,先按 Lua 的整数规则转换(含分隔符长度)再套 1 MiB 上限。这些字节数和层数是验证程序的临时上限,不是产品规则。不带比较函数的 table.sort 在超过 10 万个元素时拒绝;带比较函数时每次比较回到 Lua,钩子仍然有效。table.concat、string.format、大小写、反转和 utf8 按长度线性完成,并受临时内存上限约束。string.format 的宽度最多两位数字。
  • 解析 TOML 清单。不认识的字段、组件或服务拒绝,原因归到「清单无效」。
  • 软件渲染的悬浮窗外壳画出清单里的文本、按钮、计时和进度条。
  • 开发说明里的番茄钟示例走完暂停、跳过、完成、走到 0、满 4 轮工作后长休息、换走或清掉当前待办、安静时段不弹出、重新打开没有上一次的剩余时间。
  • 密钥放在宿主侧。Lua 没有读取函数。已确认主机的响应里,只要对上列出的形式,整段正文清空,ok 为假,说明是「请求失败」,状态码仍保留。检查的形式:原文;Bearer / bearer 加原文;实际发出的请求头值;上述各项的标准 Base64 与 URL 安全 Base64(带填充和不带填充,不按长短丢弃);%XX 百分号解码(十六进制忽略大小写,未保留字节也可以编码,可以只编码其中一部分,最多四次)之后再找这些形式;+ 再按空格看一次。短编码可能和无关正文撞上,这时也会清空。这是对已确认主机的尽力检查。Windows 自动测试写入凭据管理器(CRED_PERSIST_SESSION)。目标名 Lanwork.Spike.ToolHost/<工具 id>/<密钥 id> 只为这次验证,不是产品规则。
  • HTTPS GET 只发往清单里的主机名。请求在另一条线程上连接和读取,脚本回调不等待网络。POST 是「不在范围内」。附带密钥的明文 http 不发送。

通过标准和结论写在 docs/roadmap.md「技术验证」。该项未通过。

测量

2026-10-15,commit 8ebadf01cc7c35ac85b08d7994338bdeed9f7df0,release,Slint 1.18.1,软件渲染,Lua 5.4。机器是 Linux(Ubuntu 24.04.5,x86_64,4 核),不是 Windows 11。

读数 值
打开示例到 on_load 再加一帧 30 ms
这一帧 18 ms
VmRSS 14,495,744 字节
RssAnon 1,593,344 字节
随后空闲 5 秒的进程 CPU 0%

这是 Linux 验证进程的读数,不是 Windows 的 Private Bytes,不是 5 分钟桌面采样,也不是性能测量验收。不写「已达到」。数字上限仍待定。

未测

  • Windows 11 桌面上这扇窗口长什么样
  • Windows 桌面上的空闲采样
  • 凭据管理器界面

Windows CI 会编译并跑凭据读写的自动测试。那不是屏幕检查。

待定

沿用规格里还没定的部分,验证程序没有把它们写成产品规则。主要包括:清单版本字段、内存字节数和 CPU 等数字上限、模式匹配的临时文本和模式长度上限、gsub 结果的临时内存预算、模式函数和宿主重入的临时层数、string.rep 的临时结果上限、不带比较函数的 table.sort 的临时元素上限、timer.after 的最短秒数、睡眠或关机期间如何补计时、不附带密钥的明文 http、同一主机名内是否跟随重定向、凭据目标名和持久级别、默认窗口大小和计时显示格式、require 与多个 Lua 文件、一次前进跨过多个间隔要不要补叫、组件计时和服务计时在同一拍里谁先。协程不放入,已经定了。已确认主机回显密钥时整段正文清空,已经定了;检查是尽力的,只覆盖「密钥」一节列出的形式,Base64 不按长短过滤。

测试

cargo test -p lanwork-toolhost-spike 在这台 Linux 上通过,含番茄钟示例、额度示例、沙箱超时、pcall 包住的死循环、xpcall 两个死循环以及处理函数里的死循环、与 Lua 5.4 对照的 string.find / match / gmatch / gsub、64 KiB 上未锚定的 .*x 约 1 秒显示「脚本超时」(pcall 和 xpcall 都不能把这次回调留住)、$? 重复 26 次不会一直跑、string.gsub(string.rep("a", 700), "", string.rep("x", 512 * 1024)) 在 16 MiB 预算内停下并报可接住的 not enough memory(回调继续,窗口没有「脚本超时」)、嵌套 gsub 的各层结果缓冲区加在一起计入这块预算、递归 gsub 在深度 48 和 100 时于 8 层停下、递归 xpcall(含处理函数再次调用自己)和递归 table.sort 在重入上限停下且进程不崩溃、复制满 64 KiB 会对照截止时间、超大 string.rep(含 "2000000" 这种字符串次数)、不带比较函数的大表 table.sort、自签证书的 HTTPS GET、响应回显密钥(含百分号编码的未保留字节、部分编码、大小写混合的十六进制,以及短密钥 xyz 的 Base64 eHl6;命中后正文为空)、读凭据失败,以及软件渲染的像素检查。

Windows 上 cargo clippy -D warnings 曾把凭据管理器读写标成死代码:非测试的库目标没有调用方。这段 Win32 代码现在只在 cfg(all(windows, test)) 里编译,由凭据往返测试写入、读出并删除。命令行验证仍用内存存储。

同一轮 Windows 测试还报「缺少 toml 代码块」:检出把开发说明转成了 CRLF,代码块标记按 LF 查找。解析前先把换行收成 LF。

Open in Web Open in Cursor 

cursoragent and others added 12 commits October 15, 2026 07:31
在 spikes/toolhost 里用 mlua 嵌入 Lua 5.4,按规格做沙箱、清单、悬浮窗外壳、密钥和 HTTPS。产品界面仍不接入。Windows 屏幕检查标未测,该项未通过。

Co-authored-by: Wynn <wynn0921@qq.com>
把 Linux 上这次 release 测量对应的 commit 写进技术验证结论。

Co-authored-by: Wynn <wynn0921@qq.com>
非测试构建里没有人构造它,clippy 会把它当成未使用。从 crate 根导出后,Windows 上的检查才能通过。测试仍然会读写凭据。

Co-authored-by: Wynn <wynn0921@qq.com>
非测试的库目标没有调用方,clippy 把 Win32 凭据代码当成死代码。改为 cfg(all(windows, test)),由凭据往返测试实际写入、读出和删除。

Co-authored-by: Wynn <wynn0921@qq.com>
Windows CI 把说明转成 CRLF 后,代码块标记对不上,番茄钟等示例测试报缺少 toml 代码块。解析前先把换行收成 LF。

Co-authored-by: Wynn <wynn0921@qq.com>
Windows 检出的说明本身已是 CRLF。直接把每个 LF 换成 CRLF 会留下多余的 CR,测试自己对不上代码块。

Co-authored-by: Wynn <wynn0921@qq.com>
钩子在截止之后每条指令都报错,宿主再把这次回调记成超时。HTTP 改到另一条线程,脚本回调不等待连接和读取。响应正文和响应头里的原文、Base64 和百分号编码在交给 Lua 之前去掉。读凭据失败时先放开借用再调用脚本。

Co-authored-by: Wynn <wynn0921@qq.com>
xpcall 改由宿主在错误展开之后调用处理函数,超时钩子仍然有效。string.find、match、gmatch、gsub 和 string.rep 在进入 C 之前按长度和回溯量词拒绝。已确认主机的响应只要对上列出的密钥形式,正文改为空并显示「请求失败」。

Co-authored-by: Wynn <wynn0921@qq.com>
非锚点的 ^ 和 $ 按字面量计,后面的量词不再被清掉。string.rep 的次数字符串先按 Lua 转成整数再套 1 MiB 上限。密钥的 Base64 不论长短都检查。

Co-authored-by: Wynn <wynn0921@qq.com>
未锚定的 .* 会从每个起点重试。宿主按 lstrlib 移植 find、match、gmatch、gsub,每 4096 步对照回调截止时间,超时与钩子一样抛出「脚本超时」并置中止标记。量词分档的文本上限去掉,只留文本和模式的长度上限。不带比较函数的 table.sort 在元素过多时拒绝。

Co-authored-by: Wynn <wynn0921@qq.com>
@wynxing
wynxing marked this pull request as ready for review October 15, 2026 12:08
@wynxing
wynxing merged commit 01da2ed into main Oct 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants