源文件 按 main 分支生成 · 最新发布 v0.4.0

运行时

窗与窗格、守护进程、托盘、终端、本机接口。

本页
  1. 守护进程(Daemon)
  2. 窗口(App window)
  3. 退出(Quit)
  4. 登录启动(Launch at login)
  5. 托盘(Tray)
  6. 本机接口(Local API)
  7. 本机 token(Local token)
  8. 本机记录(Records)
  9. 界面方案(Interface profile)
  10. 中断(Interrupted)
  11. 补跑(Catch-up)
  12. 终端(Terminal)
  13. 远程屏幕(Remote screen)
  14. 命令流(Command stream)
  15. 窗格(Pane)
  16. 布局(Layout)
  17. 版本闸(Schema gate)

守护进程(Daemon)

本机常驻的 TypeScript 进程,自己跑 tool-call 循环。关窗不停;窗口崩了轮次还在。插电时,有轮次在跑或有渲染在等,它就挡住空闲睡眠(屏幕照样会灭);合盖、菜单栏睡眠、用电池、关机仍会睡,唤醒后续上。只有窗口进程还活着(含藏到托盘)时,被杀的守护进程会被拉起来;窗也没了之后再崩就停,直到你再开 App 或下次登录。设置里有可选独立运行时,默认关,生产在原生封闭门通过前保持禁用;未开启时监督仍是窗口进程。它还持有你开的终端会话,所以关窗之后那些进程照跑。退出或守护进程自己停掉时,进程一起停,但开在哪、屏幕上有什么会留下来;下次启动在原来的位置重新开一个,分屏布局按这个接回去。结束会话才忘掉它。

回避:云电脑, 24/7 服务器, 把 UI 进程当运行时, 合盖仍继续, 杀一次就停, 独立 KeepAlive 登录项, 关窗杀掉终端

窗口(App window)

Tauri 桌面窗,里面是 Svelte 写的界面(代码和开发说明里叫信使,messenger)。关窗是隐藏到托盘,不是退出;隐藏时仍留在 Dock。登录时只到 Dock 和托盘,窗不弹。点 Dock / Cmd-Tab / Spotlight / 托盘左键 / 再开 App 把窗叫回来。同时只跑一个窗口进程;第二次启动只唤醒已有的那个并连上已有守护进程。窗口崩了不会自己回来。连上守护进程之前:退出可用,Stop 不可用,界面显示「连不上运行时」。拖过的宽高和是否最大化,退出后再开还是关前那样;不记位置,免得换显示器落到屏外。拖边框的过程中不去问窗口是不是最大化:那一问在 macOS 上会把窗口挪开,松手时位置就和拖着的时候不一样。

回避:只用浏览器打开, 无托盘, 隐藏即从 Dock 消失, 窗口 KeepAlive, 登录抢屏, 多窗口, 窗崩当退出

退出(Quit)

结束这次会话。窗口监督(默认)时窗口进程和守护进程都停,托盘和 Dock 都消失。独立运行时若已开启,退出只关 UI,作业继续;要停作业须显式 stop(写 runtime.stop)。进行中的轮次标成中断,你开的终端会话里的进程一起停;下次打开时,每个还开着的终端在原来的位置重新开一个,分屏布局按这个接回去。不是关窗。结束会话才忘掉它。不动登录启动偏好。关机和注销在轮次上也按退出处理。开发态同一条规则。生产独立运行时仍 gated,默认仍是窗口监督。

回避:关窗当退出, 默认只关窗留无头守护进程, 退出当关掉开机启动, 开发态 Cmd+Q 只关窗, 把 gated 独立模式当成已开启

登录启动(Launch at login)

可选,默认开。登录时只拉窗口进程到 Dock 和托盘,窗不弹;窗口进程再确保守护进程在跑。关掉只影响下一次登录,不杀当前这次;下次登录窗和守护进程都不来。退出也不清这个开关。守护进程不是自己的登录项。

回避:登录弹窗, 关掉立刻杀掉当前这次, 守护进程自己当登录项, 退出清掉开机启动, 关掉开机启动却留下会自己起来的作业

托盘(Tray)

窗口进程上的菜单栏图标。左键把窗叫回来。右键:显示窗口、Stop(没开窗则最近活动轮)、退出。没有隐藏、没有暂停守护进程。窗崩了托盘也没了。

回避:托盘角标, 第二套界面, 守护进程自己做托盘

本机接口(Local API)

守护进程在 127.0.0.1 上给窗口用的 HTTP + WebSocket,带本机 token。变更走 HTTP;转录流走一条只收的 WebSocket。窗口经它读写名册、发消息、订阅转录流。

回避:把 UI 进程当运行时, 云 API, 双向命令通道, 工作区文件当 HTTP 资源, 把本机 token 放进 URL

本机 token(Local token)

守护进程每次进程启动新铸的随机秘密,用来认证对本机接口的请求。HTTP 用 Authorization: Bearer;WebSocket 在连上后第一条消息带上,不进 URL。窗崩不换;守护进程退出或崩溃后下一进程再铸。不是端点 API key,不进 SQLite,不进仓库。

回避:API key, 安装一次的长活密钥, 写进 .env, 放进 WebSocket query

本机记录(Records)

