Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: Show live agent activity and hand-offs in the desktop office
- Activity snapshots infer `responding` from token events (the default
  coding mode never emits it) and keep a bounded streaming tail plus each
  running tool's one-line call preview.
- The office controller overlays WebSocket token/reasoning events between
  snapshots (sequence-gated, throttled) without extra HTTP requests.
- Busy agents keep a status bubble (icon, verb, latest words or call
  preview) with priority-based collision avoidance; typing, reading,
  thinking and waiting have distinct motions.
- Members walk in from the door, collect a task envelope at their
  dispatcher's desk, and walk back to hand in a report before leaving;
  mesh messages fly as labelled envelopes. Reduced motion keeps the
  envelopes without walking.
- The page reports bubble rectangles so the Windows window region no
  longer clips bubbles above the walls.
- Add a macOS host: non-activating NSPanel with WKWebView, a
  chrome.webview shim, hit-test click-through, drag, resize, zoom and
  context menu. Not yet compiled on macOS.
  • Loading branch information
tmoonlight committed Oct 6, 2026
commit 93c934a4a791e71bcf75d3f61aa69e00c45c0770
8 changes: 6 additions & 2 deletions assets/desktop_pet/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ components:

### 场景状态

角色动作、气泡和工具图标共同呈现状态。等待授权、等待回答、失败、停止均有明确文案;当前工具图标只在执行工具时显示。上下文文件堆属于相应工位,依据当前上下文使用率呈现。
角色动作、气泡和工具图标共同呈现状态。忙碌期间气泡常驻,一行「图标 动作 · 细节」:动作用短的现在进行时动词,细节用系统界面字体(路径、命令、英文推理在像素字体里难以辨认),放不下时从开头裁掉,保留最新的字和文件名;正在输出的气泡带闪烁光标。等待授权、等待回答、失败、停止均有明确文案;工具图标只在执行或准备工具时显示。上下文文件堆属于相应工位,依据当前上下文使用率呈现。

### 交接

成员加入、完成和 agent 间消息用走路与信封表达:信封带两格残影的抛物线和黄色标签(任务 / 回报 / 消息 / 新任务),落地时收件人头顶冒「!」。走路沿 2 单位网格寻路、不穿家具。

### 溢出成员

Expand All @@ -114,7 +118,7 @@ components:

- **Do** 保留原作家具、透明房间、等距像素构形与稳定会话布局。
- **Do** 让控制条、提示和成员列表保持可读,并把状态变化绑定到真实快照。
- **Do** 为会话按钮和跟随开关保留焦点及可访问状态,减少动态效果偏好下抑制消息传递动效。
- **Do** 为会话按钮和跟随开关保留焦点及可访问状态;减少动态效果偏好下不走路,但保留交接信封(它承载信息)。

### Don't:

Expand Down
421 changes: 356 additions & 65 deletions assets/desktop_pet/agent_office_pet.html

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions assets/desktop_pet/surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@
| 真实状态 | 场景表达 |
| --- | --- |
| 尚未开始执行 | 待命;没有演示角色补位 |
| 推理、调用工具 | 思考或工作;工具执行时显示与聊天共源的图标 |
| 推理 | 思考点轮流跳;气泡显示「思考中」或具体进度标题,以及推理最后几个词 |
| 输出正文 | 打字动画;气泡显示「撰写回复」与最新几个字和光标,两次快照之间由 WebSocket token 实时补字 |
| 调用工具 | 读类工具看屏幕点头、写类工具敲键盘;气泡显示与聊天共源的图标、动作和调用预览 |
| 等成员回报 | 沙漏;气泡显示「等成员回报」 |
| 等待授权、等待回答、重试 | 保留各自的明确状态文案 |
| 主 agent 成功完成 | 留在主工位,显示 `zzz`;下一轮开始后恢复执行状态 |
| 子 agent 成功完成 | 离开工位;后续任务出现时再按真实成员恢复 |
| 子 agent 加入 | 从门口走进,到派活的人桌前领「任务」信封,再回工位 |
| 子 agent 成功完成 | 走到派活的人桌前递「回报」信封后出门;后续任务出现时再按真实成员恢复 |
| 执行失败或停止 | 本轮保留失败或停止状态,不能表现为成功休息 |
| 整理上下文 | 显示整理状态及机器人提示;文件堆随后跟随新上下文比例 |

