Skip to content

v2.2.4

Latest

Choose a tag to compare

@github-actions github-actions released this 21 Sep 07:03

新增

模型可以直接在对话里画出「会动的图」—— 内置可视化 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: falsefindIdentity() 直接返回 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.tsregisterIpc() 之后立刻盖章,并重推一份完整快照——闸门自己那几次推送全都发生在它还是 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-builder Skill 新增 references/contribution-points.md:contributes 的每一个键「宿主到底拿它做什么」逐条列出,并把装得上却什么都不做的那些(slashCommandsagentsmodesthemes,以及 views.locationsidebar / 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 typechecknpm run lint 通过(0 error,4 个既有 any warning)