这台机器上应用自己存下的东西:库里的消息、轮次、工具调用、工作记录、质量事件、规划与任务、交付与审查、需求台账、整理跳的运行记录、判断、花费、复盘、记忆与技能、内置提示词的修改,以及守护进程的日志。Bot 用 describe_data 看有哪些表,用 query_data 跑一条只读的 SQL,用 read_data_log 读日志的末尾,拿来回答「哪类活常被退回」「哪条调用常读不懂」,再据此提改进。只读,在守护进程另起的子进程里跑,10 秒就停;不出批准卡,叫停中也能查。查不到的就是不给 Bot 看的:远程访问的密钥和回执、推送配置、你的终端都在拒绝名单上,数据目录里只读得到两份日志,日志里像密钥的抹掉。只在你要它分析、改进时才查。细节见行为说明·本机记录和 ADR 0065。

行为细节

Bot 能只读地查这台机器上应用自己存下的东西,拿依据分析、提改进(ADR 0065):

  • 三个工具:describe_data 列出表和行数,主要的表附一句说明,后面附几条现成的查询(recipes),给 table 看那一张的列和类型;query_data 跑一条只读的 SQLite 查询,或用 recipe 按名字跑现成的那几条——单条 SELECT 或 WITH … SELECT,? 按 params 顺序绑定,默认 100 行、上限 1000,长格子截断,二进制只给字节数,10 秒没答完就停,列名写错时错误里带上点到的表的列;read_data_log 读 daemon.log(或开发版的 daemon-dev.stderr.log)的末尾,可以 grep。都没有副作用:不出批准卡,叫停中、只读段里也能用。Claude Agent 跑的 Bot 也有。
  • 怎么跑:查询在守护进程另起的子进程里跑(--query-data),只读连接加 query_only,同时最多两个;超时直接杀掉,不卡守护进程。
  • 查不到的:remote_*、pending_keys、request_receipts(远程访问的密钥和回执)、notification_push_config、notification_devices(推送)、terminals(你的终端)、sqlite_*。名单上的表回「no such table」;经视图、索引碰到它们,或想打开别的库、写库的,按查询计划拦下。数据目录里只读两份日志,不跟软链,日志里像密钥的抹掉。
  • 读得到的也包括别的 Bot 的私聊和记忆(只读,不进你的上下文)、你在聊天里贴过的任何东西——密钥该贴在批准卡上,别贴进聊天。
  • 什么时候用:只在你要 Bot 分析、改进时;它拿到的依据用来改自己的技能和记忆,或者经批准卡改内置提示词。

回避:Bot 写库, 让 Bot 读终端或远程访问的密钥, 读数据目录里日志以外的文件, 后台定时去查, 把别的 Bot 的记忆读进自己的上下文

界面方案(Interface profile)

你对应用样子和用法的改动,叠在应用自己的上面:令牌(颜色、字体、圆角、阴影这些设计令牌,浅色深色分开设)、一份固定的用法清单(专注模式、每种窗口从哪打开、回车还是 ⌘回车 发送、时间格式、动效、代码配色、内容区玻璃)、你自己的 CSS,和界面上改过的字。所有窗口和配对的手机共用一份。在任何一个和 Bot 的会话里说你想要什么,Bot 用 describe_interface 看、edit_interface 改:这是一次「改」,按它的权限模式,自动和全部允许下立刻生效,改动先问时出卡、能先在窗口里试看,只读下拒绝;它回复下面挂着撤销。你在「设置 › 界面」里也能看、改、撤销,每次修改都记着谁改的、为什么。停止按钮、等你的卡片和设置入口不能被盖住:哪个窗口里被盖住了,那个窗口就停用令牌和 CSS;?ui=default 和 ⌥⇧⌘U 让一个窗口暂时回到默认样子。拖出来的宽度和布局不在方案里,照旧只存本机。细节见行为说明·界面方案和 ADR 0106。

行为细节