文件堆反映当前上下文使用量相对窗口容量的比例,不使用会话累计消耗量。容量未知时不绘制文件堆,不能把未知当作零用量。消息信封由真实成员变化或消息记录触发;减少动态效果偏好下抑制该动效。
文件堆反映当前上下文使用量相对窗口容量的比例,不使用会话累计消耗量。容量未知时不绘制文件堆,不能把未知当作零用量。消息信封由真实成员变化或消息记录触发;减少动态效果偏好下不走路,信封仍然飞过(它表达谁和谁在交接,不是装饰)。

状态气泡常驻于忙碌的 agent 头顶,按「求助 > 主 agent > 正在输出 > 刚变化」的优先级摆放:撞上就左右错开或抬高(引线连回头顶),再放不下只显示动作。有人忙时机器人不闲聊。

## 桌面交互

Expand Down
7 changes: 7 additions & 0 deletions cmake/acecode_desktop.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ FetchContent_MakeAvailable(webview)
# acecode_testable 把 agent/TUI/web assets 全部拖入桌面壳。
if(APPLE)
set(ACECODE_AGENT_BROWSER_HOST_SOURCE ${ACECODE_AGENT_BROWSER_HOST_MAC_SOURCE})
# The macOS office host is the only ARC translation unit in this target.
set(ACECODE_DESKTOP_PET_SOURCE ${ACECODE_DESKTOP_PET_MAC_SOURCE})
set_source_files_properties(${ACECODE_DESKTOP_PET_MAC_SOURCE}
PROPERTIES COMPILE_OPTIONS "-fobjc-arc")
endif()

