Skip to content

Commit d920da1

Browse files
author
tmoonlight
committed
feat: add queue card item handling and related tests
- Implemented `buildQueueCardItem` and `buildQueueCardItems` for transforming queue items into a structured format for display in `QueueCardList`. - Added unit tests for `buildQueueCardItem` covering various states (QUEUED, SENDING, FAILED) and ensuring proper handling of edge cases. - Introduced `searchSessions` module for scoring and ranking sessions based on user queries, including freshness scoring and relative time formatting. - Created unit tests for `searchSessions` to validate scoring logic and time bucket categorization. - Developed `sessionModel` module for normalizing model states and generating display labels, with corresponding tests to ensure functionality. - Added `slashCommands` module for command ranking and parsing, including tests for command matching and sorting logic. - Implemented `useGlobalShortcut` hook for handling global keyboard shortcuts, with tests for shortcut matching behavior.
1 parent 54de855 commit d920da1

95 files changed

Lines changed: 6329 additions & 315 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,49 @@
44

55
ACECode is a C++17 terminal AI coding agent. The root `main.cpp` contains the TUI entry point, while reusable logic lives under `src/` by feature area: `agent_loop/`, `commands/`, `config/`, `daemon/`, `provider/`, `session/`, `skills/`, `tool/`, `tui/`, and `utils/`. Unit tests live in `tests/` and mirror source paths, for example `src/session/session_storage.cpp` is tested by `tests/session/session_storage_test.cpp`. Static model catalog assets are in `assets/models_dev/`; user docs are in `docs/`; vendored or submodule code is under `external/`; vcpkg overlay ports are under `ports/`.
66

7+
Start with docs instead of duplicating them here: [README.md](README.md) for product overview, [ARCHITECTURE.md](ARCHITECTURE.md) for high-level structure, [DOCS_INDEX.md](DOCS_INDEX.md) for document navigation, [docs/daemon-api.md](docs/daemon-api.md) for HTTP/WebSocket behavior, [docs/model-context-resolution.md](docs/model-context-resolution.md) for model selection, [docs/skills.md](docs/skills.md) and [docs/skills-implementation.md](docs/skills-implementation.md) for skills, and [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md) for the current desktop multi-workspace model. [CLAUDE.md](CLAUDE.md) is a detailed implementation memory; consult it for current subsystem notes but keep this file concise.
8+
79
## Build, Test, and Development Commands
810

911
- `git submodule update --init --recursive`: fetch required submodules.
10-
- `cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=<vcpkg-root>/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=<triplet> -DBUILD_TESTING=ON`: configure a testable build.
12+
- `cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=<vcpkg-root>/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=<triplet> -DVCPKG_OVERLAY_PORTS=$PWD/ports -DBUILD_TESTING=ON`: configure a testable build.
1113
- `cmake --build build --config Release`: build the `acecode` executable.
1214
- `cmake --build build --target acecode_unit_tests`: build the GoogleTest binary.
1315
- `ctest --test-dir build --output-on-failure`: run the unit suite.
1416
- `scripts/code_quality_check.sh` or `scripts/code_quality_check.bat`: run local quality checks for common DRY and error-handling issues.
17+
- Web UI lives in `web/`: run `pnpm install` once, `pnpm test` for JS tests, and `pnpm build` before configuring/building CMake when embedded frontend assets must be refreshed. See [web/README.md](web/README.md).
18+
- Desktop shell is opt-in: configure with `-DACECODE_BUILD_DESKTOP=ON` and build `acecode-desktop`. The desktop target expects the daemon executable beside it at runtime.
19+
20+
## Architecture Boundaries
21+
22+
- Keep TUI-specific code in `main.cpp`, `src/tui/`, and `src/markdown/`; place reusable, testable logic in the relevant `src/<feature>/` directory.
23+
- `acecode_testable` intentionally excludes the full TUI entry point and desktop WebView shell; pure helpers can be added there and covered by unit tests.
24+
- Daemon/API work usually touches `src/daemon/`, `src/web/`, and `src/session/`; update [docs/daemon-api.md](docs/daemon-api.md) when protocol behavior changes.
25+
- React/Vite/Tailwind frontend work stays under `web/src/`. Do not edit generated build output directly; regenerate it with the web build.
26+
- Avoid modifying vendored/submodule trees (`external/`, `hermes-agent/`, `claudecodehaha/`) unless the task explicitly targets them.
27+
28+
## OpenSpec Workflow
29+
30+
For non-trivial behavior changes, create or continue an OpenSpec change under `openspec/changes/` before implementation. Use the prompts in [.github/prompts/opsx-propose.prompt.md](.github/prompts/opsx-propose.prompt.md), [.github/prompts/opsx-apply.prompt.md](.github/prompts/opsx-apply.prompt.md), and [.github/prompts/opsx-archive.prompt.md](.github/prompts/opsx-archive.prompt.md). During implementation, read the change context, complete tasks one by one, and immediately update task checkboxes in the change's `tasks.md`.
1531