应用的样子和用法你能改,也能让 Bot 照你说的改(ADR 0106):

  • 改什么:令牌(tokens.css 里的任何 --x:颜色、字体、圆角、阴影、聊天最大宽度,浅色、深色分开或一起设);用法(专注模式、每种窗口在桌面上从哪打开、回车还是 ⌘/Ctrl+回车 发送、24 或 12 小时制、动效跟随系统或一直减弱、代码配色、内容区玻璃);自定义 CSS(最多 64 KB);界面上的字(按位置的键,原文的占位符一个不能少);还有主题和语言。拖出来的宽度和布局不在里面,照旧只存本机。
  • 你的消息气泡跟着强调色:改了 --accent 而没单独改 --you 时,你发的消息的气泡跟着强调色变(深色下深一档,白字看得清);要气泡和强调色不一样,就单独设 --you。
  • 怎么让 Bot 改:在和它的会话里说,比如「强调色换成紫色」「侧栏深一点」「改成 ⌘回车 发送」。它先 describe_interface 看现在的样子、可用的钩子和令牌,再 edit_interface 改,然后过几秒再读一次,看各个窗口的回报。按权限模式算「改」:自动和全部允许下立刻在所有窗口生效,它的回复下面挂着「界面已改 · 撤销」;改动先问时出一张 ui-edit 卡,能点「在这个窗口试看」,放行才生效;只读时拒绝。
  • 稳定的钩子:自定义 CSS 用 [data-ui="…"] 当选择器(侧栏、会话行、消息、你的话、Bot 的回复、输入框、批准卡、窗格、标签、设置、流程图……一共二十多个),它们跨版本不变;类名可能会变,应用更新后对不上的选择器会出现在回报里。根上有 data-surface="desktop|tablet|phone" 可以分设备写,data-theme 分浅色深色。
  • 底线:停止按钮、等你的卡片上的按钮、提问卡、去设置的入口、暂停自定义界面和撤销按钮不能被盖住。每个窗口套用之后都查:被方案藏起来、变得几乎透明、缩到很小、点不到或被别的盖住,就在这个窗口停用令牌和 CSS,提示一次,并回报给 Bot。应用自己收起来的按钮不算。
  • 回到默认:地址里加 ?ui=default,或按 ⌥⇧⌘U(Windows、Linux 上 Ctrl+Alt+Shift+U),这个窗口暂时回到应用自己的样子,再按一次恢复;「设置 › 界面」的开关让所有窗口都回去,「全部恢复默认」把四部分一起清掉。
  • 记录:每次修改记着谁改的、为什么、前后的整份方案和当时的应用版本。只能撤销最新的一次,而且只在界面还是它改成的样子时;任何一版都能恢复。设置里连续的保存合成一条,改回原样另记一条。
  • 设置 › 界面:最上面是总开关(这个窗口暂时用默认界面时,原因也写在这里),下面分五页:用法、令牌、自定义 CSS(停手一秒自动保存)和可用的钩子、界面上的字、修改记录(连同各种窗口的回报、全部恢复默认、回到默认的快捷键)。宽窗口里是开关下的一排标签;手机上是一张列表,每行写着这一页改了什么,点开是单独一页,返回回到列表。
  • 代码配色:浅色、深色各从一份固定名单里选一个 Shiki 主题(GitHub、Vitesse、One、Solarized、Catppuccin、Rosé Pine、Gruvbox、Dracula、Nord、Tokyo Night、Monokai……),聊天代码块、命令输出和文件编辑器都用它,连同它的底色和正文色;没选的那种外观用应用自己的(聊天 GitHub、编辑器 Vitesse)。换了之后屏幕上的代码就地重画。暂停自定义界面时它照旧,它算用法。
  • 内容区玻璃:实色、磨砂、通透三档。打开后内容卡片透出窗口的毛玻璃(只在有窗口材质的桌面上:macOS、Windows 11),卡片上的对话(Bot 的和项目会话都算,连同顶上的会话头)、文件预览、流程图和文件树不再各画一层底;菜单、弹窗、浮窗、编辑器和终端保持实色。令牌 --content-glass(卡片留多少,比如 60%)能按外观微调。别在钩子上写 backdrop-filter:模糊是窗口材质做的,它只会把弹出的图片和菜单困在窗格里。
  • 够不着的:PDF 和 Office 预览只认浅色深色;少数组件自己重定义了令牌,要用 CSS 改。字号和间距不是令牌,整体缩放还没有。

回避:Bot 不问就改界面, 用 CSS 藏掉停止或批准按钮, 写在类名上指望它永远对得上, 每个设备一份界面, 把布局和宽度推给手机, 改应用的源码来换样子

中断(Interrupted)

守护进程挂了、被杀后拉起、你退出、关机或注销时,进行中的轮次被标成中断。那些轮里还在跑的命令(连同它开出来的进程)被要求退出;守护进程挂了、或退出时它们还没退完的,下次启动时停掉,停之前先核对这个进程号还是当初那个进程,已经换成别的程序的不碰。转录里那一行和其他 Bot 消息一样带名字和头像(你和它单聊时两样都不带),「继续」贴在「中断」气泡后面:新开一轮、触发条是这条中断行,不重试断掉的那一下;该 Bot 的下一轮(哪个会话都行)能看见「上次断了」,看一次即止。重启打断的活,3 级及以下不会自己接着做,4 级起由监督器按重启原因决定是否、何时自己接着做;会自己接着做的(或被你的叫停扣着的)不发什么,各轮自己的「中断」行就够了。不会自己接着做的(3 级及以下、结果不明的外部调用、开发版接连重启、没挂在事上的轮),重启后在这件事所在、有你的会话里(你最近说它的地方优先)每件各发一句:为什么重启(有意停下、意外退出、开发版重启)、每一轮停在哪一步,带「继续」「先放着」和一条通知;本来要自己接着做、还没来得及守护进程又启动的,由后一次启动补发这一句。细节见行为说明·中断。合盖 / 系统睡眠不是中断:冻住,醒来接着跑。睡着的时间不算卡住,也不算命令超时;被睡眠打断的请求醒来后等网络回来(约一分钟)再整个重发。窗口崩了不是中断:守护进程继续。补全超时、流挂、端点 4xx/5xx 不是中断。健康检查暂时失败而守护进程进程还在,也不是中断:窗不拉第二份,第二份抢不到端口就不改库。

行为细节