set(ACECODE_DESKTOP_SOURCES
Expand Down Expand Up @@ -132,6 +136,9 @@ if(WIN32)
target_link_libraries(acecode-desktop PRIVATE
ole32 shell32 user32 gdi32 dwmapi
)
endif()

if(WIN32 OR APPLE)
set(ACECODE_DESKTOP_PET_PAGE "${CMAKE_SOURCE_DIR}/assets/desktop_pet/agent_office_pet.html")
set(ACECODE_DESKTOP_PET_PAGE_CPP "${CMAKE_BINARY_DIR}/generated/desktop_pet_page_embedded.cpp")
add_custom_command(
Expand Down
3 changes: 2 additions & 1 deletion cmake/acecode_layer_libraries.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ set(ACECODE_DESKTOP_BINARY_ONLY_SOURCES
${ACECODE_DEEPIN_WINDOW_EFFECTS_SOURCE} ${ACECODE_DESKTOP_LINUX_SOURCE}
${ACECODE_AGENT_BROWSER_HOST_SOURCE} ${ACECODE_AGENT_BROWSER_HOST_MAC_SOURCE}
${ACECODE_DESKTOP_MAIN_SOURCE} ${ACECODE_DESKTOP_SPLASH_SOURCE}
${ACECODE_DESKTOP_WEB_HOST_SOURCE} ${ACECODE_DESKTOP_PET_SOURCE})
${ACECODE_DESKTOP_WEB_HOST_SOURCE} ${ACECODE_DESKTOP_PET_SOURCE}
${ACECODE_DESKTOP_PET_MAC_SOURCE})
acecode_collect_group_sources(_desktop apps/desktop)
list(REMOVE_ITEM _desktop ${ACECODE_DESKTOP_BINARY_ONLY_SOURCES})
acecode_add_layer_library(acecode_desktop_support apps ${_desktop})
Expand Down
2 changes: 2 additions & 0 deletions cmake/acecode_source_paths.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(ACECODE_DESKTOP_MAIN_SOURCE "${CMAKE_SOURCE_DIR}/src/apps/desktop/main.cpp")
set(ACECODE_DESKTOP_SPLASH_SOURCE "${CMAKE_SOURCE_DIR}/src/apps/desktop/splash_screen.cpp")
set(ACECODE_DESKTOP_WEB_HOST_SOURCE "${CMAKE_SOURCE_DIR}/src/apps/desktop/web_host.cpp")
set(ACECODE_DESKTOP_PET_SOURCE "${CMAKE_SOURCE_DIR}/src/apps/desktop/desktop_pet.cpp")
set(ACECODE_DESKTOP_PET_MAC_SOURCE "${CMAKE_SOURCE_DIR}/src/apps/desktop/desktop_pet_mac.mm")
set(ACECODE_DESKTOP_LINUX_SOURCE "${CMAKE_SOURCE_DIR}/src/apps/desktop/linux_desktop.cpp")
set(ACECODE_AGENT_BROWSER_HOST_SOURCE "${CMAKE_SOURCE_DIR}/src/apps/desktop/agent_browser_host.cpp")
set(ACECODE_AGENT_BROWSER_HOST_MAC_SOURCE "${CMAKE_SOURCE_DIR}/src/apps/desktop/agent_browser_host_mac.mm")
Expand Down Expand Up @@ -87,6 +88,7 @@ acecode_require_sources("shared target source paths"
${ACECODE_DESKTOP_SPLASH_SOURCE}
${ACECODE_DESKTOP_WEB_HOST_SOURCE}
${ACECODE_DESKTOP_PET_SOURCE}
${ACECODE_DESKTOP_PET_MAC_SOURCE}
${ACECODE_DESKTOP_LINUX_SOURCE}
${ACECODE_AGENT_BROWSER_HOST_SOURCE}
${ACECODE_AGENT_BROWSER_HOST_MAC_SOURCE}
Expand Down
6 changes: 4 additions & 2 deletions docs/daemon-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,9 +566,11 @@ disabled, the write is silently ignored.

`offices` 最多五个:跨工作区按 `last_user_message_at` 降序,排除归档、子会话和没有真实用户输入的会话。打开会话、模型输出、隐藏提示及 agent 间消息不推进此时间;旧记录从 JSONL 逆向读取后缓存。目录或旧记录仍在加载时 `complete=false`,客户端可继续读取;空目录返回空数组及 `selected:null`。分叉继承保留消息的原始时间。

`selected` 和 `agents` 只传身份、标题、所属工作区、真实状态和当前上下文用量等展示字段;不含输入草稿、工具参数、工具输出或历史正文。`agents` 包含根会话和当前 daemon 已装载的直属/网状子会话;网状子会话的 `parent_session_id` 是树根,`agent_path` 保留实际层级。已成功结束的子会话可仍出现在原始快照中,由桌宠投影立即离座。标题/摘要限 240 UTF-8 字节。
`selected` 和 `agents` 只传身份、标题、所属工作区、真实状态和当前上下文用量等展示字段;不含输入草稿、原始工具参数、工具输出或历史正文(下文 `activity.detail` / `activity.text` 两个有界展示片段除外)。`agents` 包含根会话和当前 daemon 已装载的直属/网状子会话;网状子会话的 `parent_session_id` 是树根,`agent_path` 保留实际层级。已成功结束的子会话可仍出现在原始快照中,由桌宠投影立即离座。标题/摘要限 240 UTF-8 字节。

运行中条目的 `activity` 来自事件分发器维护的紧凑状态,不依赖有限的重放缓冲:`known`、`busy`、`turn_id`、`outcome`、`phase`、`label`、`tool`、`seq`、`compact_id`、`transfers`。待授权/待回答优先于工具和模型阶段;`outcome` 保留 `completed`/`aborted`/`error` 差别。`transfers` 最多保留 16 个带序号的 agent 间交接标识,不含消息内容。持久化 `last_turn_outcome` 使恢复后的完成主会话继续显示睡眠状态。
运行中条目的 `activity` 来自事件分发器维护的紧凑状态,不依赖有限的重放缓冲:`known`、`busy`、`turn_id`、`outcome`、`phase`、`label`、`tool`、`detail`、`text`、`seq`、`compact_id`、`transfers`。待授权/待回答优先于工具和模型阶段;`outcome` 保留 `completed`/`aborted`/`error` 差别。`transfers` 最多保留 16 个带序号的 agent 间交接标识,不含消息内容。持久化 `last_turn_outcome` 使恢复后的完成主会话继续显示睡眠状态。

流式正文会把 `phase` 置为 `responding`(默认「用于编程」模式下服务端不发该进度,由 token 事件推断),推理片段对应 `reasoning`,`text` 是本段正文或推理最后不超过 160 UTF-8 字节的尾巴,换阶段或开始工具调用即清空;`detail` 是正在运行工具的一行调用预览(`display_override`,为空时取 `command_preview`),最多 120 字节,工具结束清空。桌宠页面在两次快照之间直接用 WebSocket 的 `token` / `reasoning` 事件按 `seq` 叠加实时文字(约 150ms 节流),快照追上后以快照为准。

`token_usage` 是最近请求上下文统计,配合 `context_window` 计算文件堆厚度;未知窗口不伪造比例。休眠会话可通过已有 model 端点补充窗口大小。该端点不会发起模型请求或改变会话运行状态。

Expand Down
10 changes: 8 additions & 2 deletions docs/desktop-agent-office.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
# Agent 桌面办公室

Windows Desktop 会显示一个透明的像素办公室。单会话有一个主角色 Maestro,多 agent 任务按真实子会话入座;无需启动额外模型,也没有演示任务循环。美术与拖动缩放来自原有 Claude 桌宠,旧的 `design-prototypes/agent-office` 事务所原型已移除。
Windows 与 macOS Desktop 会显示一个透明的像素办公室。单会话有一个主角色 Maestro,多 agent 任务按真实子会话入座;无需启动额外模型,也没有演示任务循环。美术与拖动缩放来自原有 Claude 桌宠,旧的 `design-prototypes/agent-office` 事务所原型已移除。

## 操作

- 顶部显示跨工作区最近发过消息的五个主会话。顺序只看用户最后发送消息的时间,打开、切换和 agent 输出不会把会话顶到最前面。
- “跟随”默认开启,办公室随主窗口当前会话切换。点击门牌会关闭跟随并固定办公室;再次打开跟随后恢复跟随。查看最近五个之外的当前会话不改变门牌排序。
- 主 agent 成功完成后留在座位显示 `zzz`,下一轮自动醒来;成功完成的子 agent 立即离座,重新工作时以同样的外观返回。
- 忙碌的 agent 头顶常驻状态气泡:「图标 动作 · 细节」。正在输出时显示「撰写回复」和最新的几个字(打字动画 + 光标),推理时显示思考点和推理尾巴,调用工具时显示动作(读取、运行命令、修改代码…)和调用预览(路径、命令)。默认「用于编程」模式下服务端不发正文进度,桌宠由 token 事件推断。气泡互相避让:先左右错开,再抬高并用引线连回头顶,放不下时只留动作。
- 子 agent 出现时从门口走进来,先到派活的人桌前领「任务」信封再回工位;完成后走到派活的人桌前递「回报」信封(派活的人显示「收到…的结果」)再出门;失败或停止的成员直接出门。网状协作中 agent 间消息以带「消息 / 新任务 / 回报」标签的信封在工位间飞过。系统开启「减少动态效果」时不走路,交接信封仍然显示。
- 等待授权、等待回答、重试、压缩、失败和取消有各自状态。超过七个子角色时点击 `+N` 查看成员并打开对应会话;列表随状态更新,Escape 收起。选中房间人物后双击可打开其会话。
- 文件堆随当前上下文占用增长,压缩后依据实际新统计变薄。工具符号与聊天工具行共用 SVG 来源。
- 拖动房间空地移动窗口;滚轮、右下角把手和右键菜单可缩放。窗口不抢主界面焦点,透明角落可穿透点击;尺寸按原实现保存。

## 构建与数据流

由当前工作区的 `scripts/dev_desktop.bat` 启动开发版。先构建 `web/`,再配置/构建 CMake Desktop,使内嵌主界面与桌宠资源保持一致。`ACECODE_DESKTOP_PET=0` 可对本次进程禁用桌宠;macOS/Linux 当前没有原生桌宠窗口实现。
由当前工作区的 `scripts/dev_desktop.bat` 启动开发版。先构建 `web/`,再配置/构建 CMake Desktop,使内嵌主界面与桌宠资源保持一致。`ACECODE_DESKTOP_PET=0` 可对本次进程禁用桌宠;Linux 当前没有原生桌宠窗口实现。

macOS 版(`src/apps/desktop/desktop_pet_mac.mm`,ARC 编译)用非激活 NSPanel + WKWebView 承载同一份页面:文档开始前注入 `window.chrome.webview` 垫片,页面代码不分平台;透明区域的点击穿透靠定时检测鼠标是否落在房间、控制条或页面上报的浮层里,再切换 `ignoresMouseEvents`;拖动与把手缩放由定时器跟随鼠标。默认 1.25 倍(约 430×315 点),设置存 `~/Library/Application Support/ACECode/desktop-pet/settings.json`。

数据链为:会话元数据/事件分发器 → `GET /api/desktop-office` → App 级办公室控制器 → 原生 WebView 桥 → 像素场景。控制器复用现有引用计数 WS 订阅,事件触发刷新并以 3 秒轮询兜底;正在加载旧记录时短间隔继续读取。快速切换以请求代次和取消信号隔离迟到响应;页面卸载释放订阅和计时器,重载从原生缓存恢复当前选择与跟随状态。

页面把成员列表、提示和状态气泡的矩形(节流后)上报给原生窗口:Windows 的窗口区域同时裁剪绘制,冒出墙顶的气泡必须并入区域才不会被切掉;macOS 用同一份矩形判断点击穿透。

断线时明确显示正在重连,恢复后重取真实快照。错误和取消不会被当作成功睡眠。只同步小型展示字段,不通过动画触发或控制 agent 任务。

视觉规则见 [桌宠设计](../assets/desktop_pet/DESIGN.md),协议见 [daemon API](daemon-api.md)。验收命令及本次实测范围见 [交付记录](../openspec/changes/integrate-desktop-agent-office/verification.md)。
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-06
31 changes: 31 additions & 0 deletions openspec/changes/enhance-desktop-agent-office-motion/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Context

见 proposal.md。前一变更 `integrate-desktop-agent-office` 已交付 Windows 办公室;本变更只增强表现层与平台覆盖。

## Decisions

### 1. 状态来源

服务端 `SessionActivityState` 在分发器锁内折叠事件:token → `responding`,reasoning → `reasoning`,各自保留有界尾巴,换阶段或开始工具即清空;工具开始记录 `display_override`(为空取 `command_preview`)。这样快照在任意时刻都说得清「正在做什么」,打开办公室或断线重连后不依赖重放。实时性由 Web 控制器负责:token/reasoning 事件按会话写入叠加层,`seq` 大于快照才生效,快照追上即丢弃;连续正文在快照尾巴后拼接。不为每个 token 发 HTTP 请求。

### 2. 文案

动作动词与工具图标同表(`toolIcons.js`);「适合日常工作」模式下的具体进度标题优先,引擎默认的笼统文案(等待模型响应、正在调用工具 X…)不进气泡。细节按半角宽度从开头裁剪,保留最新的字和文件名。

### 3. 交接动画

走路沿 2 单位网格 BFS(8 向、不斜穿桌角),家具占地外扩 1 单位;先到派活的人(`agent_path` 的上一级,找不到时是主 agent)桌前停顿并交接信封,再回工位或出门。离开中的成员又接到任务时掉头回座。只在本办公室已显示过真实快照后演,切换办公室和重连恢复直接落座。减少动态效果时不走路但保留信封。

### 4. 原生窗口区域

Windows `SetWindowRgn` 同时裁剪绘制,气泡冒出墙顶会被切掉,所以页面把气泡矩形(含尾巴与引线)节流上报,原生端与成员列表/提示一起并入区域。macOS 没有窗口区域,定时检测鼠标是否命中房间、控制条或这些矩形,切换 `ignoresMouseEvents`。命中判定与消息解析是纯函数,跨平台单测。

### 5. macOS 宿主

NSPanel(无边框、非激活、不可成为 key window、浮动层级、所有空间可见),WKWebView 关闭背景绘制并重载 `acceptsFirstMouse`。页面按 WebView2 接口编写,宿主在文档开始前注入同名垫片,消息经 `WKScriptMessageHandler` 回到 C++,宿主经 `evaluateJavaScript` 派发。拖动与把手缩放由 60Hz 定时器跟随鼠标。ObjC 回调只持有控制器 weak_ptr;关闭时移除脚本消息处理器(否则内容控制器强引用导致泄漏)。默认 1.25 倍逻辑尺寸,设置不写入 ACECode 数据目录。

## Risks / Trade-offs

- [macOS 未在本机编译] → 只用稳定的 AppKit/WebKit 公共接口与 ARC;需 macOS CI 或实机构建确认。
- [气泡拥挤] → 优先级 + 错开/抬高/压缩三级避让;极端满员时仍可能重叠。
- [流式尾巴进入快照] → 只保留 160 字节、换阶段清空,接口仍需鉴权,与聊天页可见内容一致。
26 changes: 26 additions & 0 deletions openspec/changes/enhance-desktop-agent-office-motion/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Why

桌面 agent 办公室接入真实会话后,用户反馈三点:看不到代理之间交互的动画;代理飙字时看不到状态,气泡只出现一个图标、看不出在做什么;需要 macOS 支持。根因分别是:交接只有一个约 1 秒、9×6 像素的小信封,成员直接闪现/消失;默认「用于编程」模式下服务端不发正文进度,快照一直停在「等待模型」,WebSocket 的 token 事件又被控制器整体忽略,忙碌状态没有文字气泡;原生窗口只有 Windows 实现。

## What Changes

- 会话活动快照增加流式正文/推理尾巴(≤160 字节)与工具调用预览(≤120 字节),由 token 事件推断 `responding`。
- 控制器用 WebSocket 的 token / reasoning 事件按 seq 叠加实时文字(约 150ms 节流),不增加快照请求。
- 忙碌 agent 常驻状态气泡「图标 动作 · 细节」,按优先级避让;打字、阅读、思考、等成员各有动作。
- 成员走进门、到派活的人桌前领「任务」信封、回工位;完成后走去递「回报」信封再出门;网状消息带标签信封。减少动态效果时不走路但保留信封。
- 页面上报气泡矩形,Windows 窗口区域并入(避免被裁剪),macOS 用于点击穿透判定。
- 新增 macOS 原生桌宠:非激活 NSPanel + WKWebView,注入 `chrome.webview` 垫片复用同一页面。

## Capabilities

### New Capabilities

无。

### Modified Capabilities

- `desktop-agent-office`: 状态可读性、交接动画与 macOS 宿主(以新增要求记录,原变更尚未归档)。

## Impact

会话活动状态(domain)、办公室快照文档、Web 办公室状态/控制器与图标动词、桌宠页面、Windows 宿主窗口区域、新增 macOS 宿主与 CMake 登记。不改变 agent 调度、权限或模型请求。macOS 宿主未在本机编译验收,需在 macOS 构建与实机确认。
Loading