1632
## Coding Style & Naming Conventions
1733

1834
Follow `.editorconfig`: UTF-8, LF line endings, final newline, 4-space indentation for C++ and CMake, 2-space indentation for JSON/YAML. Use C++17. Keep headers in `src/**/*.hpp` and implementations in matching `.cpp` files. Name test files with the singular suffix `_test.cpp`; `tests/CMakeLists.txt` discovers that pattern automatically. Prefer existing helpers such as `ToolArgsParser`, `ToolErrors`, and path/session utilities instead of duplicating parsing or validation logic.
1935

36+
This is a terminal UI project: avoid adding Emoji or ambiguous-width glyphs to C++ source, rendered UI, logs, and console output. Prefer ASCII or width-stable symbols already used in the codebase.
37+
2038
## Testing Guidelines
2139

2240
Tests use GoogleTest through the `acecode_unit_tests` target. Add tests for pure logic, serializers, parsers, validators, and headless state machines. Keep TUI-heavy code in `src/tui/`, `src/markdown/`, and `main.cpp` manually validated unless the code can be isolated. Use `testing::TempDir()` or `std::filesystem::temp_directory_path()` for file I/O; do not write test artifacts into the repository tree. Prefer `EXPECT_*` unless failure would make later assertions unsafe.
2341

42+
If CMake test discovery/build integration is unavailable in the editor, still keep changes compatible with the documented `cmake --build` and `ctest` commands. For web-only changes, at minimum run `pnpm test` and `pnpm build` from `web/`.
43+
2444
## Commit & Pull Request Guidelines
2545

2646
Recent history uses short imperative commits, sometimes with `feat:` prefixes, for example `feat: Implement AskUserQuestion tool` or `Add unit tests for session serialization`. Keep commits focused and mention tests when relevant. Pull requests should describe the behavior change, list verification commands, link related issues, and include screenshots or terminal captures for visible TUI changes.
2747

2848
## Security & Configuration Tips
2949

3050
Do not commit API keys, Copilot tokens, generated session data, or local `~/.acecode/config.json` contents. Treat `--dangerous` mode as a local-only convenience and avoid recommending it in docs or examples without a clear warning.
51+
52+
On Windows PowerShell 5.1, avoid `Set-Content` / `Out-File` for files containing Chinese or other non-ASCII text because they can write BOMs or mojibake. Use editor-based edits or explicit UTF-8 without BOM APIs instead.

CLAUDE.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ web/
161161

162162
SidePanel 折叠 UI:`ChatView``SidePanel` 包到 `<div class="ace-side-panel-shell" style={{width: collapsed ? 0 : sidePanelWidth}} data-collapsed={...}>`,折叠态宽度归 0 + opacity 过渡 200ms,SidePanel 仍 mount(tab/cache/preview 内部 state 保留)。SidePanel tab 行右端有 `.ace-side-panel-collapse-btn`(展开态),折叠态时 ChatView 顶部 header 内显示 `.ace-side-panel-expand-fab` 让用户重新展开。
163163