守护进程重启时,进行中的轮次都被标成中断,那一行写在这一轮所在的会话里。长活的轮多半在你不在的 Bot↔Bot 私聊里:2026-09-26 一件视频活被重启打断,中断只写在那条私聊里,停了 7.6 小时,直到你自己发现去点了继续。所以重启之后,引擎一起来、调度器走第一拍之前(停机期间到期的回看和日程不会抢在它前面叫醒 Bot),被打断、又不会自己接着做的活每件各发一句。4 级起监督器会自己接着做的(有意停下立即续;意外退出稳定 1 分钟后续一次,开发版重启连续 5 分钟没有再重启后续一次),和被你的叫停扣着的,这一句不发、通知也不发:各轮自己的「中断」行就够,它们各自的中断通知作废、标已读(2026-10-04 起;之前每次开发版重启都发一句、一条通知,2026-10-04 早上《一拳超人》的两句上你都按了「不续」,第二次把一件 1 分钟后本来会自己接着做的活放下了)。要发的是:3 级及以下的、最后一步结果不明的、开发版 5 分钟内接连重启的、没挂在事上的轮(监督器不接);本来要自己接着做、还没到 1 分钟守护进程又启动了的(之后就不会自己做了),由后一次启动补发,写明「它是之前开发版重启时断的,还没来得及自动接着做,守护进程又启动了一次」。这一句发在这件事所在、有你的会话:规划的轮发在你最近说这件事的会话(和监督器自己的提醒一样,你还在那里时),否则规划所在的会话;你不在那里时,依次找这一轮所在的会话、这条 Bot↔Bot 私聊往上开出它的那个会话、你和这个 Bot 的私聊。同一件事、同一个地方的几轮(比如一件规划在两条 Bot↔Bot 私聊里各断了一轮)合成一句。原来先发在规划所在的会话:2026-10-04 一件在群里开、上午在你和视频导演的私聊里接着做的事,两次重启的这一句都发在群里,「中断」行却在私聊里、挨着你最后说的话。这一句是应用写的系统行,不进 Bot 的上下文,显示成应用自己的话(见叫停的按钮):

  • 为什么重启:这一次是开发版(bun --watch / --hot 跑的,或带 REAL_BOT_DEV=1),说「开发版守护进程重新启动了」,不管上一次怎么结束的:--watch 每次保存代码都先发 SIGTERM、不等进程退出就原地重启,收到 SIGTERM 的停机又先写下了「有意停下」,在那里这个标记分不清改代码和退出;不是开发版、上次是有意停下的(退出、更新、你让它重启,写了 last_shutdown = clean),说「守护进程停下后重新启动了」;其余是「守护进程意外退出后重新启动了」。
  • 断在哪:每一轮的 Bot、所在的任务(没有任务就是规划)、不在这个会话时它在哪、最后一步(执行记录里最后一条命令或 MCP 调用)。
  • 接下来:3 级及以下不会自己接着做;4 级起这一句写明监督器的续跑安排(有意停下立即续;意外退出稳定 1 分钟后续一次;开发版连续 5 分钟没有再重启后续一次,不管之前重启过几次;最后一步是结果不明的外部调用、或被叫停覆盖的不自己续),每一轮安排不同时逐个写明,自己续跑走的就是下面这个「继续」。点「继续」,这件事被打断的每一轮都照它自己那条中断行的「继续」接着做:新开一轮,触发条是那条中断行,同样受叫停管——叫停把这几轮全扣住时这次按下不算,解除后再按;只扣住其中几轮时,其余的照常接着做,按钮旁说接着做了几轮、还有几轮被叫停扣着,这一句不算按过,解除后再按接着做剩下的;已经接着做了的、Bot 已经在那里干活的、会话或 Bot 没了的跳过,都已经接着做了时按下就算按过,什么也不多做。点「先放着」就先不动它,中断行自己的「继续」回来,之后还能从那里接着做(这个按钮 2026-10-04 前叫「不续」,读起来像是关掉提醒)。每句只按一次。
  • 通知:发了这一句的,一件事一条(interrupted),这几轮各自中断行的通知作废;同一个会话里被这一句覆盖的中断行,在你按下按钮之前不再各带一个「继续」。没发这一句的,各轮的中断通知作废并标已读(goes_on_by_itself),不弹系统通知、不算 Dock 角标。

哪些轮算被重启打断:守护进程开始一次停掉所有轮时(退出、应用更新或远端重启时的强制排空、守护进程自己停掉),以及启动时收拾上一次留下的活轮时,把那一刻的活轮记下来;下次启动只取仍是中断、中断行还没被接着做的。只有紧接着的那次启动取:中间另有一次运行开过库却没取(没起来就挂了),或者中间跑过一个不认识这条记录的旧版应用、又开过轮,这条记录就过时了,只清掉,不拿这一次重启的原因去说那次早先的停机,那些轮的中断行和通知照旧。发这几句时出错只写一行 daemon.log,守护进程照常起来。卡住被清扫中断的轮不算;强制排空之后又取消的,取消时忘掉。每次启动在工作记录里记一条 daemon.restart(原因和被打断的轮),有被打断的再写一行 daemon.log。通知本身不看引擎级别;按原因自己续跑在 4 级(监督器),先查副作用记录,最后一步结果不明的不续,否则最后一步是送审或渲染时会付两遍钱。每一行都被接着做之后(不论是监督器还是你),这一句记为已继续、通知解决。「继续」「先放着」走 POST /v1/messages/:id/control({action: "resume" | "leave"},手机上同样可用),这一句的 control 是 {kind: "restart", cause, notes, offer, acted?},notes 是这几轮的中断行;只接着做了一部分时回答带 partial: {continued, held}。

回避:从断点接着调工具, 中断只写在你不在的 Bot↔Bot 私聊里, 会自己接着做的也发卡片让你点「继续」「不续」, 重启后不问你就把结果不明的最后那次送审或渲染再交一遍, 不核对就按进程号杀上次留下的进程, 睡眠当中断, 关机当睡眠, 窗崩当中断, 把「上次断了」永远挂在人设上, 把补全超时当中断, 把端点错误当中断, 把健康检查超时当中断

补跑(Catch-up)

守护进程回来之后(唤醒、崩溃拉起、退出后再开、开机),每个错过的日程只再执行最近该跑的那一次,不把错过的周期连开。睡眠醒来后先等一分钟再补跑:合盖时系统会自己醒几秒,那时网络常常还没回来。

回避:错过就跳过, 错过几次跑几次, 只补睡眠不补退出

终端(Terminal)

