面向所有者的 Web 应用
这一页写给要改动面向所有者的 Web 应用的开发者,记录每个功能遵循的规则、背后的接口,以及哪些东西只能在真实浏览器里检查。怎么使用这个应用属于用户文档,归发行版负责;FreeInference 的用户文档是它的 Cloud Agents 指南。
对话视图
轮次导航条
导航条在 Chat 栏右侧的空白处。要同时满足这几个条件才会出现:对话至少有两轮可见的内容,这一栏至少 720px 宽,阅读区域至少 200px 高,并且设备报告的是精确指针;Trajectory 里没有导航条。当前轮次的标记最长、颜色最深,鼠标悬停或获得焦点的标记会稍微变宽。标记的长度只表示交互状态,从不代表消息长短、耗时、token 数或进度。对话很长时,标记之间的间距保持不变;导航条自己可以滚动,两端渐隐,滚动它不会带着对话一起滚。
预览显示的是当时发出去的问题,而不是对话标题;等这一轮结束后,再显示这次 attempt 最后一条可见的回复,这样正在生成的回答不会在鼠标底下变来变去。预览不包含推理过程、工具参数、工具输出和运行时记录;问题最多显示 50 个字符,回复最多 120 个字符。Fork 复制过来的历史,对话和预览用的都是持久化的消息,即使那些轮次没有保存事件日志也一样。
跳转时,会把这一轮的第一个问题放在阅读区域顶部往下大约 24px 的位置;没有问题的轮次,就落在它第一处可见的内容上。跳转不改变 URL、选中的 attempt 和工作区状态。之后再跳一次、手动滚动,或者离开这个对话,都会取消还没完成的跳转;找不到目标时,对话停在原处,并播报这一轮无法定位。悬停和跳转都不发请求,也不保存任何东西。打开某一轮的历史链接时,导航条只覆盖那个视图渲染出来的轮次。
导航条是一个有名字的导航区域,里面是真正的按钮,同一时间只有一个标记在 Tab 顺序里;用方向键、Home 和 End 可以到达其他标记。当前标记带有 aria-current,预览作为标记的描述;请求的跳转只播报一次,流式输出则什么都不播报。
阅读位置
对话记录有两种状态:要么跟随末尾的新输出,要么在新输出到来时把读者正在看的文字固定在原处。刚打开时是跟随状态;点了 Back to bottom、滚到实时的末尾,或者读者发出的后续消息被接受之后,也会恢复跟随。往上滚动、通过导航条跳转,或者选中一个较早的 attempt,会把位置固定住。固定期间有新内容时,Back to bottom 上会多一个圆点并更新它的名字,但不会把读者拉回去。切到 Trajectory 再切回来,位置不变;重新加载则从末尾打开。
任务和子代理
Tasks and subagents 窗口显示所选轮次和 attempt 最近记录下来的任务列表和委派出去的代理。只有其中某一项有实际记录的数据时,窗口才会出现。创建任务的结果会把分配的 id 和任务名对应起来,所以之后的状态更新改的是同一行;Status not recorded 表示所选的事件里没有这个任务的状态。
子代理卡片在 Returned to the main agent 下显示的回复,取自各个运行时记录它的地方:
Claude Code 的 Agent 或 Task,以及 OpenCode 和 Kilo 的 task,把代理的回答作为这次调用自己的结果返回;Claude 还会在旁边记下代理的状态。
Codex 把完成的代理的回答或错误,放在它的
wait调用所报告的代理状态的消息里。pi 没有子代理。
来自 MCP 服务器的工具,不管叫什么名字,都不会显示成任务列表或子代理;不过 Codex、pi、OpenCode 和 Kilo 在每次调用开始记录 MCP 服务器之前录下的会话,仍可能把这样的工具显示成子代理。回复还没到、没有记录下来或者只存了一部分时,卡片会写明;后台启动的代理只返回一条启动通知,卡片也会这样说明。Trajectory 通过运行时的归属记录,把子步骤和委派它的步骤连起来:Claude Code 记录的是委派它的那次调用,Codex 记录的是子代理自己的 thread。有这些记录之前录下的会话,步骤列表仍是平铺的。
工作区面板的键盘操作
工作区、Git、文件列表和终端的标签页,用左右方向键、Home 和 End 切换视图,焦点一直留在标签栏里。较窄的 Git 面板用一个改动文件选择器,让 diff 占满宽度;diff 和文件预览都是可以用键盘滚动的区域。在窄面板里打开文件时,焦点会移到预览上;Back 或 Escape 回到列表,并让焦点落在刚才打开的文件上。
在浏览器里检查
单元测试和 DOM 测试覆盖了导航数据、焦点和一个注入的盒模型(useConversationScroll.dom.test.tsx)。Markdown 重排、sticky 输入区、尺寸变化的时机和真实的像素位置,需要用浏览器和一个有好几轮的对话来检查:
点中间的一个标记。它的问题应该落在阅读区域顶部往下大约 24px 处,这个标记变成当前标记。
在一个很长的轮次里往下滚,直到它的问题移出屏幕。这一轮的标记应该仍是当前标记。
在输出流式到来时往上滚。文字在屏幕上的位置偏差应该只有几个像素,并且出现 Back to bottom。
点 Back to bottom,或者滚到末尾。应该恢复跟随;再往上滚,跟随应该保持暂停。
分别试试栏宽在 720px 两侧、阅读区域高度在 200px 两侧,以及触摸输入的情况。没有导航条时,对话记录仍应正常滚动。
在看历史内容时展开一个工具行、调整输入区大小、打开再关上工作区。读者眼前的文字应该停在原处。
在大约 100 轮的对话里,检查标记间距、导航条滚动,以及每个标记都能用键盘到达。
Fork 一个对话;复制过来的轮次应该有标记和预览。
切到 Trajectory 再切回来:那里没有导航条,Chat 保持原来的位置。
在两种语言、两种主题和减少动效的设置下,把键盘检查再做一遍。
设计和尺寸测量见轮次导航设计。源码:JobDetail.tsx、TurnNavigator.tsx、conversationTurnNavigation.ts 和 useConversationScroll.ts。
工具审批
Approvals 收件箱列出所有者各个对话里待审批的 MCP 调用,包括被折叠的任务列表挡住的那些。角标数的是已加载的调用,还有更多时后面带一个 +。Approve 在当前的授权和 attempt 栅栏下放行这一个被扣住的请求;Deny 拒绝它。收件箱从不改动对话的权限规则。所有者的变更流报告相关变化时,队列会刷新,轮询作为兜底。过期的调用即使 worker 还没记下过期,也会离开队列;在和过期或另一个标签页的竞争中输掉的决定,不能让它复活。
对话 Tools 面板里的 Always allow,会在一个服务端事务里同时记下这次批准和以后的权限,所以所有者提交后可以离开或刷新页面。等这个对话的连接上没有调用在等审批或派发时,规则才会保存,而且只覆盖这个对话里指定的那项能力。保存之前,服务端会再核对一遍所有者、连接和策略的版本,以及能力指纹:如果重新连接过、连接被停用、权限改过,或者能力定义变了,就丢弃这个意图,而不是恢复一个已经撤销的权限。调用历史会显示这次保存是待处理、已应用还是已丢弃。
被扣住的调用最多等十分钟。Claude Code 的单服务器 HTTP 请求超时和远程空闲超时,都设成和它整体工具超时一样的 26 分钟;如果只调大整体计时器,网络层的计时器仍可能在 Approvals 还显示待审批时就放弃这个请求。接受 SSE 的客户端会立刻收到响应,等待期间收到 keepalive 注释,最后收到这次调用唯一的 JSON-RPC 结果或错误。断开连接会取消等待并让这次审批过期;不会有任何东西替一个被放弃的请求继续派发。过期的审批从来没有派发过;会话授权发生变化,也会在派发前拦下被扣住的调用。派发之后才出错,结果可能就不确定了。
GET /v1/agent/mcp/invocations?pending_only=true 读取已认证所有者的待审批收件箱;加上 job_id 或 thread_id 可以缩小范围。读取历史必须带其中一个范围,所以两个都不带时,绝不会暴露整个账号的历史。limit 默认 50,接受 1 到 100。响应带有 invocations、has_more 和 next_before_id;has_more 为真时,把 next_before_id 作为 before_id 传入。id 按从新到旧排列,游标不含边界本身,所以新来的调用不会挤动较早的分页。读取和决定都限定在所有者范围内,并且不包含已删除的对话,但这些对话的审计历史会保留。读取从不改动调用记录,也不授予任何权限。中继这一侧见 MCP 服务器。
记忆、头像和首页建议
记忆是代理替所有者长期遵守的规则。头像和首页建议则是每次读取时根据最近的请求推算出来的;它们不保存任何东西,不交给代理,也不受记忆设置影响。为什么这样拆分,见记忆重新设计。
记忆
每条记忆是一条规则,属于两个范围之一:用户记忆对所有项目生效,项目记忆只对一个仓库生效,并且优先于用户记忆。
工具。 每个任务都可以在平台的 MCP 端点上调用
memory_save、memory_update和memory_delete;什么时候调用由代理决定,每条记忆写成一句英文。不会额外调用模型。更新或删除会把旧记忆归档,所有者自己写的也一样。注入。 任务开始时,它所在仓库的有效记忆和所有者的用户记忆,会带着工具要用的 id 加到请求前面,最多 12,000 个字符:先放仓库记忆,同一范围内最近写入或编辑的排在前面。只有加进去的记忆会更新
last_used_at;没有记忆时什么都不加。栅栏。 每次写入都会在自己的事务里检查这个任务的 attempt 是否仍是正在运行的那一个,所以已停止或被替换的任务即使还有请求在途,也改不了记忆。
范围。 代理默认存到任务所在的仓库,除非被告知这条规则适用于所有项目;没有仓库的对话存的是用户记忆。代理只能看到和修改任务所在仓库的记忆,以及所有者的用户记忆。
上限。 每个范围都有自己的开关;关掉的范围既不注入也不写入,已有的记忆保留。一个范围最多保留 30 条有效记忆,每条最多 240 个字符。满了以后,会把最久没用过的、代理保存的记忆归档腾出位置;所有者写的或编辑过的记忆从不这样归档,所以一个范围全是这类记忆时,会拒绝新的记忆。归档的记忆可以恢复,90 天后由每个控制平面进程每小时一次的清理删除。在同一范围里添加一条和现有有效记忆相同的记忆(忽略大小写和末尾标点),会直接返回已有的那条。
GET /v1/agent/memories 返回所有项目的记忆,加上 ?repo=owner/repository 则返回一个仓库的,格式为 {repo, enabled, memories, archived}。POST 带上 {content, repo} 添加一条所有者自己写的记忆(repo 为 null 表示所有项目)。PATCH /v1/agent/memories/{id} 带上 {content, repo, expected_version} 改写或移动它,DELETE 带上 {expected_version} 删除它,POST /v1/agent/memories/{id}/restore 带上 {expected_version} 恢复一条已归档的记忆。DELETE /v1/agent/memories 清空一个范围,已归档的也一起清掉。PUT /v1/agent/memories/settings 带上 {repo, enabled} 打开或关闭一个范围,GET /v1/agent/memories/projects 列出所有者工作过或存有记忆的仓库。拒绝时会带上明确的原因:memory_changed(409,版本已过时)、memory_scope_full(409)、memory_invalid(422:包含凭据、超过 240 个字符,或只适用于当前任务)和 memory_not_found(404)。任何响应都不会回显记忆的内容。相关的表和迁移见升级说明。
头像和首页建议
没有上传头像照片的账号,会显示一个自动生成的宝可梦伙伴:七种之一,表情和姿势对这个账号固定不变。账号菜单和 Profile 显示它的固定形态,由 GET /v1/agent/profile 以 companion_data_url 返回;它只根据账号推算,所以每个页面都显示它也不会多读任何请求。在首页上,栖息地的颜色和环绕的徽章,会跟随从所有者最近 50 条请求里用英文关键词识别出的工作领域。所有图形都在本地画成一个独立的 SVG;账号标识和请求内容从不嵌入其中,也不使用任何图像服务或模型。
首页还会列出最多三条固定的建议,依据是统计同样这 50 条请求里的英文关键词:要求改动却没说怎么验证成功的、报告失败却没给复现步骤的,以及要求审查却没说明范围的。只计算所有者自己写的内容:引用的文字、代码块、assistant 的回复、Fork 复制的内容、自动的目标轮次和已删除的对话都会被忽略。GET /v1/agent/insights 返回 {focus_areas, suggestions, avatar},每次读取时现算。
工作区文件
Files 面板读取保留沙箱里当前的仓库,并且是只读的。路径用正斜杠,必须留在仓库之内:绝对路径、往上跳出的路径和 .git 路径都会被拒绝,符号链接只显示、不跟随。目录列表有数量上限:超出上限的条目,以及不是 UTF-8 的文件名,会被略去,并且这个目录会写明这一点,而不是看起来像是完整的。因为上限被略去的条目,仍然可以按路径打开。回到工作区根目录会取消还没完成的按路径读取,免得迟到的响应把目录树带走。打开一个被这次运行删掉的文件,会说明它已被删除,而不是报告读取失败。
复制和下载只提供预览大小上限之内的完整内容;超出上限的文件会显示这个上限,而不是给出一部分。栅格图片有预览,HTML 和 SVG 一律作为转义后的文本显示。在没有仓库的对话里,每一轮的结果上都有 Download workspace,它把工作区打成一个 .tar.gz,不含 .git 目录和元数据文件;其他隐藏文件都包含在内,.gitignore 也不做过滤。超过服务器下载上限的工作区会被拒绝,而不是只存一部分。见临时工作区。
其他小功能
通知。 在 My Settings → Notifications 里可以打开浏览器通知,分别针对代理提问和轮次完成,两者各自开关,按账号保存。应用开着的时候,通知覆盖所有对话,点击通知会打开对应的那一轮;浏览器关着时什么都收不到。
插件。 在对话发出第一个任务之前,通过 + → Plugins 选择。Claude Code 和 Codex 接受
name@marketplace,来源是一个 GitHub 仓库或公开的 HTTPS marketplace;pi、OpenCode 和 Kilo 接受 npm 包名,可以固定到某个确切版本,比如@example/plugin@1.2.3。每个对话最多八个。沙箱会在第一个提示词之前装好它们,安装失败会让启动停下,作为任务错误报出来。选择会随对话保存,并由 Fork 继承。改写提示词。 见改写草稿提示词。
Review。 在工作区的一个标签页里启动一个独立的代码审查子代理,审查范围是从对话最初的基线以来的所有改动,包括工作区里当前的改动,并且要求它不修改文件。它需要有可用的原生 Fork,所以不能从已归档的对话或已经丢失上下文的对话启动。再选一次会重新打开这个对话已有的审查。
Watch PR and merge。 在同一个对话里启动一条后续消息,让代理盯着检查和审查,修复失败和反馈,等当前的 head 满足必需的检查和审查后合并;哪些东西不经人就能合并,由仓库规则决定。它会保留输入区里还没发出的文字和附件,并且只有在共用这个工作区的所有对话都没有正在进行的工作或等待中的消息时才能启动。代理需要能读取检查和审查、把草稿标为就绪并合并的源代码管理工具:也就是 GitHub 预设,搭配一个对 Contents 和 Pull requests 有读写权限的令牌。连接到
/mcp/readonly的连接必须用这个预设重新连接。盯守会随这一轮的超时结束;如果这一轮做了修复,它会结束,让平台去发布这些修复。