164+
全局会话搜索面板(`add-webui-search-palette`):`Ctrl+K` / `Cmd+K`(经 `lib/useGlobalShortcut.js` 的 `matchShortcut` 判定,`window` keydown + preventDefault)或 TopBar 🔍 按钮触发 `SearchPalette`。前端**纯聚合**所有 workspace 的 sessions:`api.listAllWorkspaceSessions`(底层 `mergeAllWorkspaceSessions` 纯函数 + `Promise.allSettled`,单 workspace 失败不阻塞其它)→ `lib/searchSessions.js::rankSessions` 加权排序(title 前缀 +1000 / 子串 +500 / summary +200 / workspaceName +100 / fuzzy 兜底 +50,叠 24h/7d/30d 时间衰减 0~50)→ z-300 居中模态。键盘导航 ↑/↓/PgUp/PgDn/Home/End/Enter/Esc;选中同 workspace 直接 `setActiveRef`;跨 workspace 优先 `aceDesktop_activateWorkspace` + 整页 navigate `?open=<sid>`(App.jsx mount 时解析并 `replaceState` 抹掉 query),无 bridge 时降级直接 setActiveRef。数据 60s TTL 缓存,`session_status` / `session_status_snapshot` / `mark_session_read_ack` 任一 WS 帧到达即 invalidate。**后端零路由变更**。
165+
166+
排队卡片栈(`redesign-webui-queue-cards`):busy 期间提交的待发送消息**不进 transcript**,改由 `<QueueCardList>`(在 `<InputBar>` 上方)渲染成卡片堆。状态机(`lib/chatInputQueue.js`)与 `enqueueQueuedInput` / `cancelQueuedInput` / `markQueuedInput*` / `nextQueuedInput` / `completeQueuedInputForMessage` 全部不变;只是渲染分支换地方。每张卡片左侧 3px `.ace-queue-card-indicator` 色条标注状态(QUEUED 灰 / FAILED 红),右侧恒挂"取消"(close 图标),FAILED 多一个"重试"。状态↔标签映射收敛在 `lib/queueCardItem.js::buildQueueCardItem`(纯函数,Node 单测覆盖);DOM 端只是把这份结构映射到 className。`Message.jsx::UserBubble` 已剥离 `queued`/`onCancelQueued`/`onRetryQueued` props——transcript 里出现的 user 气泡一定是后端真实落库的消息。
167+
164168
### Web UI: HTTP / WS 协议增量
165169