你自己在这台机器上开的一条 shell 会话,守护进程持有;和 Terminal.app 同权,cwd 默认落在工作区,但不关在工作区里,也不走批准。Bot 看不见、用不了,也不知道它存在;桌面上一个终端标签就是一条会话,手机和窄屏上所有会话收在终端那一页里当它的标签。要停必须显式「结束会话」并确认。细节见行为说明·终端。

行为细节

你自己在这台机器上开的一条 shell 会话,守护进程持有。和 Terminal.app 同权:cwd 默认落在工作区,但不关在工作区里,也不走批准——批准管的是 Bot 的危险动作,你不需要向自己申请。项目的右键菜单(目录行或项目会话)里的「在项目目录打开终端」开在那个项目目录,没设工作区也能开。用来跑你自己的活,包括启动项目这类一直开着的进程。关窗不停。它在哪个目录,以 shell 自己报告的为准(zsh 开箱即用,每个提示符前报一次):cd 之后标签名跟着变;从没报告过的,就是开的那个目录。退出或守护进程自己停掉时,里面的进程一起停,但它在哪个目录、屏幕上有什么会留下来,下次启动在那个目录重新开一个。结束会话才忘掉它。屏幕也在守护进程那里:接回时拿到的是它现在的样子,全屏程序也原样;程序问终端的问题只由它回答,窗格不答,所以几个窗格同时看一条会话也只答一次。shell 拿到的环境按 Terminal.app 那样配,但只放行一份白名单,守护进程自己的其他环境变量(包括密钥)不带进去。桌面和手机同时看一条会话时,它的行列按你最近在看、在敲的那一端来。配对过的设备同权,手机上看得见也敲得动。Bot 看不见、用不了,也不知道它存在。桌面上一个终端标签就是一条会话:新开一个终端标签就是新起一个 shell,标签里不再有一排会话可切。关掉标签只是从窗里拿走,进程照跑;没被任何标签摆出来的会话在「+」和空窗格里可以「接回」。手机和窄屏上没有窗格,远程连过来的设备不管多宽也没有(平板也一样),所有会话收在终端那一页里当它的标签。要停必须显式「结束会话」并确认。守护进程那份会话列表才是真的;同一个文件夹里开的几条按开的先后编号,桌面标签和手机上叫法一样。

回避:把它当 Bot 的壳, 给 Bot 开终端, 让终端命令走批准卡, 关窗即杀, 关标签即杀进程, 把窗格标签当会话列表, 一个终端标签里再套一排会话, 两个标签指着同一个 shell, 跨守护进程重启续命, 云电脑

远程屏幕(Remote screen)

已配对的设备看到并操作这台 Mac 的屏幕,在手机的「Mac 屏幕」一页里。画面和输入来自 macOS 自带的「屏幕共享」,Deskfolk 自己不抓屏、不注入输入。能直连就直连,连不上就经中继。默认关,只有 Mac 窗里的设置能打开;屏幕共享要这台 Mac 的账户密码,在手机上输一次,连上后记在手机上,之后自动登录。锁屏时能用,合盖、睡眠、重启后还没人登录时不能用。Windows 上画面和输入来自用户装的 VNC 服务,这一页叫「电脑屏幕」,要的是 VNC 密码。细节见行为说明·远程屏幕。

行为细节

已配对的设备在「工具 → Mac 屏幕」里看到这台 Mac 的屏幕并操作它。画面和输入都来自 macOS 自带的「屏幕共享」(本机 5900 端口):Deskfolk 不抓屏、不注入输入,所以不要录屏或辅助功能权限,每次更新也不会掉权限。守护进程只连这一个本机端口,不是能指向别处的隧道。

开关:默认关,只在 Mac 窗的 设置 → 远程控制 里开,配对的设备改不了;关掉时正在进行的连接当场断开。「屏幕共享」本身要在 系统设置 → 通用 → 共享 里打开,卡片上写着它此刻开没开,没开时点一下就到那一页。没开远程屏幕时,手机页面写明去哪里开。

登录:屏幕共享要这台 Mac 的账户名和登录密码。在手机上输一次,连上之后它存在这台手机上(和配对密钥放在同一处),下次自动登录;登录表单里取消「记住」就不存。屏幕共享拒绝它时,手机忘掉它、让你再输;配对换了或换了一台 Mac,也不会拿来用。代价写在这里:能读到这台手机配对密钥的人本来就能用你的终端,记住的密码让他还能登进 Mac 的屏幕、用 sudo,这是换来不用每次输入的。锁屏时也能看能操作,可以在锁屏上输密码解锁:锁屏界面闲置半分钟就会关掉显示器,而关着的显示器被屏幕共享拍成一片黑,所以有设备连上时 Mac 会点亮显示器,连着期间一直亮着,最后一个会话结束才放开。合盖又没接外接显示器、睡眠、重启后还没人登录时用不了:守护进程是按用户跑的。

两条路:手机先试直连——WebRTC data channel,非 trickle 的 offer / answer 走已加密的远程链路,所以直连的对端仍是这台已配对的设备,Mac 端由 real-bot-rtc 接起再连到屏幕共享。同一网络、IPv6 或 Mac 设置里填的 STUN / TURN 服务器让两边找得到彼此;默认不填,不用任何第三方服务器。手机拿不出任何候选地址时直接走中继;拿得出但 8 秒内没连上,也改走中继:守护进程自己连屏幕共享,字节装进远程链路上的 type 9 帧,两端都不超前对方确认的 48 KiB。走中继时受中继的带宽预算限制,Retina 屏全屏刷新会慢。页面顶栏标着「直连」或「经中继」。

