新增
模型可以直接在对话里画出「会动的图」—— 内置可视化 widget
- 新增工具对
visualize_read_me/visualize_show_widget:模型讲「复利是怎么滚起来的」「这个 transformer 的结构」时,不再只能一行行写文字 —— HTML/SVG 作为工具参数传进来,在对话流里内联渲染成一个真的在跑的页面(滑块能动、图表能画、脚本会执行),不是截图,也不是代码块 read_me按模块下发 7 万字设计规范(图表、图类、艺术风格、UI 组件、配色、SVG 预置),show_widget收代码,它的说明里明确要求先读规范;没有照抄原版那个「先读过文档」的布尔门闩 —— 门闩的代价是模型被自己误写的一个参数卡住整整一轮,收益只是少漏读一次- 沙箱是独立的
ncw-widget://协议加一个sandbox="allow-scripts"的不透明源 iframe:widget 是模型生成的任意 HTML,父文档拿不到它的 DOM,它也拿不到聊天界面。不复用插件协议(内置工具没有 pluginId,两种 CSP 策略也不同),也不用srcDoc(它继承父文档的 CSP,开发时能跑、打包后内联脚本全被拦,症状是一句谁也读不懂的 Refused to execute inline script) - widget 跟随应用主题:把本仓库的 token 翻译成规范认得的那套 CSS 变量名,换一套外观,图里的卡片、描边、强调色跟着变,模型不需要知道这件事存在;规范里的 info 色族对到 accent,于是 widget 里的链接与界面其余部分同色,并跟着换色器走
- 内容按固定间隔节流推进 iframe(间隔内多次变更只保留最新一份;不用 debounce —— token 连续不停时它会让整整一代内容永远等不到发送),收尾那一帧立刻发,它决定图表何时画出来;高度由 iframe 自量自报,宿主钳制后生效
- 刻意不给
sendPrompt()(原版 widget 里「像用户打字一样往对话里发一条消息」的函数):它是 widget 通往 agent 主循环的一条输入通道,而 widget 内容是模型生成的、还可能是它从某个网页里读来的;read_me的说明里明说本宿主没有它,免得模型照规范画出一批点了没反应的按钮 - 工具申报
needsNetwork: true,尽管它自己一个字节都不出网 —— 出网的是它产出的那张卡片(规范推荐从 CDN 拉 Chart.js);申报 false 等于用户关掉「联网」后仍有一条从这里出网的路。代价说清:开关关掉时连纯 SVG 图也做不了,这是刻意的取舍。readOnly: true,三档权限直接放行(联网那道闸仍管得住它)
等用户表态的题面,参数还在流就能读
AskUserQuestion/ProposeGoal/ExitPlanMode的入参就是给人读的题面。此前从第一个 token 到待决面板出现往往隔着好几秒,那几秒里工具卡上只有一行工具名,用户不知道自己马上要被问什么;现在题面边写边渲染- 预览里的一切不可作答:可作答的题面只有主进程待决表一个来源,预览是从半截 JSON 投影出来的,连
interaction.id都不存在 —— 拿它渲染的控件全部 disabled;切题可以点(只是翻页,点不出任何会失败的承诺),作答不能点 - 只读预览与可作答的卡是同一套组件:题面从预览换成可作答的那一瞬间不跳版式,而那正是用户盯着看的时刻;折叠摘要报「共 N 题」—— 多道题的卡片折叠起来只有一行,不写清一共几道,用户看到待决卡只显示第一题会以为就问了这一件事
- 工具状态词新增两档:等表态(「球在你那…」「等你拍板…」—— 这一档里 Agent 其实不在干活,词得说清是在等人)与画图(「勾勒中…」「配色中…」)
任务清单:展开卡同一画法,收尾时如实补报
TodoWrite工具卡展开后的清单与输入框上方那张任务清单是同一件东西 —— 同一份数据两套画法,改一处必漏一处,而两者在同一屏上下相邻;入参可能是半截的(流式中每一帧都可能多出半个条目),只做收窄、绝不猜内容- 清单多了「和本轮的关系」三档:
running(本轮真的写成功过,显示进行中与转圈)/stopped(本轮已收尾,未完项按未完成陈列并报出剩余条数)/snapshot(转录里的历史快照,不转圈、不说「正在做」)。此前转录里一个in_progress既可能正被推进、也可能是上一轮的死账,组件推不出来就照画,表现为清单永远「在跑」,用户会去等一个不会来的结果 - 收尾补报:主代理正常收尾时,若本 run 成功写过的清单仍有未完成项,给模型一次内部补报机会 —— 它挡的是「正文里宣布完成、用户正看着的清单却没更新」:回执与界面长期对不上,全程零报错。每 run 只提醒一次(不被工具调用重置);不自动替模型打勾、不反复续跑逼它清空阻塞项;Goal/Stop 的续跑与强停裁决优先,计划模式与后台任务不催
「谁还在跑」改为全局广播,运行中角标终于会停
- 新增
agent:activeRuns广播:run 起止与首屏握手都带上主进程的权威集合,外层工作区 Tab、内层对话 Tab、侧边栏会话行三处运行中指示全读它 - 此前那颗圆点只能靠按 run 订阅的事件流收尾,而有两类 run 的结束永远送不到:定时任务起的 run(没有任何窗口订阅过,主进程整批丢弃)和 ⌘R 重载后还没打开的会话 —— 症状是 Agent 早就跑完了、消息也停了,角标一直转到应用重启,状态行却写着「已完成」
- 侧边栏会话行不再兼信
sessions:list快照里的running字段:run 结束时主进程不发会话变更,列表不重拉,那个 true 会一直挂着 —— 表现为对话跑完了侧边栏还在转圈,直到用户恰好改名/归档某条会话才消;一个事实只有一处真源 - 只播顶层 run:子代理的起止不改变这个集合,一次编排十几个子代理不会变成十几次内容相同的广播
文件引用点得开了
- 气泡里的
file_ref(拖进会话的文件)与行内@引用此前只读,点上去毫无反应 —— 而它们长得像按钮,那是一次注定失败的承诺;现在点了就在右侧工作台打开,由已装插件决定用谁打开 - 打开前先确认它还打得开:引用是发送那一刻的快照,文件后来被删掉、改名是常态,直接开只会留一个只显示错误的 Tab 要用户自己去关。判定是真读一次而不是 stat(stat 只回答「存在」,会让「预检说能开」和「打开是空的」分叉);打不开一条 toast 说清原因,同一条引用连点多次是一条通知,不是铺满屏幕
- Markdown 里的文件链接走同一条判定,失败原因画在链接旁边;「打不开」的判定与文案全应用只有一份
侧边栏左下角有了账户菜单
- 原先那张「账户信息 + 齿轮」合体卡片整个是一块可点区域,点哪儿都开设置,于是「看/管自己的账户」在界面上没有入口(设置 › 账户页里有,但没人知道要走那儿);现在拆成两块:左边点开账户菜单(头像/昵称/邮箱、充值、邀请、支持、登出),右边齿轮仍然直接开设置,并可直达指定设置页
- 余额只读登录状态里那一条数据链,菜单里不存第二份 —— 两处各存一份迟早复现「设置页和菜单里的数字对不上」;那颗刷新按钮只负责发起刷新,结果由广播回来
- 邀请入口是「去浏览器里拿」而不是在菜单里显示链接:邀请码那条接口要网页会话 cookie,桌面端令牌会被平台白名单判失败,客户端拼不出真链接 —— 不编一个码,也不把带码的链接写死
改动
- macOS 发布包启用签名与公证:此前两平台都故意不签名(当时没有证书),而不签名的包既过不了 Gatekeeper,也过不了更新器的 designated requirement 校验 —— 表现为更新包下载成功但装不上。过程中发现
CSC_IDENTITY_AUTO_DISCOVERY: false时findIdentity()直接返回 null,签名和公证会一起静默跳过、连CSC_LINK都不读,于是打包前先点名缺哪个 secret(「签名成功、公证被静默跳过」是最贵的失败:CI 全绿,用户拿到的包照样报警);证书只在 macOS 那一行注入 —— 设成全局的话 Windows 会拿 mac 的 p12 去签 exe - 脚手架与 CLI 包(
create-nextcowork-plugin/plugin-cli)升至 0.2.0
修复
启动偶发停在「首屏握手失败: No handler registered for 'app:getBootstrap'」——重载一次就正常
- 闸门此前只按「库里没有要迁移的东西」放行。可窗口为了画迁移进度屏被提到了
registerIpc()之前(窗口必须早于闸门,闸门必须早于openDatabase(),见main/index.ts),于是没有迁移的那一次启动里,渲染层只要比主进程快一点,挂上的 App 第一个 invoke 就撞上还没登记的频道 - 放行判据拆成两半、缺一不可:「闸门里没事」加「主进程答得上来」(
MigrationState.ipcReady)。后一半是启动序列上的一个里程碑,由main/index.ts在registerIpc()之后立刻盖章,并重推一份完整快照——闸门自己那几次推送全都发生在它还是 false 的时候,只翻标志而不重推的话,用户在失败页点「继续」会一直没反应 - 判据抽成纯函数
views/migration-release.ts:它错的唯一形态(渲染层比主进程快)起一次 Electron 也未必撞得上,留在组件里就只能靠手测(见下面的测试) - 顺带把这条错路上的诊断写清楚:启动流程在
registerIpc()之前崩掉的话,窗口已经建出来了,它会一直停在空白首屏、界面一个错都不报(闸门不放行,它也递不出错误),此时日志里的[app] 启动流程失败:是唯一线索。原先这种情况下渲染层报的是「频道没登记」,而真正的病因是主进程启动崩了,两者几乎没关系
合并旧数据卡在 UNIQUE constraint failed: usage_records.id,点重试每次都停在同一处
- 「这条会话缺 ⇒ 它的子行也缺」这个假设在子表上不成立:
usage_records只增不删、也没有指向sessions的外键,会话被用户删掉时它的用量行留在库里;于是「目标库里缺这条会话」与「它的用量行已经在目标库里」可以同时为真,而补会话那条 INSERT 只按父表判过缺 - 子表改成判它自己的主键(从
PRAGMA table_info读,不写死id——file_snapshots的主键是复合的,写死会让那一张的判重静默失效)。已经在那里的行一个字节都不动 - 没有改用
OR IGNORE了事:判完唯一约束仍然是兜底,非主键的唯一性(索引、外键)坏了照样抛,让这次会话整体回滚、把错误摆在闸门上,而不是静默变成「少了几条消息」
Windows 上每起一条后台命令就弹一个独立控制台窗口,停止命令还要闪黑框
- spawn 的
detached在 Windows 上被翻成DETACHED_PROCESS,而这个标志会让系统忽略「不弹窗口」,于是每跑一个钩子、每起一条后台 Bash 都弹出一个控制台;现在 Windows 一律不传它 - 「杀整棵树」与「脱离会话」拆成两件事:Windows 那条路靠
taskkill /T,本来就不依赖进程组;此前混用一个开关,Windows 上会退化成只杀那一个 shell ——npm run dev的 node 活下来继续占着端口,而界面报告已停 taskkill补上「不弹窗口」:主进程是 GUI 程序,每次停止/超时都会闪一个黑框,Win11 上还可能被「默认终端应用」接管成一个真的终端窗口
Bash 输出里混着看不见的 ANSI 转义序列 —— 看着像乱码,行过滤一条都匹配不上
- 管道不是 TTY,但很多 CLI 照样着色(vitest 用的 picocolors 在 Windows 上无条件开色);ESC 本身不可见,用户看到的是「
[32m✓ [39m src/…」这种读不下去的东西,只会以为命令输出坏了 - 剥在入口而不是回读时:
BashOutput的按行过滤会被行首的ESC[31m弄得一条都匹配不上(而输出看起来完全正常),环形缓冲与回给模型的输出预算也不该被转义序列吃掉 - 终端(PTY)绝不过这道 —— xterm 靠这些序列画屏幕,剥掉等于把终端变成一段乱糟糟的纯文本
文档
plugin-builderSkill 新增references/contribution-points.md:contributes的每一个键「宿主到底拿它做什么」逐条列出,并把装得上却什么都不做的那些(slashCommands、agents、modes、themes,以及views.location的sidebar/panel)明确标出来连带原因——没有可挂载的 Tab kind、扫描器不读它们、打包器不复制agents/与modes/目录。此前作者只能从「装上了、界面上没反应」反推engines.nextcowork的说明补上「写你真正需要的那个最低版本,而不是最新的那个」;打包排查表新增两行:声明了 skill 而模型看不见、声明了 agents/modes/themes/slashCommands 而毫无反应
测试
- 新增
views/__tests__/migration-release.test.ts:把五种启动时序直接摆出来——主进程还没应答时idle也不放行、能应答之后放行、迁移进行中与失败页照常画、在主进程能应答之前点「跳过并继续」不生效 legacy-merge新增一条按子表主键判重的用例(用量行已经在、它那条会话缺着时补会话而不是撞唯一约束,且第二次合并仍然不抛);原来「制造第二条会话必然失败」的手段换成一条悬空外键——旧手段(消息 id 已经在目标库里)现在被判成正常情况并跳过,不再是失败- 新增:widget 协议(只服务两个固定路径、其余一律 404、CSP 头)、外壳运行时的构建产物自包含、iframe 同步算法对半截 HTML 的合入、内容帧节流用假时钟钉住「间隔内只发最后一份」「收尾帧不等窗口」、
visualize工具与规范按模块下发、widget 主题映射表覆盖规范正文实际用到的变量、交互与 todo 预览对半截 JSON 的收窄(含「拿不准就不画」)、待决卡的只读预览不与 QA 探针属性相撞、文件引用与 Markdown 链接的「先预检再开」、活跃 run 广播让丢过事件的窗口角标收敛、todo 补报的「每 run 一次额度」 - 完整套件 5733 项全部通过(23 skipped);
npm run typecheck与npm run lint通过(0 error,4 个既有 any warning)