166170
`add-web-chat-ui` change 在 `add-web-daemon` 的 14 条 Requirement 之上扩 9 项;`enhance-webui-chat-rendering` 又扩 1 个端点 + 协议字段;`add-webui-side-panel` 加 2 个文件浏览端点:
@@ -176,6 +180,8 @@ SidePanel 折叠 UI:`ChatView` 把 `SidePanel` 包到 `<div class="ace-side-pane
176180
| `PUT /api/skills/:name` body `{enabled}` / `GET /api/skills/:name/body` | 启停切换 + 查看 SKILL.md;PUT 写 `cfg.skills.disabled` 数组并 `save_config` + `SkillRegistry::reload` |
177181
| `GET /api/files?cwd=&path=&show_hidden=``[{name,path,kind,size?,modified_ms?}]` | SidePanel 文件 tab 的 lazy 文件树。`cwd` 必须 ∈ `{deps.cwd}` 白名单;`path``weakly_canonical(cwd/path)` + prefix 检查防越权。硬编码 noise 黑名单(.git/node_modules/dist/build/__pycache__/.venv/venv/target/.next/.cache)始终过滤。隐藏文件(dot 开头)默认过滤,`show_hidden=1` 透出 |
178182
| `GET /api/files/content?cwd=&path=``text/plain; charset=utf-8` body | SidePanel 预览 tab 读文件原文。> 5MB → 415 `{error:"file too large",size:N}`;前 512 字节出现 `\0` → 415 `{error:"binary"}`;不存在 → 404。实现:`src/web/handlers/files_handler.{hpp,cpp}`(纯函数,17 个 unit test 覆盖路径越权 / 噪音过滤 / 排序 / 二进制嗅探) |
183+
| `GET /api/commands?workspace=<hash>` → `{builtins:[{name,description}][, skills:[{name,description}]]}` | InputBar 斜杠下拉的命令清单。builtins **硬编码白名单 = init + compact**(描述与 TUI `register_builtin_commands` / `register_init_command` 对齐)。**`workspace` 参数(由 `expand-webui-skill-commands` 引入)**:缺省 → 不返回 `skills` 字段(向后兼容旧客户端);提供 → handler 用 `acecode::initialize_skill_registry(tmp, *cfg, workspace_cwd)` 临时构造一个 SkillRegistry 扫该 workspace 的项目链(`.agent/skills`、`.acecode/skills` + 全局 + external_dirs),与 daemon 全局 SkillRegistry 合并(workspace local 优先,first-wins by name),按字典序输出 skills 字段。`/init` `/compact` 在 web 端选中后只插入输入框 + chip 高亮,daemon 端**不**做特殊执行(原 add-webui-slash-commands 决策);`/<skill-name> args` 由下面新加的 expander 真展开。实现:`src/web/handlers/commands_handler.{hpp,cpp}`(纯函数 `build_commands_payload` + gtest case)|
184+
| `POST /api/sessions/:id/messages`(行为扩展) | `expand-webui-skill-commands` 引入:在 `send_input` 之前调 `try_expand_skill_command(text, registry)`(`src/web/handlers/skill_command_expander.{hpp,cpp}`)。命中已知 skill 名(按 session 的 workspace cwd 临时 scan)→ text 被替换为 `build_skill_invocation_hint(meta, args)`**轻量提示**:`[SYSTEM: User invoked /<name> skill] + Description + Use skill_view(name=...) to load full SKILL.md + User's request: <args>`****注入 SKILL.md body / supporting_files,LLM 第一次看到提示后主动 invoke `skill_view` tool 把 SKILL.md 拉一次进 context,后续重复同名 `/skill` 调用不再注入(避免 context 膨胀)。TUI `src/skills/skill_commands.cpp::cmd.execute` 也走同一个 `build_skill_invocation_hint`,跨端行为统一。Builtin (`/init`/`/compact`) 不在 SkillRegistry 中 → 透传走普通 user message;未知命令 (`/foobar`) 同样透传。****新增执行端点,****改 AgentLoop API |
179185

180186
`SessionMeta` 增加 `forked_from` / `fork_message_id` 字段(空时省略,老 meta 文件向后兼容)。Web 上每条消息 hover 浮出 `[复制] [分叉]` actions(codex 风格);分叉成功后立刻切到新 session(同 sidebar)。
181187