操作:点按是左键,双指点按是右键,双指拖是滚动。底部一排补上软键盘没有的键:⌘ ⌥ ⌃ ⇧ 按一下作用于下一个键,esc、⇥、方向键,以及弹出键盘;软键盘打的字按字符发过去,退格、回车也是。双指捏合放大的是手机上的画面,Mac 那边不会缩放:捏住的那一点留在指下,最多放到 Mac 的一个像素占手机两个像素。放大后双指拖动的是画面,不再滚动 Mac;一指仍是 Mac 上的点按和拖动。顶栏「原始大小」一下放到 Mac 一个像素对手机一个像素,「适应屏幕」回到整页。

触摸板模式:顶栏「触摸板」把整块屏幕(连同画面上下的黑边)当成触摸板,画面上画出一个指针,这个选择记在这台手机上。单指滑动移动指针:走多远看手指滑多远,不看落在哪;滑得慢走得细(不到手指滑过的画面距离),滑得快能一下划过整块屏幕。点按在指针处单击,双指点按是右键,双指滑动滚动;按住不动约半秒再滑动是按着左键拖拽,指针尖上出一个圈,抬手松开。捏合照样放大手机上的画面;放大后指针总在看得见的地方,滑到边上画面跟着移,在画面本身的边上不留边距,菜单栏和屏幕四角照样够得着。顶栏「原始大小」在这个模式下以指针为中心放大。指针刚出现在屏幕中间,或者在上一次点按的地方,第一次滑动或点按时 Mac 的指针才跳过来。noVNC 没有把指针放到手指以外地方的接口,它的手势还要滑过 50 像素才算拖动,所以这个模式的触摸由页面自己读,指针事件由页面按 RFB 直接写进同一条通道(ARD 登录之后是明文 RFB)。

流畅模式:顶栏「流畅」让 Mac 在连着时临时降低分辨率,这个选择记在这台手机上。屏幕共享把帧缓冲的每个像素都发过来:16 寸 MacBook Pro 在「更多空间」下是 4112×2658,照片壁纸加一个窗口的整屏变化约 7.5 MB,手机还要用 JavaScript 解压;降到 2336×1510 约 2.8 MB,解码时间也只剩三分之一左右。挑的是主显示器上一个像素对一个点、长宽比和现在一样、按点算不小于现在的模式里最小的那个,所以窗口不会被挤小,只是 Mac 本机屏幕这期间发虚;已经是一个像素一个点的屏幕不降,页面会说明。按下开关时页面重连:会话开始时守护进程先让 real-bot-rtc display-hold 换好模式再答应手机,屏幕共享连上时报的就是新尺寸;关掉时同样先恢复再答应。几台设备共用一次降低,最后一个要流畅的会话结束后再等 3 秒(给重连留余地)才恢复;在 Mac 窗里点断开、关掉远程屏幕时立刻恢复。模式是按进程设的(kCGConfigureForAppOnly):helper 不论怎么结束(守护进程关掉它、守护进程挂了、它自己崩了),macOS 都会把原来的模式放回去,正常结束时它先恢复好再说一声。Mac 窗的远程屏幕卡片上写着现在降到了多少。

谁在看、怎么结束:有设备连着时,Mac 窗的设置卡片上写着是哪台、直连还是经中继,可以当场「断开」。撤销那台设备、关掉开关、远程链路断开、手机页面切到后台、Mac 45 秒收不到页面的保活,连接都会结束;从后台回到这一页会自动接回。

Windows:Windows 没有自带的屏幕共享,画面和输入来自用户装的 VNC 服务(推荐 TightVNC,注册成系统服务、只允许本机回环),守护进程照样只连本机 5900(ADR 0059)。手机从远程链路得知对面是 Windows:这一页叫「电脑屏幕」,登录只要 VNC 服务的密码,按键行是 Ctrl、Alt、Win、⇧,顶栏原来「流畅」的位置是 Ctrl+Alt+Del,没有流畅模式(TightVNC 发 JPEG,本来就小)。有设备连着时 real-bot-rtc stay-awake 按住显示器和睡眠,作用和 Mac 上的 caffeinate 一样。没有 VNC 服务应答时,电脑窗的卡片列出安装和配置的几步,按钮打开 TightVNC 的下载页。还没在真的 Windows 电脑上验过。

接口:手机经远程链路调 POST /remote/screen/start | offer | tunnel | status | stop,只在远程链路上存在、本机回环到不了;/remote/features 的回答里带 screen: "rfb-v1" 时手机才显示入口,host 说对面是 Mac 还是 Windows。Mac 窗经本机接口调 GET / PUT /v1/remote/screen(开关和 ICE 服务器)和 POST /v1/remote/screen/disconnect,这几条不在远程白名单里。协议细节见远控协议,取舍见 ADR 0056。Bot 用不了。

回避:远程桌面, 让 Bot 操作屏幕, 在手机上打开这个开关, 在本机窗里看本机屏幕, 自己抓屏, 把 Mac 密码存到 Mac 或中继上, 云电脑

命令流(Command stream)

Bot 执行一次 shell 时 stdout / stderr 的实时外推,让那一次调用在它跑的时候就看得见。只是可见性,不改变那次调用是什么:仍然 stdin 关闭、仍然十分钟超时、路径仍然逐次判定。不是终端,没有输入,不进主转录:进行中挂在那条气泡下面,结束折叠成一行,回看走工作目录的轮次视图。

