将客户端接入 app-server
需要管理会话、接收回合流或控制执行时,使用 app-server。脚本如果只需提交任务并取得
结果,wuu exec 已经封装了这套生命周期。
wuu app-server --workdir /path/to/project让客户端持续连接进程的 stdin 和 stdout。每条协议消息是一行 JSON 对象;stdout 是
协议流,进程诊断应单独处理。未提供显式 --config 文件时,服务使用本地配置。
排查插件故障时,--safe-mode 可在不激活插件的情况下启动。
先发送 initialize:
{"id":"1","method":"initialize","params":{"protocol_version":"wuu-app-server/v0.1","client":{"name":"example-client"}}}响应使用相同的 id,并包含 result 或 error。通知只有 method 和 params,没有
id。检查初始化结果的 status 和 issues:协议响应成功,仍可能返回 needs_setup。
初始化同时返回 catalog_providers,即本地可连接服务目录,客户端可直接展示服务选项。
config/model-catalog/refresh 成功后在同名字段中返回更新后的目录;刷新期间应保留现有
选项。旧版宿主可能省略该字段,此时可调用 config/model-catalog/providers 获取目录。
创建会话,再使用返回的 result.thread.id:
{"id":"2","method":"thread/start","params":{}}{"id":"3","method":"run/start","params":{"thread_id":"THREAD_ID","prompt":"总结这个仓库","request":{"mode":"start"}}}这是有先后关系的两个请求,不能原样作为批次提交。收到第一个响应后,将 THREAD_ID
替换为实际 ID。持续读取通知,直到 run/updated 为返回的运行 ID 报告终态。
run/start 响应表示执行已被接纳,不表示任务完成。
交互式客户端也可以使用 turn/start,接收回合和条目更新。自动化执行可能包含多个续接或
Schema 修正回合,因此单个 turn/completed 不代表整次运行结束。用 run/interrupt
停止运行;关闭由客户端启动的服务前,先请求 shutdown。
归档正在运行的会话
Section titled “归档正在运行的会话”thread/archive 接收 thread_id 和 archived。归档运行中的会话需要用户明确确认,
然后发送 archived: true, force: true。请求会中断它捕获的执行、保留排队输入,并保持
待处理状态,直到部分历史、终态和执行器清理完成。清理超过两秒不会要求用户再次确认。
stdio 中同一会话的后续请求仍按顺序等待;其他会话和服务器关闭请求不受阻塞。
桌面归档请求没有响应超时。切换页面只使旧界面不再处理结果,不撤销已确认的请求。 服务器关闭或嵌入方取消请求上下文,会在归档元数据提交前停止归档;执行结束后会重新 检查授权。如果连接或提交确认丢失,结果可能未知,应先读取持久会话状态再决定是否重试。 错误若明确说明归档元数据已提交、但刷新视图不可用,不代表归档写入失败。已经提交的 变更不会因后续取消而回滚。
读取会话目录
Section titled “读取会话目录”通过 stdio 调用 thread/list、thread/listAll 和 thread/listArchived 时,目录读取在
后台执行,避免大型目录阻塞会话读取和停止请求。响应可能乱序返回,请通过请求 ID 关联。
每次目录读取都保留请求受理时选中的状态提供方。目录是可刷新的视图,不保证它完成后才
执行后续会话修改。
分页读取会话历史
Section titled “分页读取会话历史”需要紧凑会话视图时,在 thread/resume 中传入 conversation_history: true。
主机会先将已结束的工具活动和过程说明折叠为 process_summary 条目,再计算历史页大小。
回复、错误和已发布附件仍然可见;正在执行的轮次保留实时条目。
将返回的 history_cursor 原样传给 thread/history/read 读取更早的会话页。
会话页当前最多包含 100 个轮次、1 MiB 的序列化条目数据;分页可以拆分轮次,
但不会拆分处理过程摘要。iOS 在打开会话后自动逐页加载更早的历史,直到读取完成。
每次只请求一页;关闭视图、切换会话或断线时停止。浏览旧消息时保持阅读位置,
加载失败可在时间线顶部重试。工具详情和超长内容的完整版仍按需读取。
用户打开摘要时,调用 thread/activity/read,传入 thread_id、turn_id、摘要的
item_id,并将 activity.last_item_id 作为 last_item_id。响应包含 turn 和可选的
next_cursor;将后者作为 cursor 读取下一页详情。详情页应与会话状态分开保存。
单个超长条目通过 remote_content_ref 和 thread/content/read 获取完整内容。
若历史编辑移除了分页边界,应重新打开会话,不再使用过期游标。
未传入 conversation_history 的客户端继续使用原来的历史格式。
旧版主机会忽略此选项,返回常规历史页。
不增加用户消息的显式继续
Section titled “不增加用户消息的显式继续”空闲的 Wuu 引擎会话可以用服务器返回的最新轮次 ID 调用:
{"id":"continue-1","method":"turn/start","params":{"thread_id":"thread-id","resume_after_turn_id":"thread-id-turn-0001"}}省略 prompt 和附件。核心先获得线程执行租约、刷新持久历史,再比较源轮次。源 ID 过期、正在执行、只读、没有历史、非 Wuu 引擎或至多一次工具的结果未知时,会拒绝接纳。成功返回新的空轮次,不插入用户消息;即使新轮次已经结束或服务器重启,重复源 ID 仍会被拒绝。响应结果不明确时先重新读取会话,不要自动换成新的最新轮次 ID 重发。
持久化的 meta/turn_continue 只划分显示轮次,不进入模型输入。采样前从账本恢复已完成工具结果;带 incomplete_stream_fragment cause 的显示片段被排除。接纳后进程崩溃会留下独立的中断轮次,用户可以再次明确继续。
Responses 和 Chat Completions 使用经过规范化的客户端历史,保留配对工具结果及兼容推理;失败响应身份不会复用,Responses 原生状态仍检查 provider、model 和 scope。Anthropic Messages 的历史若以 assistant 结束,会仅在本次请求加入 user 兼容引导,避免使用不受支持的 assistant prefill;工具结果结尾本来就映射为 user。兼容引导不会成为持久用户消息。参考 Anthropic prefill 限制和 Responses 会话状态。
排队输入与崩溃恢复
Section titled “排队输入与崩溃恢复”持久对话中的 turn/queue、turn/steer 接收输入,以及编辑和取消操作,都会先提交本地存储,
再返回成功响应。重试响应不明确的请求时应复用稳定的 client_id:重复排队返回原内容,
已消费的 ID 在不同输入方法间也会被拒绝。取消会阻止同一发送模式的重试;明确切换排队与即时引导模式,
会取代前一种模式的取消记录。修改内容使用 turn/update-queued;重复 turn/queue 不会修改或重新执行输入。
hold: true 只保留输入,不派发。
核心重启后,未消费输入通过 held_user_messages 返回,不会恢复为自动执行的队列。
仍由当前进程管理的输入只出现在 pending_user_messages 中。用户可以用 turn/steer 明确发送待处理项。
输入进入历史和清理恢复记录在同一事务中完成;即时引导在下一次模型请求前提交尚未归档的完整历史后缀,
避免把已经派发的输入当作未执行消息重放。并发编辑和取消会阻止过时输入进入历史。
内置引擎在模型回复过程中收到 turn/steer 时,会在当前可见文字处结束助手条目,
将该片段作为未完结的已引导片段保存,并在它后面接收用户消息,然后继续同一轮的下一次模型请求。
工具执行期间收到的引导会在下一次模型步骤前接收,不取消已经启动的工具。
持久 Codex 对话支持向当前轮次补充明确的人类输入。Wuu 用 expected_turn_id
绑定当前轮次,适配器再用原生轮次 ID 约束发送目标;附件和稳定的 client_id
沿用普通输入入口,不改变工具或权限。RPC 成功只确认本地接收,原生确认返回的
轮次 ID 匹配且用户历史提交成功后,才显示已消费的用户消息。同一轮次可以接收多次补充。
引导结果的可选 delivery 字段表示计划发送路径 active_turn 或 queued,不代表原生已消费。
不支持当前轮次引导的
引擎和临时原生对话继续使用下一轮排队;原生返回不支持该方法时也退回普通队列。
原生明确拒绝的输入则恢复为待处理,不会静默转入后续轮次。
如果发送后的确认丢失、发送后被取消,或确认记录无法提交,待处理项会包含
delivery_unconfirmed: true。即使重启,也不能编辑、撤回、重新排队或重发这个 ID。
桌面端显示原因,并保留原始内容供查看和复制。
应先检查原生对话,再决定是否提交替代输入。这个边界防止自动重复执行,
不承诺外部工具效果恰好发生一次。原生轮次结束时尚未接收的补充会保留为待处理。
执行时重新读取当前权限选择,不会恢复旧权限。这些保证针对本地进程崩溃,不会自动重试效果不明的工具调用。 临时会话仍只保存在内存中,也不提供持久待处理输入。
托管浏览器请求
Section titled “托管浏览器请求”内嵌浏览器客户端的基础能力包含六个反向请求方法:browser/cdp、browser/screenshot、
browser/open_tab、browser/close_tab、browser/set_visibility 和 browser/list_tabs。
这些请求由核心发给客户端;客户端应持续读取协议流,并以原请求 ID 返回结果或错误。
每个浏览器请求都带核心指定的 thread_id 和 workdir。标签页创建、列举、输入、截图和
清理按对话隔离;页面打开的标签页继承来源页的归属。现有标签页属于其他对话时,应拒绝
请求,不得导航或接管该标签页。
browser/list_tabs 还可返回 workspace_tabs:用户已打开的网站或插件页面候选项,包含 tab_id、url、title、workspace_id、source 和 active。候选项与当前对话拥有的 tabs 分开;列举只提供元数据,不读取正文或取得控制权。工具显式 attach 时,在原有活动和执行租约内发送 browser/open_tab,令 tab_id 与 workspace_tab_id 相同且不传 initial_url。宿主借用原生页面,拒绝已占用、已关闭或其他窗口的页面;不得按 URL 重建 workspace: 标签。browser/close_tab(工具 detach)、finalize、轮次结束、接管、停止和所属宿主销毁都释放绑定,不关闭用户页面。被释放的绑定 ID 出现在 closed 中,原生页面仍存活。借用期间沿用 Agent 权限限制,不扩大截图或媒体权限。
request_id 标识进行中的操作。核心停止等待时,browser/request_cancelled 通知携带
该 ID 及相同的 thread_id、workdir。只取消匹配的操作,在异步工作后和输入派发前
再次检查。用户接管或停止活动会撤销该对话所有标签页的输入权限;之后归还控制权可
授权新操作,但不能恢复已经撤销的请求。
桌面客户端还协商 browser/finalize 和 browser/turn_ended,支持按执行实例清理。每个工作区的首次浏览器请求前,核心发送 browser/turn_started,携带 workdir、thread_id、turn_id 和不可变的 execution_id;同一次执行中的所有浏览器请求都带这组标识。逻辑轮次恢复时可以复用 turn_id,但必须分配新的执行 ID;旧执行的请求和结束通知不得影响新执行。
browser/finalize 原子应用 keep: [{tab_id, status}],其中状态为 persistent、handoff 或 deliverable,并关闭未保留的临时页面,但不结束执行。手动打开或接管的页面保持持久。保留弹窗时,先保护它仍存活的同任务来源页,再关闭临时页面。宿主重启后,持久记录中的标签 ID 可以尚未创建;若对应的存活标签属于其他任务,则返回错误。
轮次完成或中断后,browser/turn_ended 带执行标识及保留记录。客户端先使未完成操作失效,再关闭临时页,并释放保留页面的调试器和输入、指针状态,不替换页面内容或改变当前预览布局。清理不得删除历史消息引用的截图文件。持久保留记录读取失败时,preserve_all: true 要求保留该任务所有页面,避免丢失上下文。两个方法都返回 closed、kept,以及可选的 tabs(含 tab_id、url、title、status);过期结束请求返回 stale: true,不修改标签。未协商新增方法的宿主继续使用基础请求契约。
thread/resume 接受 session_id,省略时选择工作区最近的可见会话。thread/fork 接受
thread_id,创建独立对话。thread/start 的 ephemeral: true 创建内存会话,服务退出后
无法恢复。
会话快照中的可选字段 title_revision 是随标题修改一起保存、单调递增的版本号,省略时
视为零。合并列表、恢复响应或 thread/updated 通知时,保留版本较大的标题;
updated_at 表示会话活动时间,不能用来判断标题的新旧。自动生成只填补空标题,
可在后续回合成功后重试,不会覆盖已保存的手动标题。
每个对话保留自己的模型和权限选择。应在对话空闲时,用带 thread_id 的
config/model/update 修改;这不会改变工作区默认值。对话忙时返回 thread_busy,客户端可
等待后重试。不带 thread_id 的请求修改未来
对话的默认设置;加上 keep_selection: true 则只保存服务的连接,不把它设为默认。不要通过 turn/start 临时覆盖单个回合的权限模式。
删除已归档对话
Section titled “删除已归档对话”thread/listArchived 返回当前会话存储中的全部已归档对话,包括其他工作区的对话。
删除这份列表快照时,为每个 ID 单独调用 thread/delete,并设置
only_if_archived: true。每次请求独立执行永久删除,客户端应报告各项失败。
如果对话在删除事务开始前已恢复,服务会拒绝删除,保留聊天记录和侧聊内容。
正在运行的对话、活跃子 Agent 或正在运行的侧聊也会阻止删除。
省略 only_if_archived 时,仍可删除普通空闲对话。
输入内容分段
Section titled “输入内容分段”turn/start、turn/queue、turn/update-queued 和 turn/steer 接受可选的
content_parts,用于在 prompt 之外保存有序的展示元数据。二进制附件仍使用
images / files。
type | 字段 |
|---|---|
text | text: string |
pasted_text | text: string,可选 title: string |
file_selection | text: string、source: FileSelectionSource、intent: "comment" | "edit" | "quote"、id: string,可选 comment: string |
response_selection | text: string、selection: ResponseSelection,引用捕获时已显示的助手回复,支持仍在输出的回复 |
FileSelectionSource 包含字符串字段 workspace、path、quote、revision,
以及整数字段 start_line、start_column、end_line、end_column。行号和
UTF-16 列号均从 1 开始,结束位置不包含在选区内。revision 标识捕获时的文件
内容,不一定是 Git 版本号。服务端检查必需元数据、意图值、正数坐标及范围顺序,
不读取文件或核实其版本。
PDF 选区增加可选的 source.pdf 元数据,其中 start_page 和 end_page 从 1 开始,
包含首尾页。四个行列坐标均为 0,不把页码写成文本行号。已交付 PDF 还会在 pdf
中保存 artifact_uri、artifact_sha256、artifact_thread_id,并把 revision 设置为
sha256:<artifact_sha256>。Wuu 检查页码顺序和交付标识的一致性。这些字段标识
引用时的快照,不授予文件访问权限,也不证明摘录确实存在于文件中。桌面的“添加到对话”
和“评论”只把摘录加入当前草稿,不会发送。工作区 PDF 的版本标识对应捕获摘录时观察到的文件。
文件选区的 text 必须包含模型可见的完整序列化文本块,包括来源上下文、意图和
可选评论。客户端按顺序拼接各段文本形成 prompt。只有拼接结果与提交的提示词
在去除首尾空白后相同,服务端才保留有效元数据;服务端不重建或解析客户端的
序列化格式。未知或无效分段会被丢弃,文本不匹配时丢弃展示元数据,但不修改
提示词。Provider 和引擎使用规范消息文本;Codex 接收普通文本输入,无需特殊的
text_elements。
已接受的元数据会随历史和暂存队列回放保留。客户端必须容忍未知字段和分段类型;
无法展示元数据时,回退到完整的消息 text 或队列 prompt。模型所需的上下文
不能只保存在元数据中。
thread/start 未指定 engine 时使用 engines.default_engine,空默认值选择
wuu。所选引擎被禁用、移除或不可用时,在持久化会话之前拒绝创建,不会改用
另一实现。通过 engine/update 禁用引擎会保留配置中的默认选择;重新启用它,
或显式将 default_engine 设为可用引擎(例如 wuu)后,才能继续创建对话。
已有对话保留其引擎绑定与历史,修改默认值不会迁移这些对话。
engine/update 的响应形状与 engine/list 相同,但不会启动原生模型发现;
每个条目的 models_status 均为 deferred。变更生效后如需模型目录,应单独
调用 engine/list。
此行为替代了原先自动回退 Wuu 的逻辑。客户端应处理默认引擎不可用的情况, 提供显式选择,而不是自动改写默认值。
刷新外部引擎
Section titled “刷新外部引擎”engine/list 接受 { "cwd": "/absolute/session/directory", "refresh_models": true }。
也接受 { "include_models": false },此时只返回引擎描述与设置,不启动原生模型发现;
每个条目的 models_status 为 deferred,未返回目录不表示目录为空或不受支持。
客户端可以用这份可用性清单选择引擎并创建会话,包括使用原生默认配置或已知选择,
随后再单独请求完整目录。省略 include_models 时仍执行完整发现。
cwd 默认使用运行时根目录;会话位于其他目录或 worktree 时应传入实际路径。
显式刷新重新扫描桌面安装目录和登录 shell 路径,将保存的启用/禁用选择重新应用到
运行时,并绕过原生模型发现缓存。应用启动后新安装的引擎无需重启即可使用,
正在执行的轮次保留原有引擎租约。enabled 表示运行时可用性,binary_ok 表示
检测到了可执行文件;保存的 settings.<id>.enabled 仅表示用户选择。
查询订阅状态
Section titled “查询订阅状态”engine/list 可选参数 { "include_quota": true } 使用各模型服务和支持的外部 Agent 自己的凭据查询上游额度,返回已配置服务 subscription_providers。服务或引擎可附带 quota:
status:available、stale、unavailable、sign_in或unsupported。kind:订阅、套餐或余额;plan为可选的上游套餐信息。account:不透明、按来源隔离的id,可选账号label和凭据source;不返回 token 或密钥。checked_at为最近尝试时间,observed_at为最后成功读取时间,expires_at为新鲜度期限,目前为成功读取后五分钟。windows提供id,以及可选的label、used_percent、window_minutes、resets_at、model、scope、display和上游明确报告的unlimited。balances保留币种及十进制字符串amount,不合并不同币种或账号;可选reset_credits表示上游重置次数。error_code为安全的失败类别,不包含上游原始响应。
只需要当前账号额度时,可同时设置 include_activity: false,跳过本地历史统计。省略该参数时仍按原有行为返回历史统计。
已接入原生 ChatGPT/Codex、Grok Build 订阅、Anthropic OAuth、Kimi Code 和智谱/Z.ai 套餐周期,以及 DeepSeek/OpenRouter 预付余额。外部 Codex 通过 CLI 账号请求读取;外部 Claude Code 和 Grok 使用自己的本机登录。macOS 默认 Claude 登录可读取钥匙串,但自定义凭据目录不会借用默认账号。浏览器 SuperGrok、任意兼容端点和没有适配器的 Agent 暂不支持。
缺失百分比表示未知,不是零或无限;上游可用 100% 或更大数值表示耗尽。ACP 上下文占用和本地 token 不能推导账号额度,过期快照也不能在重置时间自行补满。暂时读取失败且仍能确认账号时,保留该凭据来源的最后成功快照为 stale,不改成功读取时间或到期时间;认证被拒绝时丢弃该账号的缓存额度。快照跨服务重启保留,更换凭据不会继承另一个账号的数据。所有来源的采集共用八秒期限。额度查询不发起推理,不更改模型选择、工作区默认值或路由策略。
只有上述按需订阅响应读取历史统计。initialize、配置响应、普通 engine/list 和 engine/update 不附带 latest_request 与 local_usage;需要统计的客户端应独立加载订阅快照,不阻塞导航。所有请求来源共用一次历史扫描;扫描失败时保留服务清单,统计字段缺失而非零值。
该响应中引擎及订阅来源的 local_usage 汇总本地保留历史中已上报的输入、输出、缓存 token 和 reported_turns,不含未上报或 Wuu 外用量,也不是账单。可选 latest_request 提供最近请求的 status、error、at、model、usage_reported,有上报时附带 token 计数。新旧顺序按请求时间判断,来源按持久记录归属;对话编辑和供应商切换不改变历史归属,旧请求用量不得填充到新请求。
查询用量概览
Section titled “查询用量概览”usage/overview 汇总本地保留历史中已记录的 token 用量:total_sessions(至少有一条用量记录的会话数)、metrics(与 settings/usage 相同的汇总对象,含 active_days)和 days(每个活跃日一条,按日期升序)。它只读取用量记录,不读取对话内容。可选参数 { "timezone": "America/Los_Angeles" } 按 IANA 时区划分日期;省略时使用 UTC,未知时区返回请求错误。没有记录时返回零值和空的 days。与 local_usage 一样,这些值不含未上报或 Wuu 外的用量,也不是账单。桌面「用量」页使用的完整快照 settings/usage 接受同样的可选 timezone;桌面端会带上自己的时区,让每天的柱子落在日历对应的那一天。其中的 engine_breakdowns 把同一份 Wuu 内用量按会话绑定的 Agent 引擎归桶,并为每个引擎增加 outside 一侧——从引擎自己的本地账本(~/.claude 转录、~/.codex rollout、OpenCode 数据库、Devin sessions.db)读出的 token 合计,减去 Wuu 已认领的原生会话,两列不会重复计数。从不向 Wuu 用量记录上报的引擎——ACP 家族,如 Devin——则把其已认领会话的账本行计入 in_wuu 一侧,因为 Wuu 自己的记录无法覆盖它们。没有可读本地账本或扫描失败的引擎不返回 outside。同样的账本合计也会落到每日序列——已认领行落在当天自身的 token 字段,外部行落在 days[].outside——趋势图和热力图因此能给页面统计到的每一天着色;每天的轮次和 agents 保持不变。
wuu debug app-server initialize --workdir /path/to/projectwuu debug app-server send --workdir /path/to/project config/read '{}'每条调试命令都会启动服务、完成请求并关闭服务。流式执行需要持续连接的客户端,不应靠 串联独立探查命令实现。
stdio 协议是受信任的本地控制接口,不是带认证的网络服务。远程或托管部署必须在外围提供 传输安全和隔离。协议参考(英文)说明消息 格式、能力协商、选择规则和云端进程身份。
回答原生引擎问题
Section titled “回答原生引擎问题”Codex 提问复用现有的 user-question/requested、user-question/resolved
通知及 user-question/list、user-question/respond、user-question/cancel、
user-question/hold 方法。请求携带所属 Wuu thread_id 和 turn_id,原生条目标识
位于 call_id。问题 ID 仅在当前宿主请求内有效,回答时应原样返回。
reply_to_tool: true 表示答案直接返回正在等待的引擎调用,不应再发送消息或追加 steer。
mode: "ask" 使用常规问答表单;mode: "offer" 使用可跳过卡片,并沿用 20 秒过期时间,
user-question/hold 可暂停倒计时。取消、跳过或过期均返回无答案,不会自动选择选项。
回合完成、中断或原生进程断线会撤销问题,迟到的回复会被拒绝。原生请求省略阻塞标记时
按阻塞问题处理。秘密输入请求明确不受支持,不会显示在明文输入框中。
宿主问答桥接可用时,Wuu 为普通原生回合启用提问。原生配置显式设置
features.default_mode_request_user_input = false 时会保留禁用,Wuu 也不会覆盖
原生工具级别的禁用设置。功能配置在创建线程或冷恢复时生效;已加载的原生线程保留
首次加载的功能配置。更改原生提问开关后,需重新连接原生引擎或开启新对话才能生效。