@@ -193,13 +199,18 @@ handler 实现在 `src/web/handlers/{fork,models,history,skills,files}_handler.{
193199
- `pick_active.{hpp,cpp}` — 启动选 active workspace 的纯函数:`state.json::last_active_workspace_hash` → process cwd 的 hash → registry 第一项 → 空。
194200
- `folder_picker_win.cpp``IFileOpenDialog` + `FOS_PICKFOLDERS`,COM STA。
195201
- `web_host.{hpp,cpp}` — webview 包装,暴露 `bind/eval/init_script/native_window`,debug=true 默认开 F12 DevTools。
196-
- `main.cpp::wWinMain` — 串起所有,quit 时写 `last_active_workspace_hash` + `pool.stop_all()`
202+
- `tray_icon_win.{hpp,cpp}` / `notifications_win.{hpp,cpp}` — 系统托盘 + OS 气泡通知。tray 注册 hidden message-only window,WndProc 接 `Shell_NotifyIcon` 回调消息(`NIN_BALLOONUSERCLICK` / `WM_RBUTTONUP` 等);通知用 `Shell_NotifyIconW(NIM_MODIFY, NIF_INFO)` piggyback 在同一图标上。`init_tray_icon``init_notifications(tray_hwnd)`,顺序不能反。V1 仅气泡,V2 计划接 WinRT ToastNotificationManager(需 AUMID + 开始菜单 .lnk + cppwinrt)。
203+
- `main.cpp::wWinMain` — 串起所有,quit 时写 `last_active_workspace_hash` + `pool.stop_all()` + `shutdown_notifications()` + `shutdown_tray_icon()`
197204

198205
JS↔C++ bridge(同进程,webview `bind`,无 HTTP):
199206
- `aceDesktop_listWorkspaces()``[{hash, cwd, name, daemon_state, active, port?, token?}]`
200207
- `aceDesktop_activateWorkspace(hash)``{port, token}``{error}`
201208
- `aceDesktop_renameWorkspace(hash, name)``{ok}``{error}`
202209
- `aceDesktop_addWorkspace()``{hash, cwd, name}``null`(取消)
210+
- `aceDesktop_notify({id, workspace_hash, session_id, title, body})``{ok}` — 投递 OS 系统通知(`add-desktop-attention-notifications`),前端在 `sessionTranscript.js` 命中 `question_request` / busy→idle+回合有 assistant 输出 时调用;前端 `lib/desktopNotify.js::shouldSuppress` 已根据 `health.notifications` cfg + `document.hasFocus()` + 当前 active session 做抑制规则,native 端不二次过滤。
211+
- `aceDesktop_focusSession({workspace_hash, session_id})``{ok}` — 把窗口拉前 + 切 session。toast 点击在 native click_handler 里走相同路径(`SetForegroundWindow + ShowWindow(SW_RESTORE)` + `webview.eval``window.aceDesktop_focusSessionFromBridge` / `window.aceDesktop_activateAndOpenSession`,后两个由 `App.jsx` mount 时注册到 window)。
212+
213+
桌面通知抑制规则(`config.desktop.notifications`,默认四个 bool 均 true):`enabled` 总开关 / `on_question` AskUserQuestion 触发 / `on_completion` 回合完成触发 / `suppress_when_focused` 当前 session 已可见且窗口聚焦时跳过(避免对正盯屏的用户重复打扰)。配置经 `/api/health` 透传给前端(`health.notifications`),`lib/desktopNotify.js::maybeNotify` 一站式构造 payload + 抑制 + 投递。**多 workspace v1 限制**:webview 只连 active workspace 的 daemon,后台 workspace 的事件感知不到;v2 future work。
203214

204215
切换 workspace 走**整页 navigate**(对设计稿 D6"无重载切换"的合理偏离):跨 loopback 端口 fetch 受 CORS 拦截,整页 navigate 让 origin 跟着切。代价是滚动 / 弹窗状态丢失,但 input box 清空 / 消息列表替换符合 spec。后续若加 `Access-Control-Allow-Origin: http://127.0.0.1:*` 可恢复无重载。
205216