回避:给命令流加输入, 把它当终端会话, 让它改批准粒度, 进主转录, 新增一种消息种类

窗格(Pane)

桌面窗里的一块内容区:一块窗格是一组标签页,一个标签可以是一条会话、一个终端、日程图、工作区、花费或用量。窗里可以纵横任意分割出多块窗格;窗里分成多块窗格,那仍然是同一个窗口进程,浮起来的也不是第二个系统窗口。同时只有一块是当前窗格;窄屏(≤680px)和手机上没有窗格,仍然是一次一屏、一层层盖上去。每种窗口还没开着时放在哪(新标签、替换同类、分屏、相邻窗格、浮窗……)是它的打开方式,在设置里按种类选,只记在这台机器上;已经开着的只切到前面。细节见行为说明·窗格。

行为细节

桌面窗里的一块内容区。一块窗格是一组标签页;一个标签可以是一条会话(它的产物预览、流程图都跟着它;对话上方有一条页头:和谁、他们在做什么、这里有几件等你批准或回答(点一下滚到那张卡),以及置顶、流程、设置,和标签 ⋯ 里的是同一组;它自己的设置从这个标签里的对话右侧滑出、带一层只盖这块窗格的遮罩,不另占标签),也可以是一个终端(一个标签一条 shell 会话)、日程图、工作区、花费或用量。窗里可以纵横任意分割出多块窗格,分隔条和交汇处都能拖;标签可以在自己的标签栏里拖动排序、在窗格之间拖、拖到边缘就地分割、按住 ⌥ 拖出来变成浮在上面的窗格;按住标签栏的空白处拖的是整块窗格,它的标签一起走,拖着时这些标签都留在原处变淡,跟着指针的小标签写「整个窗格 · N 个标签页」(只有一个标签时写那个标签的名字)。侧栏里的会话(列表里的每一行、置顶的头像、收起后窄栏里的头像)也能这样拖进工作台:放到窗格中间,在那块窗格里开一个新标签;放到标签栏上,插在那个位置;放到边缘,就地分出一块;按住 ⌥ 松手,浮成一块浮动窗格。这条会话已经开着时,挪过去的是它原来那个标签,不另开一个;松在工作台外面什么都不变;只点一下仍按它的打开方式打开。在窗格里任意位置右键,可以向上、下、左、右就地分出一块空窗格,也可以「浮动这个标签页」:右键的那个标签(在内容上右键就是正显示的那个)单独浮成一块浮动窗格,在原处、略小一圈,同一块窗格里的其他标签留在原处;窗格只有这一个标签时它合拢,只剩一块平铺窗格时底下留一块空窗格;浮动窗格里不止一个标签时,也能把其中一个再单独浮出来;标签的 ⋯ 里也有这一项;里面本来有右键菜单的(消息、代码编辑器)和输入框仍是它们自己的,按住 ⌥ 右键是网页视图的原生菜单。浮动窗格像当初的流程图浮窗:按住它标签栏的空白处拖着走,双击空白处停靠回布局;四条边和四个角都能拖着改大小,拉哪一边对边不动,小不过里面内容要的最小尺寸,也拖不出工作台;不就地分,右键提供停靠回布局与关闭窗格。有别的窗格时,空窗格右上角可直接关闭(只剩一块平铺窗格时不显示:关掉它也只是再留一块空的,这时标签条上什么都没有,整条收起);每块窗格的右键菜单也可关闭整块窗格及其标签,工作区和产物预览有未保存编辑时先确认。最后一块平铺窗格关闭后留下一块空窗格。窗里分成多块窗格,那仍然是同一个窗口进程,浮起来的也不是第二个系统窗口。同时只有一块是当前窗格——键盘在哪块就是哪块,在哪块里点一下或滚一下也换到哪块:Stop 打它那一轮,点通知先找已经开着那条会话的窗格。摆着不止一块时,像 Mac 上前台窗口和排在后面的窗口那样分出前后:当前那块的卡片连同窗头带一圈柔和的阴影浮在底色上,其余窗格(平铺的、浮动的都算)只在标签栏上退后——标签文字变灰变淡、正显示的标签沉进标签栏、卡片平贴在底色上,不是当前的浮动窗格阴影短一截;窗格里显示的内容一律不压暗,另一块里开着的任务地图、流程图照样看得清。只有一块时什么都不变。一样东西已经开着,再要一次只是把那一块切到前面。还没开着时放在哪,按它是哪一种窗口(会话、流程、产物、工作区、终端、日程、花费、用量;Bot ↔ Bot 私聊不开标签,在弹窗里看,见私聊),在设置 › 行为 ›「窗口打开方式」里选(「行为」页签只在桌面工作台开着时出现),只记在这台机器上,窄屏和手机上没有这一项(ADR 0074)。可选的有:

  • 新标签页。
  • 替换同类标签:当前窗格正显示同类就替换它,不是就替换离键盘最近那块正显示同类的;那块有没保存的编辑,或哪块都没有,就新开标签。「同类」是设置里的同一行。
  • 后台新标签页:放进当前窗格但不切过去。
  • 向上、下、左、右分屏:把当前窗格往那边切开;放不下或当前是浮窗,就新开标签。
  • 上、下、左、右侧窗格:那一侧有窗格就放进去;当前窗格本身就在那一侧,就放进当前窗格;都不是才往那边分出一块。
  • 浮窗:浮在布局上。这种窗口上次浮着时被你拖过或调过大小,就回到那个位置和大小(窗口变小了就收进窗口里,正好压在别的浮窗上就错开一点);没拖过的居中,已有浮窗就往右下错开一点,错开六次后回到正中。位置按窗口种类记在这台机器上,恢复默认也会忘掉。

默认是:

  • 会话:替换同类标签。在侧栏里一条条点着看不堆标签;分屏时换掉的是正读的那条,不是旁边的产物栏。终端、工作区这些不会被顶掉。
  • 流程、产物、工作区:开在右侧窗格。
  • 终端:开在下侧窗格。
  • 日程和花费:新标签页。

从某块窗格的「+」或空窗格里挑的,就放进那一块。会话设置同时只在一条会话上开着,在别处打开就把原来那处收起;日程图同时只有一块。每条会话只有一块产物预览和一个流程标签页(2026-10-12 起,ADR 0086:任务栏、流程图和详情栏里的要点都在这一个标签里,之前流程、看板、要点各是一个标签):在这条会话里、它的预览文件树里或它的流程图上再打开别的文件、别的那件事,是那一块转过去,不另开一块。流程标签从右键菜单直接打开:消息的右键菜单里有「这件事的流程」(停在这条消息的卡片上,选中这条消息所在的任务),侧栏里会话那一行的右键菜单和会话标签的 ⋯、右键里有「流程」(这个会话最近的那件事);已开着就切过去,没开就在当前窗格新开一个标签。以前存下的看板、要点标签页打开时,分别是展开的看板和按它的任务点亮的要点,再要一次就和流程标签合成一个。这些标签名是固定的,跟会话标题走:「<会话名>流程」「<会话名>的产物」,不跟打开的文件或这件事改。流程图标签换到后面再换回来,停在你离开时的地方:拖到哪、缩放多少、折起展开的轮、筛选和点亮的任务都照旧;在它的切换器里换一件事再换回来也一样;只有一条消息新要它去某张卡片时才会移开。会话窗格不再另画顶栏:头像(Bot 在干活、等你或出错时带状态点,空闲不带)和名字始终在标签上,顶栏的第二行成了标签的提示;鼠标移到标签上才出现 ⋯,里面是置顶、流程和设置,右键会话标签也在分割前面给出这几项。右键任何一个标签(会话标签的 ⋯ 里也有),可以关闭这个标签、同一块窗格里的其他标签、它右侧的标签或全部标签;全部关掉就和关掉最后一个标签一样,这块窗格跟着关掉,工作区和产物预览有未保存编辑时先确认。其他标签(终端、工作区、日程、花费、流程、产物)也始终在名字前带上各自的图标。窄屏(≤680px)和手机上没有窗格,仍然是一次一屏、一层层盖上去。

回避:多窗口, 第二个窗口进程, 把浮动窗格当系统窗口, 手机上分屏, 看得见就算已读, 不按打开方式自己改布局, 已经开着的按打开方式再开一个, 空窗格留着, 设置另开标签, 窄窗格里顶栏和标签各报一遍名字

布局(Layout)

窗格是怎么分的:纵横分割、各自的宽高、哪些浮着、哪块是当前的、终端窗格摆着哪条会话。只记在这台机器的 localStorage 里,不进守护进程,也不进 URL——URL 只说当前窗格正看着哪条会话。退出再开还是关前那样;引用的会话或终端没了就自己收拾掉;重置回默认的一块窗格前先问未保存的改动。

回避:布局进库, 布局进 URL, 跨设备同步布局, 换台机器跟着走, 重置直接丢掉未保存的改动

版本闸(Schema gate)

守护进程开库时的检查:数据库记的 schema_min_compatible 高于这个版本能懂的 SCHEMA_LEVEL,就拒绝打开,把原因写进 daemon.log 后退出,而不是接着跑、误读它不懂的新语义。engine_level 是配套的整数开关,代替一堆各自的功能开关,只支持按顺序往前调:守护进程每次启动都试着把它调到这一版自己会开的最高一级(ENGINE_LEVEL_BY_DEFAULT,2026-10-04 起是这一版的最高级,8 级质量事件与教训),同一步把库的地板抬到那一级要的高度(到叫停这一级是 1,到 8 级是 6);以后的版本带来比它更高的级别时,那一级先算试验,不论数据目录有没有旧安装版共用,都只在开发者明确放行后才开;但和它共用默认数据目录的安装版要是还不读这道闸(或读不出版本号;不在 Mac 上时无从知道装的是哪版,也算在内),源码跑的守护进程就不调,把原因写进 daemon.log,照当前级别运行——那样的旧版本会无视地板打开这个库。开发者在自己的机器上可以明确放行(engine_gate_optin:本机 API 的 POST /v1/capabilities/raise,或 apps/daemon/scripts/engine-level.ts --accept-older-app,手机上不行),接受那份旧应用在守护进程没跑时打开会无视叫停,级别当场调上去(守护进程没跑时下次启动调);收回放行不会把级别调回去;放行只到它接受时那一版的级别(越过旧安装版时也可以用 --level 只放行到某一级,比如只开 5 级、不开 6 级;没有旧安装版挡着时 --level 不起作用,库照样到默认级别),之后的版本带来新的级别要再接受一次;早先版本记下的、低于默认级别的放行,不会把没有旧安装版拦着的库压在默认级别以下。GET /v1/capabilities 把这两个数字和已开的功能报给调用方——包括手机,因为本机跑的可能是装好的旧守护进程,而信使源码树已经更新。

回避:把 schema_min_compatible 当成应用的版本号, 两个条件(闸已随发布、装好的应用版本已经够新)没都满足、开发者也没明确放行就调高 engine_level, 当成用户能看到的设置项