@@ -222,6 +233,8 @@ Both `main.cpp` and `daemon/worker.cpp` call `proxy_resolver().init(cfg.network)
222233

223234
**Behavior change:** Windows users upgrading get `proxy_mode = "auto"` by default, which means ACECode now follows the system proxy. To keep the old direct-only behavior: `{"network":{"proxy_mode":"off"}}`.
224235

236+
**Auto-fallback on unreachable proxy** (`proxy-fallback-on-unreachable`): 启动时调用 `proxy_resolver().probe_and_maybe_fallback()``init` 之后做一次同步 TCP probe(`src/network/tcp_probe.{hpp,cpp,_posix.cpp,_win.cpp}`)— 对解析出的代理 host:port 做非阻塞 connect + poll/WSAPoll,失败时设进程级 `fallback_active_`。横幅变成 `Proxy: direct (auto-fallback: <redacted-original-url> from <original-source> unreachable)`,所有 cpr 走直连。`/proxy` 输出新增 `Reachable : yes/no (<reason>)`,fallback 时多一行 `Original proxy : <url> (<source>)``/proxy refresh` 同时清 fallback + 重探(用户启动 Fiddler 后立即生效)。两个新配置:`network.proxy_probe_enabled`(默认 true,false = 一键回到旧行为)、`network.proxy_probe_timeout_ms`(默认 1500,clamp 到 [200, 10000])。Session override (`/proxy off` / `/proxy set`) 永远胜过 fallback — 用户显式意志不被二次猜测。
237+
225238
### Web Search
226239

227240
`src/tool/web_search/` 提供 LLM 可调的 `web_search(query, limit)` 工具,默认零配置:启动时 HEAD 探一次 `duckduckgo.com`(2s 超时,过 ProxyResolver),通 → 选 DuckDuckGo backend(海外/挂梯子)、不通 → 选 必应中国 backend(国内裸连)。探测结果缓存到 `~/.acecode/state.json``web_search.region_detected`,永不自动过期。
@@ -281,7 +294,9 @@ Both `main.cpp` and `daemon/worker.cpp` call `proxy_resolver().init(cfg.network)
281294
"proxy_url": "",
282295
"proxy_no_proxy": "",
283296
"proxy_ca_bundle": "",
284-
"proxy_insecure_skip_verify": false
297+
"proxy_insecure_skip_verify": false,
298+
"proxy_probe_enabled": true,
299+
"proxy_probe_timeout_ms": 1500
285300
},
286301
"web_search": {
287302
"enabled": true,
@@ -291,6 +306,14 @@ Both `main.cpp` and `daemon/worker.cpp` call `proxy_resolver().init(cfg.network)
291306
"timeout_ms": 8000
292307
},
293308
"tui": { "alt_screen_mode": "auto" },
309+
"desktop": {
310+
"notifications": {
311+
"enabled": true,
312+
"on_question": true,
313+
"on_completion": true,
314+
"suppress_when_focused": true
315+
}
316+
},
294317
"saved_models": [
295318
{ "name": "local-lm", "provider": "openai", "base_url": "http://localhost:1234/v1", "api_key": "x", "model": "llama-3" },
296319
{ "name": "copilot-fast", "provider": "copilot", "model": "gpt-4o" }

cmake/acecode_desktop.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ set(ACECODE_DESKTOP_SOURCES
4242
${CMAKE_SOURCE_DIR}/src/desktop/splash_screen.cpp
4343
${CMAKE_SOURCE_DIR}/src/desktop/web_host.cpp
4444
)
45+
# 注:notifications_win.cpp / tray_icon_win.cpp 是纯 Win32 调用(Shell_NotifyIcon
46+
# / RegisterClassEx 等),不依赖 webview/webview,所以走 acecode_testable 路径
47+
# (根 CMakeLists.txt 的 ACECODE_ALL_SOURCES GLOB 会拾取),acecode-desktop 通过
48+
# target_link_libraries 继承。这样 acecode_unit_tests 也能跑相关纯逻辑测试。
49+
# 设计:openspec/changes/add-desktop-attention-notifications。
4550

4651
# Windows 上,acecode-desktop 用 WIN32 子系统(无 console 黑窗)。
4752
# 同时挂上顶层 CMakeLists.txt 生成的 acecode.rc(已在 ACECODE_WINDOWS_RESOURCES 里),

0 commit comments

Comments
 (0)