From e6f96d01a9760611b9b06776de9bf3c4a8c85bc0 Mon Sep 17 00:00:00 2001 From: huangxiangxin Date: Thu, 7 May 2026 17:10:30 +0800 Subject: [PATCH 1/3] doc: update docs 1.update documents 2.deprecate expired docs --- .gitignore | 3 + ACECODE.md | 78 ----- AGENTS.md | 42 +-- ARCHITECTURE.md | 638 +++++++++++++++------------------------- CLAUDE.md | 379 +++++------------------- DOCS_INDEX.md | 185 ------------ PROJECT_ORGANIZATION.md | 237 --------------- QUICK_REFERENCE.md | 317 -------------------- README.md | 428 ++++++++++++--------------- README_CN.md | 406 ++++++++++++------------- REFACTORING_COMPLETE.md | 357 ---------------------- REFACTORING_SUMMARY.md | 140 --------- TODO.md | 146 --------- build_output.txt | 121 -------- 14 files changed, 708 insertions(+), 2769 deletions(-) delete mode 100644 ACECODE.md delete mode 100644 DOCS_INDEX.md delete mode 100644 PROJECT_ORGANIZATION.md delete mode 100644 QUICK_REFERENCE.md delete mode 100644 REFACTORING_COMPLETE.md delete mode 100644 REFACTORING_SUMMARY.md delete mode 100644 TODO.md delete mode 100644 build_output.txt diff --git a/.gitignore b/.gitignore index 673e4dd03..07ba6873c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# os +.DS_Store + # Build directories **/build/ **/build2/ diff --git a/ACECODE.md b/ACECODE.md deleted file mode 100644 index 0fe04c320..000000000 --- a/ACECODE.md +++ /dev/null @@ -1,78 +0,0 @@ -# ACECODE.md - -This file provides guidance to acecode (https://github.com/tmoonlight/acecode) when working with code in this repository. - -## Project Overview - -ACECode is a terminal-based AI coding agent built in C++17. It provides an interactive TUI (Terminal User Interface) using FTXUI, supporting multi-turn conversations with automatic tool calling through OpenAI-compatible APIs or GitHub Copilot authentication. - -## Build Instructions - -**Prerequisites:** CMake >= 3.20, Ninja, vcpkg, a C++17-capable compiler. - -```bash -# Initialize submodules -git submodule update --init --recursive - -# Install dependencies via vcpkg (example triplet: x64-windows-static or x64-linux) -/vcpkg install cpr nlohmann-json ftxui \ - --triplet \ - --overlay-ports=$PWD/ports - -# Configure -cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ - -DVCPKG_TARGET_TRIPLET= \ - -DVCPKG_OVERLAY_PORTS=$PWD/ports - -# Build -cmake --build build --config Release -``` - -Output binary: `build/acecode` (Ninja) or `build//acecode` (multi-config generators). - -**Note:** On Windows, libcurl >= 8.14 is required for TLS support. A custom FTXUI overlay port lives in `/ports/ftxui/`. - -## Testing - -Configure with `-DBUILD_TESTING=ON` (default ON) and run: -```bash -cmake --build build --target acecode_unit_tests && ctest --test-dir build --output-on-failure -``` - -Tests live under `tests/`, mirror the `src/` layout, and file names end in `_test.cpp` (e.g. `src/utils/terminal_title.cpp` → `tests/utils/terminal_title_test.cpp`). `tests/CMakeLists.txt` globs `*_test.cpp` automatically. The test binary links against the `acecode_testable` OBJECT library, which holds every `src/*.cpp` **except** `src/tui/` and `src/markdown/`. `main.cpp` is always excluded. - -## Running - -```bash -./build/acecode # Fresh session -./build/acecode --resume [id] # Resume a previous session -./build/acecode configure # Run setup wizard -./build/acecode -dangerous # Bypass all permission checks (use carefully) -``` - -## High-Level Architecture - -### Data Flow -User input → TUI event handler → `AgentLoop` → `LlmProvider::chat_stream()` → SSE parse → streaming tokens back to TUI. -When the LLM returns tool calls: `AgentLoop` invokes `PermissionManager` → `ToolExecutor` → result appended to message history → next LLM turn. - -### Threading Model -Three background threads besides the main FTXUI loop: -1. **Agent worker** — runs `AgentLoop`, blocked on LLM HTTP streaming -2. **Auth thread** — Copilot device-flow polling -3. **Animation thread** — drives the "thinking" spinner at fixed intervals - -### Key Components - -- **`main.cpp`**: CLI arg parsing, terminal setup, provider/tool initialization, FTXUI rendering loop, event dispatch, shutdown. -- **`src/agent_loop.{hpp,cpp}`**: Multi-turn conversation state machine. Runs on a worker thread. -- **`src/provider/`**: `LlmProvider` interface with `OpenAiCompatProvider` and `CopilotProvider`. Includes `ModelContextResolver` mapping model names to context window sizes via bundled registry. -- **`src/tool/`**: `ToolExecutor` registry + built-in tools (`bash_tool`, `file_read_tool`, `file_write_tool`, `file_edit_tool`, `grep_tool`, `glob_tool`). -- **`src/permissions.hpp`**: `PermissionManager` (`Default`, `AcceptEdits`, `Yolo`). Write/exec tools prompt unless rules match. -- **`src/commands/`**: `CommandRegistry` dispatches slash commands (`/help`, `/clear`, `/model`, `/models`, `/compact`, `/configure`, `/session`). -- **`src/tui/` & `src/tui_state.hpp`**: Handles UI state, live tool progress rendering (`tool_progress.*`), and autocomplete dropdowns (`slash_dropdown.*`). -- **`src/skills/`**: Discovers user-authored `SKILL.md` documents. Lazily loads bodies into the LLM context. -- **`src/memory/`**: Cross-session persistent user memory under `~/.acecode/memory/`. -- **`src/project_instructions/`**: Loads `ACECODE.md` / `AGENT.md` / `CLAUDE.md` from the project hierarchy into the system prompt. diff --git a/AGENTS.md b/AGENTS.md index 44a9c1cca..4b305d257 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,11 +2,13 @@ ## Project Structure & Module Organization -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/`. +ACECode is a C++17 terminal AI coding agent with daemon, web UI, and optional desktop surfaces. The root [main.cpp](main.cpp) owns the terminal TUI entry point. Reusable logic lives under [src/](src) by subsystem, including `commands`, `config`, `daemon`, `desktop`, `history`, `markdown`, `memory`, `network`, `project_instructions`, `provider`, `session`, `skills`, `tool`, `tui`, `utils`, and `web`. -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. +Unit tests live in [tests/](tests) and mirror source paths; for example, session storage code should be covered under `tests/session/`. Static model catalog assets are in [assets/models_dev/](assets/models_dev). User and subsystem docs live in [docs/](docs). Vendored or submodule code is under [external/](external). vcpkg overlay ports are under [ports/](ports). -## Build, Test, and Development Commands +Canonical root docs are [README.md](README.md), [README_CN.md](README_CN.md), [ARCHITECTURE.md](ARCHITECTURE.md), this file, and [CLAUDE.md](CLAUDE.md). Keep the root small: do not reintroduce one-off status reports, root task lists, captured build logs, or duplicate project-instruction docs. + +## Build, Test, And Development Commands - `git submodule update --init --recursive`: fetch required submodules. - `cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET= -DVCPKG_OVERLAY_PORTS=$PWD/ports -DBUILD_TESTING=ON`: configure a testable build. @@ -14,39 +16,45 @@ Start with docs instead of duplicating them here: [README.md](README.md) for pro - `cmake --build build --target acecode_unit_tests`: build the GoogleTest binary. - `ctest --test-dir build --output-on-failure`: run the unit suite. - `scripts/code_quality_check.sh` or `scripts/code_quality_check.bat`: run local quality checks for common DRY and error-handling issues. -- 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). +- In [web/](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). - 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. ## Architecture Boundaries -- Keep TUI-specific code in `main.cpp`, `src/tui/`, and `src/markdown/`; place reusable, testable logic in the relevant `src//` directory. -- `acecode_testable` intentionally excludes the full TUI entry point and desktop WebView shell; pure helpers can be added there and covered by unit tests. -- 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. -- React/Vite/Tailwind frontend work stays under `web/src/`. Do not edit generated build output directly; regenerate it with the web build. -- Avoid modifying vendored/submodule trees (`external/`, `hermes-agent/`, `claudecodehaha/`) unless the task explicitly targets them. +- Keep TUI-specific code in [main.cpp](main.cpp), [src/tui/](src/tui), and [src/markdown/](src/markdown). Put reusable, testable logic in the relevant `src//` area or a focused top-level `src/*.cpp` helper when that is the existing local pattern. +- `acecode_testable` intentionally excludes the full TUI entry point and desktop WebView shell. Pure helpers can be added there and covered by unit tests. +- Daemon/API work usually touches [src/daemon/](src/daemon), [src/web/](src/web), and [src/session/](src/session). Update [docs/daemon-api.md](docs/daemon-api.md) when protocol behavior changes. +- React/Vite/Tailwind frontend work stays under [web/src/](web/src). Do not edit generated build output directly; regenerate it with the web build. +- Avoid modifying vendored or submodule trees such as [external/](external), `hermes-agent/`, or `claudecodehaha/` unless the task explicitly targets them. ## OpenSpec Workflow -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`. +For non-trivial behavior changes, create or continue an OpenSpec change under `openspec/changes/` before implementation. Use the repository's OpenSpec workflow commands or the local `openspec` CLI to propose, apply, and archive changes. During implementation, read the change context, complete tasks one by one, and update task checkboxes in the change's `tasks.md` immediately after each task is complete. ## Coding Style & Naming Conventions -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. +Follow [.editorconfig](.editorconfig): UTF-8, LF line endings, final newline, 4-space indentation for C++ and CMake, and 2-space indentation for JSON/YAML. Use C++17. Keep headers in `src/**/*.hpp` and implementations in matching `.cpp` files where practical. Name test files with the singular suffix `_test.cpp`; `tests/CMakeLists.txt` discovers that pattern automatically. + +Prefer existing helpers such as `ToolArgsParser`, `ToolErrors`, path/session utilities, provider/model helpers, and web handler pure functions instead of duplicating parsing or validation logic. -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. +This is a terminal UI project. Avoid emoji or ambiguous-width glyphs in C++ source, rendered UI, logs, and console output. Prefer ASCII or width-stable symbols already used in the codebase. ## Testing Guidelines -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. +Tests use GoogleTest through the `acecode_unit_tests` target. Add tests for pure logic, serializers, parsers, validators, handler helpers, and headless state machines. Keep TUI-heavy code in [src/tui/](src/tui), [src/markdown/](src/markdown), and [main.cpp](main.cpp) manually validated unless logic can be isolated. -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/`. +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. + +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/](web). ## Commit & Pull Request Guidelines -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. +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 or OpenSpec changes, and include screenshots or terminal captures for visible TUI, web, or desktop changes. ## Security & Configuration Tips -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. +Do not commit API keys, Copilot tokens, generated session data, local config contents, runtime daemon tokens, or memory files with private user data. Treat `--dangerous` mode as a local-only sandbox convenience and avoid recommending it without a clear warning. -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. +On Windows PowerShell 5.1, avoid `Set-Content` and `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. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index cccc12e37..3cd3f8c02 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,410 +1,238 @@ -# AceCode 架构文档 +# ACECode Architecture + +ACECode has one shared agent core with several runtime surfaces around it: terminal TUI, daemon API, bundled web UI, Windows service mode, and the optional desktop shell. This document is the durable map of those parts. Implementation notes that change more often live in [CLAUDE.md](CLAUDE.md). + +## Runtime Surfaces + +| Surface | Entry point | Role | +| --- | --- | --- | +| Terminal TUI | [main.cpp](main.cpp) | Interactive shell experience with FTXUI rendering, slash commands, permission prompts, and local session work. | +| Daemon worker | [src/daemon/](src/daemon) | Background process that owns config, sessions, agent loops, heartbeat files, auth token, and termination handling. | +| HTTP/WebSocket API | [src/web/](src/web) | Crow server exposing health, sessions, messages, files, skills, MCP, models, and live session events. | +| Web frontend | [web/](web) | React/Vite/Tailwind UI served by the daemon from embedded or filesystem assets. | +| Windows service | [src/daemon/service_win.cpp](src/daemon/service_win.cpp) | SCM wrapper for running the daemon before login with a service data directory. | +| Desktop shell | [src/desktop/](src/desktop) | Optional webview shell that manages multiple workspace daemons and native desktop integration. | + +## Architecture Diagrams + +### System Topology + +```mermaid +flowchart TB + user[User] --> tui[Terminal TUI
main.cpp + FTXUI] + user --> desktop[Desktop Shell
src/desktop] + user --> browser[Browser Web UI
web/src] + + desktop --> daemonA[Workspace Daemon
acecode daemon] + browser --> webapi[HTTP + WebSocket API
src/web] + tui --> core[Shared Agent Core
AgentLoop] + daemonA --> webapi + webapi --> registry[SessionRegistry
per-session runtime] + registry --> core + + core --> provider[LlmProvider
Copilot or OpenAI-compatible] + core --> tools[ToolExecutor
built-ins + skills + MCP] + core --> sessions[SessionManager
JSONL + metadata] + core --> permissions[PermissionManager] + + provider --> network[Network + ProxyResolver] + tools --> workspace[Project Workspace] + tools --> memory[Memory Registry] + tools --> skills[Skill Registry] + sessions --> data[(ACECode data dir)] +``` + +### Agent Turn Flow + +```mermaid +sequenceDiagram + participant U as User + participant T as TUI or Web Client + participant A as AgentLoop + participant P as LlmProvider + participant M as PermissionManager + participant X as ToolExecutor + participant S as SessionManager + + U->>T: submit prompt + T->>A: submit(messages, callbacks) + A->>S: append user message + A->>P: stream chat completion + P-->>A: assistant delta + A-->>T: render streaming text + P-->>A: tool calls + loop For each tool call + A->>M: check permission + M-->>T: prompt when needed + T-->>M: allow or deny + A->>X: execute tool + X-->>A: ToolResult + A->>S: append tool result + end + A->>P: continue with tool results + P-->>A: text-only assistant reply or task_complete + A->>S: persist final assistant message + A-->>T: turn complete +``` + +### Daemon And Web Event Flow + +```mermaid +flowchart LR + client[Web UI or API Client] -->|REST| routes[HTTP Routes
src/web/handlers] + client <-->|WebSocket| ws[WS Session Channel] + + routes --> auth[Token Auth
loopback bypass] + ws --> auth + auth --> local[LocalSessionClient] + local --> registry[SessionRegistry] + + registry --> entry[SessionEntry] + entry --> loop[AgentLoop] + entry --> manager[SessionManager] + entry --> prompt[Async Permission + Question Prompters] + loop --> dispatcher[EventDispatcher
seq + replay ring] + dispatcher --> ws + manager --> store[(Project Session Store)] +``` + +### Persistence Map + +```mermaid +flowchart TB + cfg[config.json] --> runtime[TUI or daemon runtime] + state[state.json] --> runtime + models[models_dev snapshot] --> provider[Model resolution] + runtime --> provider + + runtime --> sessions[(projects/cwd_hash
sessions + metadata)] + runtime --> history[(input_history.jsonl)] + runtime --> memory[(memory/*.md
MEMORY.md index)] + runtime --> runfiles[(run/pid port guid token heartbeat)] + + sessions --> resume[resume and replay] + sessions --> rewind[rewind checkpoints] + memory --> prompt[system prompt context] + history --> tuiInput[TUI input history] + runfiles --> daemonStatus[daemon status and auth] +``` + +## Source Ownership + +| Area | Ownership | +| --- | --- | +| [main.cpp](main.cpp) | TUI entry point, CLI option parsing for interactive mode, provider/tool setup, FTXUI event loop, and terminal-specific UI wiring. | +| [src/agent_loop.cpp](src/agent_loop.cpp) and [src/agent_loop.hpp](src/agent_loop.hpp) | Multi-turn agent state machine, streaming callbacks, tool-call loop, cancellation, max-iteration handling, and completion semantics. | +| [src/provider/](src/provider) | `LlmProvider` implementations, provider factory/swap logic, Copilot auth integration, OpenAI-compatible streaming, model profiles, and context-window resolution. | +| [src/tool/](src/tool) | Tool registry, built-in tools, tool result metadata, summaries, MCP bridge, skills tools, memory tools, and optional web-search tool. | +| [src/permissions.hpp](src/permissions.hpp) | Permission modes and glob-style tool/path allow rules. | +| [src/session/](src/session) | Session JSONL persistence, metadata sidecars, replay, resume restore, rewind checkpoints, daemon session registry, and event dispatch. | +| [src/commands/](src/commands) | Slash command registry and built-in command implementations. | +| [src/config/](src/config) | Config load/save/validation, saved model profiles, and default schema behavior. | +| [src/skills/](src/skills) | Skill discovery, command registration, lazy skill body loading, default skill seeding, and skill invocation hints. | +| [src/memory/](src/memory) | Persistent user memory registry and memory file lifecycle. | +| [src/project_instructions/](src/project_instructions) | Configurable project-instruction discovery and prompt injection. | +| [src/history/](src/history) | Per-working-directory input history storage. | +| [src/daemon/](src/daemon) | Foreground/detached/service daemon launch, runtime files, heartbeat, process supervision, and worker lifecycle. | +| [src/web/](src/web) | HTTP routes, WebSocket envelopes, payload codecs, auth, static assets, and web-specific handlers. | +| [src/desktop/](src/desktop) | Workspace registry, daemon pool, native webview host, tray, notifications, and desktop bridge. | +| [src/tui/](src/tui) and [src/markdown/](src/markdown) | Reusable TUI helpers, markdown rendering, overlays, progress rendering, scroll helpers, and terminal render mode helpers. | +| [src/network/](src/network) | Proxy resolution, proxy probing, and shared networking configuration. | +| [src/utils/](src/utils) | Shared filesystem, encoding, logging, state, token, UUID, hashing, stream, and terminal helpers. | +| [tests/](tests) | GoogleTest coverage for headless logic through `acecode_testable`. | + +## Terminal Turn Flow + +```text +User input + -> TUI command/input handling + -> AgentLoop::submit + -> LlmProvider streaming request + -> assistant text and/or tool calls + -> PermissionManager decision + -> ToolExecutor execution + -> tool result appended to conversation + -> next provider request until assistant text-only completion or explicit task_complete +``` + +The TUI keeps rendering state in `TuiState`; callbacks from the worker side post events back to the FTXUI loop. Read-only tools are normally auto-approved. Write and exec tools prompt unless permission mode or rules allow them. + +## Daemon And Web Flow + +```text +acecode daemon start + -> spawn detached worker + -> load config and resolve data dir + -> write pid/port/guid/token/heartbeat files + -> create SessionRegistry and WebServer + -> serve REST, WebSocket, and static frontend assets +``` + +Each daemon session owns its own `SessionManager`, `PermissionManager`, `AgentLoop`, async permission prompter, and question prompter. `EventDispatcher` assigns monotonic sequence numbers and keeps a bounded replay buffer so WebSocket clients can reconnect without losing recent frames. + +Loopback clients can skip daemon auth. Non-loopback clients must provide the daemon token, and non-loopback dangerous mode is rejected. Full protocol details live in [docs/daemon-api.md](docs/daemon-api.md). + +## Persistence -## 总体架构 +| Data | Location | +| --- | --- | +| User config | `~/.acecode/config.json` for normal user mode. | +| Service config/data | Platform service data directory in service mode. | +| Sessions | `~/.acecode/projects//` with JSONL messages and metadata sidecars. | +| Rewind checkpoints | Project session storage associated with user turns. | +| Input history | Per-project JSONL history independent of session messages. | +| Memory | `~/.acecode/memory/` with indexed Markdown entries. | +| Runtime daemon files | `/run/` for pid, port, guid, token, and heartbeat. | +| State | `~/.acecode/state.json` for small cross-session flags and caches. | +| Bundled model catalog | `assets/models_dev/` at source time, installed under `share/acecode/models_dev`. | + +Session serialization intentionally keeps runtime-only UI fields out of persisted messages. Resume paths rebuild display rows and tool previews from persisted canonical messages and metadata. + +## Providers And Models + +ACECode supports GitHub Copilot and OpenAI-compatible endpoints through a shared `LlmProvider` interface. Model selection can come from legacy provider fields, named `saved_models`, a global default, per-project overrides, or resumed session metadata. + +Context-window resolution uses saved profile data, bundled models.dev metadata, provider defaults, and configured fallbacks. See [docs/model-context-resolution.md](docs/model-context-resolution.md). -``` -┌─────────────────────────────────────────────────────────────┐ -│ User (Terminal) │ -└────────────────────────┬────────────────────────────────────┘ - │ -┌────────────────────────▼────────────────────────────────────┐ -│ TUI Layer (FTXUI) │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Chat View │ │ Input Box │ │ Status Bar │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -│ ┌──────────────┐ ┌──────────────┐ │ -│ │ MD Renderer │ │ Thinking │ │ -│ └──────────────┘ └──────────────┘ │ -└────────────────────────┬────────────────────────────────────┘ - │ -┌────────────────────────▼────────────────────────────────────┐ -│ Application Layer │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ Agent Loop (submit/cancel) │ │ -│ └───────┬──────────────────────────────────────┬───────┘ │ -│ │ │ │ -│ ┌───────▼───────┐ ┌────────▼────────┐ │ -│ │ Provider │ │ Tool Executor │ │ -│ │ ┌───────────┐ │ │ ┌─────────────┐ │ │ -│ │ │ OpenAI │ │ │ │ Bash │ │ │ -│ │ │ Copilot │ │ │ │ FileRead │ │ │ -│ │ └───────────┘ │ │ │ FileWrite │ │ │ -│ └───────────────┘ │ │ FileEdit │ │ │ -│ │ │ Grep │ │ │ -│ ┌───────────────┐ │ │ Glob │ │ │ -│ │ Session │ │ └─────────────┘ │ │ -│ │ Manager │ └─────────────────┘ │ -│ └───────────────┘ ┌─────────────────┐ │ -│ │ Permission │ │ -│ ┌───────────────┐ │ Manager │ │ -│ │ Config │ └─────────────────┘ │ -│ └───────────────┘ │ -└─────────────────────────────────────────────────────────────┘ -``` - -## 模块说明 - -### 1. TUI Layer(用户界面层) - -**职责**:处理所有用户交互和显示 - -**核心组件**: -- `TuiState` - 界面状态管理 -- `ChatView` - 对话显示(支持markdown) -- `InputBox` - 用户输入(带历史记录) -- `StatusBar` - 状态显示 -- `ThinkingAnimation` - 思考动画 - -**依赖**:FTXUI库 - -**线程**:运行在UI主线程 - -### 2. Application Layer(应用层) - -#### AgentLoop -**职责**:核心业务逻辑,协调provider和tool - -**主要方法**: -- `submit(prompt)` - 提交用户请求 -- `cancel()` - 取消当前执行 -- `resume(session_id)` - 恢复会话 - -**流程**: -``` -submit() - → 添加user消息 - → 调用provider.chat_completion() - → 处理assistant response - → 如果有tool_calls: - → 逐个执行tool (需要权限确认) - → 添加tool_result消息 - → 递归调用provider (续写) - → 完成 -``` - -**当前问题**: -- ⚠️ 内部无串行化保证,依赖外部调用约定 -- ⚠️ detach线程生命周期管理风险 - -#### Provider(LLM提供者) -**接口**: -```cpp -ChatCompletionResult chat_completion( - const std::vector& messages, - const std::vector& tools, - StreamCallback on_delta, - std::atomic& abort_requested -); -``` - -**实现**: -- `OpenAIProvider` - OpenAI兼容API -- `CopilotProvider` - GitHub Copilot - -**特性**: -- 流式输出支持 -- 取消支持 -- 自动token刷新(Copilot) - -#### Tool Executor -**职责**:执行工具调用并返回结果 - -**工具列表**: -| 工具 | 只读 | 说明 | -|------|------|------| -| bash | ❌ | 执行shell命令 | -| file_read | ✅ | 读取文件 | -| file_write | ❌ | 写入文件 | -| file_edit | ❌ | 编辑文件 | -| grep | ✅ | 正则搜索 | -| glob | ✅ | 文件查找 | - -**权限流程**: -``` -execute(tool_name, args) - → PathValidator.validate(path) - 路径验证 - → PermissionManager.should_auto_allow() - 权限决策 - → 如果需要确认: - → on_tool_confirm() 回调UI - → 等待用户决策 - → 执行工具 - → 返回ToolResult -``` - -**当前问题**: -- ⚠️ Deny和Prompt语义混淆 -- ⚠️ bash工具安全规则不足 - -#### Session Manager -**职责**:会话持久化 - -**功能**: -- 保存对话历史到磁盘 -- 按项目CWD分类存储 -- 恢复历史会话 -- 自动清理旧会话 - -**存储结构**: -``` -~/.acecode/ - ├── config.json - ├── copilot_token.json - └── projects/ - └── / - ├── session_.json - └── ... -``` - -#### Config Manager -**职责**:配置管理 - -**配置项**: -- provider选择 -- API endpoint/key -- model名称 -- 会话保留策略 - -### 3. Utils Layer(工具层) - -**新增工具类**(✨本次重构): -- `ToolArgsParser` - JSON参数解析 -- `ToolErrors` - 错误消息标准化 -- `FileOperations` - 文件操作封装 -- `constants` - 常量定义 - -**原有工具**: -- `Logger` - 日志 -- `PathValidator` - 路径验证 -- `encoding` - UTF-8转换 -- `uuid` - UUID生成 -- `diff_utils` - 差异生成 - -## 数据流 - -### 1. 用户提交请求 -``` -User Input - ↓ -TUI (Event) - ↓ -main.cpp (detach thread) ⚠️ - ↓ -AgentLoop.submit() - ↓ -Provider.chat_completion() - ↓ (streaming) -Callback → TUI update -``` - -### 2. 工具调用 -``` -Provider返回tool_calls - ↓ -AgentLoop遍历tool_calls - ↓ -ToolExecutor.execute() - ↓ -PathValidator.validate() - ↓ -PermissionManager.check() - ↓ (如需确认) -UI回调 → 用户选择 - ↓ -Tool实现 (bash/file_read/...) - ↓ -返回ToolResult - ↓ -添加tool_result消息 - ↓ -继续调用Provider (带tool_result) -``` - -## 并发模型 - -### 当前模型(有问题⚠️) - -``` -UI Thread - ├─ FTXUI事件循环 - ├─ 渲染 - └─ 收集输入 - ↓ - 创建detach线程执行submit() ⚠️ - -Detached Thread (每次submit一个) ⚠️ - ├─ AgentLoop.submit() - ├─ 调用Provider - ├─ 执行Tools - └─ 回调UI (PostEvent) - -Animation Thread (thinking) - └─ 定时更新动画状态 → PostEvent -``` - -**问题**: -- detach线程在程序退出时可能悬空 -- 多个线程PostEvent,顺序不确定 -- 状态访问需要频繁加锁 -- 取消机制不完善 - -### 建议模型(TODO) - -``` -UI Thread - ├─ FTXUI事件循环 - ├─ 渲染 - ├─ 处理events (来自worker) - └─ 发送任务到worker队列 - -Worker Thread (单例) - ├─ 从队列取任务 - ├─ 执行submit/cancel/resume - ├─ 所有AgentLoop状态访问在此线程 - └─ 通过PostEvent通知UI - - 所有状态修改串行化 ✅ - join即可安全退出 ✅ -``` - -## 配置与扩展点 - -### 添加新工具 - -1. 创建 `src/tool/my_tool.cpp`: -```cpp -#include "utils/tool_args_parser.hpp" -#include "utils/tool_errors.hpp" - -static ToolResult execute_my_tool(const std::string& args_json) { - ToolArgsParser parser(args_json); - if (parser.has_error()) { - return ToolResult{parser.error(), false}; - } - - std::string param = parser.get_or("param", ""); - if (param.empty()) { - return ToolResult{ToolErrors::missing_parameter("param"), false}; - } - - // 实现逻辑 - return ToolResult{result, true}; -} - -ToolImpl create_my_tool() { - // 定义schema - ToolDef def; - def.name = "my_tool"; - def.description = "..."; - def.parameters = {...}; - - return ToolImpl{def, execute_my_tool, /*is_read_only=*/true}; -} -``` - -2. 在 `tool_executor.cpp` 中注册: -```cpp -tools_.push_back(create_my_tool()); -``` - -### 添加新Provider - -1. 继承 `Provider` 接口 -2. 实现 `chat_completion()` 方法 -3. 在 `main.cpp` 中根据配置创建实例 - -### 添加新命令 - -1. 在 `builtin_commands.cpp` 中添加处理函数: -```cpp -static void cmd_my_command(CommandContext& ctx, const std::string& args) { - // 实现 -} -``` - -2. 注册到 `execute_builtin_command()`: -```cpp -commands["/mycommand"] = cmd_my_command; -``` - -## 安全模型 - -### 路径验证 -- 所有文件操作路径必须在CWD内 -- 使用 `std::filesystem::weakly_canonical` 规范化 -- 前缀匹配检查 - -### 权限管理 -- 只读工具默认auto-allow -- 写/执行工具默认prompt -- 支持自定义规则(allow/deny) -- `--dangerous` 模式跳过所有确认⚠️ - -### Mtime冲突检测 -- file_read时记录mtime -- file_write/edit前检查mtime -- 如果外部修改则拒绝写入 - -## 性能考虑 - -### Token管理 -- `/compact` 命令压缩历史 -- 自动检测PTL错误并重试 -- 估算:~4 chars per token - -### 输出限制 -- bash输出:100KB截断 -- file_read:10MB限制 -- grep结果:200条限制 -- glob结果:500条限制 - -### 缓存 -- Session按需加载 -- Copilot token缓存到磁盘 -- Mtime记录在内存 - -## 测试策略(TODO) - -### 单元测试 -- [ ] ToolArgsParser -- [ ] ToolErrors -- [ ] FileOperations -- [ ] PathValidator -- [ ] PermissionManager - -### 集成测试 -- [ ] AgentLoop基本流程 -- [ ] Tool执行 -- [ ] Session保存/恢复 - -### E2E测试 -- [ ] 完整对话流程 -- [ ] 工具调用确认 -- [ ] 取消机制 - -## 依赖关系 - -``` -acecode -├── ftxui (TUI framework) -├── nlohmann-json (JSON解析) -├── cpr (HTTP client) -│ └── libcurl (>= 8.14 for Windows TLS) -└── C++17 标准库 -``` - -## 部署 - -### 编译要求 -- CMake >= 3.20 -- C++17 compiler -- vcpkg +## Tools And Permissions + +`ToolExecutor` owns the authoritative tool registry. Built-ins include shell execution, file read/write/edit, grep, glob, task completion, structured user questions, skills, memory, optional web search, and MCP-provided tools. + +Permission behavior is centralized in [src/permissions.hpp](src/permissions.hpp): + +- `Default`: auto-allow read-only tools, prompt for writes and exec. +- `AcceptEdits`: auto-allow file writes/edits, still prompt for shell commands. +- `Yolo`: allow all tools. + +Memory writes are path-locked to the memory directory even when broader permissions are enabled. + +## Extension Points + +- Add a slash command under [src/commands/](src/commands), then register it in the command registry. +- Add a tool under [src/tool/](src/tool), return structured `ToolResult` metadata when useful, and register it where TUI/daemon tools are initialized. +- Add provider behavior under [src/provider/](src/provider), keeping model profile and context-resolution rules centralized. +- Add daemon routes under [src/web/handlers/](src/web/handlers) and register them in [src/web/server.cpp](src/web/server.cpp). +- Add frontend behavior under [web/src/](web/src); rebuild `web/dist/` before embedding. +- Add focused unit tests under [tests/](tests), mirroring source paths with `_test.cpp` file names. -### 运行要求 -- 交互式TTY -- 网络连接(调用LLM API) -- 对项目目录的读写权限 +## Build Targets + +| Target | Purpose | +| --- | --- | +| `acecode_testable` | Object library containing headless reusable logic for production binaries and tests. | +| `acecode` | Main terminal/daemon executable. | +| `acecode_unit_tests` | GoogleTest binary when `BUILD_TESTING=ON`. | +| `acecode-desktop` | Optional desktop shell when `ACECODE_BUILD_DESKTOP=ON`. | + +The CMake build embeds `web/dist/` into generated C++ asset data. If the web build is absent, a minimal fallback page is embedded so API builds still work. ---- +## Reference Docs -_架构版本: v0.1 (重构进行中)_ -_最后更新: 2025-01-XX_ +- [README.md](README.md) and [README_CN.md](README_CN.md): user-facing overview, setup, run modes, and build entry points. +- [AGENTS.md](AGENTS.md): repository rules for coding agents and contributors. +- [CLAUDE.md](CLAUDE.md): implementation memory for current subsystem notes. +- [docs/user-manual.md](docs/user-manual.md): user workflow details. +- [docs/daemon-api.md](docs/daemon-api.md): daemon HTTP/WebSocket protocol. +- [docs/model-context-resolution.md](docs/model-context-resolution.md): model and context-window behavior. +- [docs/skills.md](docs/skills.md) and [docs/skills-implementation.md](docs/skills-implementation.md): skills usage and implementation. +- [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md): desktop multi-workspace design. diff --git a/CLAUDE.md b/CLAUDE.md index fc57c2448..169b4b407 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,356 +1,113 @@ # CLAUDE.md -Guidance for Claude Code working in this repo. +Implementation memory for coding agents working in this repository. For user-facing setup and run modes, use [README.md](README.md). For stable structure, use [ARCHITECTURE.md](ARCHITECTURE.md). For contributor rules, use [AGENTS.md](AGENTS.md). -## Project Overview +## Current Runtime Surfaces -ACECode is a terminal AI coding agent in C++17. FTXUI-based TUI, multi-turn conversations with tool calling via OpenAI-compatible APIs or GitHub Copilot. Also ships a daemon mode that exposes the same agent loop over HTTP/WebSocket. +ACECode ships one main executable with terminal TUI and daemon subcommands, plus an optional desktop shell target. -## Build +- TUI mode starts from [main.cpp](main.cpp), configures provider/tools/commands, runs the FTXUI loop, and posts worker callbacks back into the UI event queue. +- Daemon mode starts from [src/daemon/cli.cpp](src/daemon/cli.cpp), converges on `worker.cpp::run_worker`, writes runtime files, then serves [src/web/](src/web) routes and WebSocket events. +- Web UI code lives in [web/src/](web/src), builds with React 18, Vite, Tailwind v4, `markdown-it`, `highlight.js`, and `diff2html`, then gets embedded from `web/dist/` by CMake. +- Desktop mode is opt-in through `-DACECODE_BUILD_DESKTOP=ON`; [src/desktop/](src/desktop) manages workspace registry, daemon pool, webview host, tray, notifications, and bridge calls. -**Prerequisites:** CMake >= 3.20, Ninja, vcpkg, C++17 compiler. +## Build And Verification Notes -```bash -git submodule update --init --recursive +Use the command set in [AGENTS.md](AGENTS.md) as the source of truth. Important local facts: -/vcpkg install cpr nlohmann-json ftxui \ - --triplet \ - --overlay-ports=$PWD/ports +- `acecode_testable` is the shared object library for headless logic and unit tests. +- `acecode` links `acecode_testable` plus FTXUI and TUI/markdown sources. +- `acecode_unit_tests` is available when `BUILD_TESTING=ON`. +- `acecode-desktop` is only created when desktop building is enabled. +- Rebuild [web/](web) with `pnpm build` before configuring CMake when embedded frontend assets need to change. +- Windows builds require libcurl 8.14 or newer for TLS behavior and use UTF-8 compile options. -cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ - -DVCPKG_TARGET_TRIPLET= \ - -DVCPKG_OVERLAY_PORTS=$PWD/ports +## Agent Loop And Tools -cmake --build build --config Release -``` +[src/agent_loop.cpp](src/agent_loop.cpp) is the multi-turn state machine. A text-only assistant reply ends the loop. `task_complete` is an optional explicit terminator that renders a concise completion row. `AskUserQuestion` is not a terminator; its answer returns as a tool result. `config.agent_loop.max_iterations` is the hard cap. -Output: `build/acecode`. On Windows, libcurl >= 8.14 is required for TLS. A custom FTXUI overlay port lives in `/ports/ftxui/`. +Core tools are registered for both TUI and daemon paths: `bash`, `file_read`, `file_write`, `file_edit`, `grep`, `glob`, `task_complete`, `AskUserQuestion`, skill tools, memory tools, optional `web_search`, and MCP tools. `ToolResult` can carry summaries and hunks so TUI/web resume can render useful compact rows instead of raw output folds. -**Tests:** `-DBUILD_TESTING=ON` (default), then -``` -cmake --build build --target acecode_unit_tests && ctest --test-dir build --output-on-failure -``` -Tests under `tests/` mirror `src/` layout, files end in `_test.cpp`. `tests/CMakeLists.txt` globs `*_test.cpp` automatically. The test binary links against `acecode_testable` (an OBJECT library covering every `src/*.cpp` **except** `src/tui/` and `src/markdown/`, which pull FTXUI). `main.cpp` is always excluded. +`bash_tool` streams cleaned output, polls abort state, truncates very large output, and supports POSIX `stdin_inputs`. File tools should preserve checkpoint hooks by calling `track_file_write_before` before mutating files. -## Running +## Sessions And Persistence -```bash -./build/acecode # Fresh session -./build/acecode --resume [id] # Resume a previous session -./build/acecode configure # Setup wizard -./build/acecode -dangerous # Bypass permission checks -``` +[src/session/](src/session) persists canonical conversation messages as JSONL with metadata sidecars. Runtime-only display fields are not serialized. Resume paths rebuild TUI pseudo-rows, tool previews, summaries, and diffs from persisted messages and metadata. -Config: `~/.acecode/config.json`. Sessions: `.acecode/projects//`. +Rewind support uses per-user-turn checkpoints. `SessionManager::track_file_write_before` is the hook file-mutating tools call so `/rewind` can restore file state. -## Architecture +Daemon session multiplexing uses `SessionRegistry`. Each session entry owns its own `SessionManager`, `PermissionManager`, `AgentLoop`, async permission prompter, and question prompter. `EventDispatcher` gives each emitted event a monotonic sequence number and keeps a bounded replay ring. -### Data Flow +## Skills, Memory, And Project Instructions -User input → TUI event handler → `AgentLoop` → `LlmProvider::chat_stream()` → SSE parse → streaming tokens back to TUI. Tool calls flow through `PermissionManager` → `ToolExecutor` → result appended to history → next LLM turn. +[src/skills/](src/skills) discovers `SKILL.md` files from configured global, project, and external skill directories. Skill metadata is read from YAML frontmatter at startup; full bodies are loaded lazily through skill invocation or the `skill_view` tool. -### Key Components +[src/memory/](src/memory) stores Markdown memory entries under `~/.acecode/memory/` and rewrites an index on upsert/remove. `memory_write` is constrained to that directory even under broad permission modes. -- **`main.cpp`** — CLI parsing, terminal setup, FTXUI rendering loop, event dispatch. The TUI layout and event wiring live here. +[src/project_instructions/](src/project_instructions) loads configured project-instruction filenames from the global config directory and then from the project hierarchy, outer-first, subject to per-file and aggregate byte caps. The repository root intentionally keeps only canonical docs; do not add duplicate root instruction files for this repository. -- **`src/agent_loop.{hpp,cpp}`** — Multi-turn state machine on a worker thread, callbacks back to TUI. **Termination protocol**: a text-only assistant reply ends the loop (default of hermes-agent and claudecodehaha). `task_complete` is the OPTIONAL explicit terminator that renders a "Done: " row. `AskUserQuestion` is **NOT** a terminator — its answer flows back as a `tool_result`. Hard cap: `config.agent_loop.max_iterations` (default 50). Esc emits `[Interrupted]` and short-circuits. +## Daemon And Web UI -- **`src/provider/`** — `LlmProvider` interface with `OpenAiCompatProvider` (OpenAI-compatible REST+SSE; supports DeepSeek/Qwen/OpenRouter reasoning via `delta.reasoning_content` and echoes it back on assistant messages — see `openspec/changes/support-deepseek-reasoning`) and `CopilotProvider` (GitHub device-flow OAuth). `ModelContextResolver` + `models_dev_registry` consult a bundled `models.dev/api.json` snapshot at `/../share/acecode/models_dev/`; network refresh is opt-in via `config.models_dev.allow_network` or `/models refresh --network`. +Daemon startup writes pid, port, guid, token, and heartbeat files under `/run/`. Runtime file writes are atomic where practical, and daemon tokens are owner-only on supported platforms. -- **`src/tool/`** — `ToolExecutor` registry with built-ins: `bash_tool`, `file_read_tool`, `file_write_tool`, `file_edit_tool`, `grep_tool`, `glob_tool`, `AskUserQuestion`, plus `web_search` (registered only when `cfg.web_search.enabled` is true; see `src/tool/web_search/`). `ToolResult` carries an optional `summary: ToolSummary` for the green/red one-line success/failure row; tools without `summary` fall back to the legacy 10-line fold. `bash_tool` streams cleaned output via `ctx.stream`, polls `ctx.abort_flag` every 10ms, head+tail-truncates output above 100KB, and accepts `stdin_inputs: string[]` (POSIX only). +[src/web/server.cpp](src/web/server.cpp) registers health, sessions, messages, skills, MCP, model, history, files, commands, fork, and static asset routes. WebSocket payloads use envelopes with `type`, `seq`, `timestamp_ms`, and `payload`. -- **`src/permissions.hpp`** — `PermissionManager` with three modes (`Default`, `AcceptEdits`, `Yolo`). Read-only tools auto-approve; write/exec prompts unless glob rules match. +Loopback requests bypass daemon token auth. Non-loopback requests require `X-ACECode-Token` or `?token=`, and non-loopback dangerous mode is rejected. Keep [docs/daemon-api.md](docs/daemon-api.md) in sync for protocol changes. -- **`src/session/`** — `SessionManager` persists conversation as JSONL + metadata sidecar, lazily created on first message. `session_replay.{hpp,cpp}` expands canonical OpenAI roles into TUI pseudo-rows on `--resume`. `tool_metadata_codec.{hpp,cpp}` rides on `ChatMessage.metadata` under reserved subkeys `tool_summary` / `tool_hunks` so resume restores green/red summaries and color diffs. Decoders are lenient — malformed JSON returns `nullopt` so legacy sessions degrade to the gray fold instead of crashing. `session_serializer` uses an explicit field allowlist; runtime-only fields (`expanded`, `display_override`) never persist. +The frontend has pure helpers under [web/src/lib/](web/src/lib) with Node-based tests. Prefer adding data-shaping logic there rather than embedding it directly in components. -- **Tool-result rendering (`main.cpp`)** — Successful summarized rows render as one green line (`icon verb · object · m1 · m2 …`). Failed rows add up to 3 dimmed lines of stderr. `Ctrl+E` toggles `expanded` to fall back to the 10-line fold. `tool_call` rows prefer `display_override` (recomputed via `ToolExecutor::build_tool_call_preview` on resume, never persisted) over the raw `[Tool: X] {JSON}` form. +## Desktop Shell -- **`src/tui/tool_progress.{hpp,cpp}`** — Live tool-progress renderer. While `state.tool_running`, replaces the thinking spinner with a 5-line tail + status block (tool name, command preview, elapsed seconds, cumulative bytes); the bottom status bar gets a compact `◑ bash 23s` chip. Pushed via `AgentLoop::on_tool_progress_*` callbacks; `main.cpp` throttles re-renders to ≥150ms. While waiting on the LLM, a `○ Thinking 14s ~82 tok` chip appears in the bottom bar (token segment after 3000ms; prefers exact `last_completion_tokens_authoritative`). +The desktop shell runs a webview against workspace-local daemon processes. It does not change daemon internals; each daemon still serves one current working directory. Workspace switching currently uses whole-page navigation so browser origin follows the active loopback port. -- **`src/tui/picker_scroll.hpp`** — Header-only viewport-scroll helper for keyboard-navigated overlays. Pure function `scroll_to_keep_visible(selected, prev_offset, visible_rows, total)` — no FTXUI dep, consumed by `acecode_testable`. Visible-row constants: `kResumePickerVisibleRows = 10`, `kRewindPickerVisibleRows = 10`, `kSlashDropdownVisibleRows = 8`. All three overlays support ArrowUp/Down + PgUp/PgDn + Home/End and render `↑ N more above` / `↓ M more below` indicators. +Key modules: -- **`src/tui/slash_dropdown.{hpp,cpp}`** — Autocomplete dropdown above the input while the buffer starts with `/` and contains no whitespace. Reads `CommandRegistry`, ranks by prefix > substring(name) > substring(description). Suppressed while resume picker, rewind picker, tool-confirmation, or AskUserQuestion overlay is active. +- `workspace_registry`: persisted workspace list and names. +- `daemon_pool`: per-workspace daemon process management. +- `web_host`: native webview wrapper and bridge binding. +- `tray_icon_win` and `notifications_win`: Windows tray and notification integration. -- **`src/tui_state.hpp`** — Central shared state: messages, input buffer, animation flags, overlays, tool-progress state, waiting-indicator state. Waiting fields reset on `on_busy_changed(true)` and are only meaningful while `is_waiting`. +Detailed behavior is in [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md). -- **`src/prompt/system_prompt.hpp`** — Builds dynamic system prompt from cwd info + tool registry descriptions. +## Network, Proxy, And Web Search -- **`src/markdown/`** — Lexer + formatter from markdown to ANSI escapes, with code-block syntax highlighting. +[src/network/proxy_resolver.*](src/network) centralizes proxy behavior for cpr call sites. `proxy_mode=auto` follows platform/system proxy settings; `off` forces direct; `manual` uses `proxy_url`. Startup can probe proxy reachability and temporarily fall back to direct if the configured proxy is unreachable. -- **`src/utils/`** — `logger.hpp` (file log, daemon mode rotates daily), `token_tracker.hpp`, `path_validator.hpp`, `encoding.hpp`, `uuid.hpp`, `stream_processing.hpp` (used by `bash_tool`: `strip_ansi`, `utf8_safe_boundary`, `feed_line_state`), `base64.hpp` (encoder only, used by OSC 52 clipboard), `paths.{hpp,cpp}` (process-level `RunMode` enum + `resolve_data_dir`: `User`→`~/.acecode/`, `Service`→`%PROGRAMDATA%\acecode\` / `/Library/Application Support/acecode/` / `/var/lib/acecode/`). +`/proxy` shows or changes the session-level effective proxy state without persisting changes. -- **`src/auth/github_auth.hpp`** — Copilot device-flow OAuth, token persistence + refresh. +[src/tool/web_search/](src/tool/web_search) provides optional HTML-backed search with backend auto-detection and fallback. `/websearch` shows status, refreshes region detection, or switches backend for the current session. If `config.web_search.enabled=false`, the tool is not registered. -- **`src/daemon/`** — Background daemon wrapping the agent loop in HTTP/WebSocket (`openspec/changes/add-web-daemon`). Three startup modes converge on `worker.cpp::run_worker(opts, cfg)`: `acecode daemon --foreground`, `acecode daemon start` (POSIX double-fork+setsid / Windows `DETACHED_PROCESS`), `acecode service install` + `start` (Windows SCM via `service_win.cpp`, runs as LocalSystem). Shared startup: load config → `validate_can_start` (GUID mutex) → write pid/port/guid/token to `/run/` → `HeartbeatWriter` (writes JSON every 2s, reads `timestamp_ms` not mtime) → `SessionRegistry` + `LocalSessionClient` + `WebServer` → block on `server.run()` until SIGTERM/CTRL_BREAK or `request_worker_termination()`. `runtime_files.cpp` does atomic `.tmp + rename`; token is owner-only (POSIX `chmod 0600` / Windows DACL). +## Model Profiles And Context Windows -- **`src/web/`** — Crow 1.3.2 HTTP/WebSocket server on `127.0.0.1:28080` (configurable via `web.port`; port-in-use is fail-fast, no fallback). Routes: `GET /api/health`, `GET/POST/DELETE /api/sessions`, `GET /api/sessions/:id/messages?since=N`, `GET /api/skills`, `GET/PUT /api/mcp` (PUT writes config without auto-reload), `WS /ws/sessions/:id`. WS envelope: `{type, seq, timestamp_ms, payload}`. `auth.cpp::require_auth` — loopback free pass, non-loopback requires `X-ACECode-Token` or `?token=`; `preflight_bind_check` rejects non-loopback without a token and any `-dangerous + non-loopback`. +Model resolution layers are: -- **`src/session/` (daemon multiplexing)** — `SessionRegistry` is `unordered_map>`; each entry owns its own `SessionManager` + `PermissionManager` + `AgentLoop` + `AsyncPrompter`. `SessionClient` is the abstract surface; `LocalSessionClient` is the same-process impl. `EventDispatcher` is the per-session pub/sub: monotonic seq, 1024-capacity ring buffer, atomic replay-then-register on subscribe so reconnect fills gaps without losing racy frames. `permission_prompter.cpp` indirects `AgentLoop`'s permission ask: `CallbackPrompter` (TUI synchronous modal) vs `AsyncPrompter` (daemon, emits `PermissionRequest`, blocks 5min on condvar with abort polling). Swap via `AgentLoop::set_permission_prompter`; absent injection preserves the legacy callback path (TUI is zero-changed by daemon work). +1. `default_model_name` from `saved_models`. +2. Per-project model override. +3. Resumed session provider/model metadata. +4. Legacy provider config fallback. -- **`src/skills/`** — User-authored `SKILL.md` discovered from `.acecode/skills`, `.agent/skills`, plus `skills.external_dirs`. `SkillRegistry` reads only YAML frontmatter at startup, lazy-loads body when invoked. Each skill registers as `/`; `skills_list` and `skill_view` tools expose them to the LLM. `/skills reload` rescans. +Context windows resolve through model profile data, bundled models.dev metadata, provider defaults, and configured fallbacks. The detailed rules live in [docs/model-context-resolution.md](docs/model-context-resolution.md). -- **`src/memory/`** — Cross-session user memory under `~/.acecode/memory/`. `MemoryRegistry` scans `.md` entries with `name`/`description`/`type` frontmatter (`type ∈ {user, feedback, project, reference}`), rewrites `MEMORY.md` index on every upsert/remove. Tools: `memory_read` (no args = full index; by type or name), `memory_write` (path-locked to memory dir even in Yolo mode; auto-approved). Commands: `/memory list|view|edit|forget|reload`, `/init` (LLM authors ACECODE.md via `file_write_tool`; static skeleton fallback when no provider configured). +## Config Notes -- **`src/history/`** — Per-cwd persistent input history (`/input_history.jsonl`, independent of any session JSONL). `record_history` lambda in `main.cpp` centralises space-only and adjacent-duplicate suppression for both Normal and Shell (`!`) modes. Append-first; head-truncates via `.tmp + rename` when over `config.input_history.max_entries`. Resilient load (missing → empty; malformed → skip-with-warning). `/history` lists oldest-first; `/history clear` wipes both memory and disk. +The config schema is intentionally sparse on write: defaults are omitted when possible. Notable sections are `saved_models`, `models_dev`, `skills`, `memory`, `project_instructions`, `agent_loop`, `daemon`, `web`, `network`, `web_search`, `tui`, `desktop`, and `mcp_servers`. -- **`src/project_instructions/`** — Loads `ACECODE.md` / `AGENT.md` / `CLAUDE.md` from `~/.acecode/` then walks HOME → cwd outer-first, picking at most one file per directory by `cfg.filenames` priority. Toggles `read_agent_md` / `read_claude_md` (no toggle for `ACECODE.md`). Caps: per-file `max_bytes`, aggregate `max_total_bytes`, walk-depth `max_depth`. Injected as `# Project Instructions` after tool descriptions. +`mcp_servers` without `transport` default to stdio. `sse` is the legacy two-endpoint protocol. `http` is Streamable HTTP, defaulting to `/mcp` when no endpoint is provided. -### Threading Model +## Useful Source Anchors -**TUI mode:** -1. **Agent worker** — `AgentLoop`, blocked on LLM HTTP streaming -2. **Auth thread** — Copilot device-flow polling (only when needed) -3. **Animation thread** — drives the thinking spinner +- [src/commands/builtin_commands.cpp](src/commands/builtin_commands.cpp): slash command registration and command help. +- [src/tool/tool_executor.cpp](src/tool/tool_executor.cpp): tool registry behavior, tool result message formatting, and compact call previews. +- [src/web/tool_event_payload.cpp](src/web/tool_event_payload.cpp): web serialization of tool progress and summaries. +- [src/web/message_payload.cpp](src/web/message_payload.cpp): REST/WS message payload identity and metadata. +- [src/session/session_serializer.cpp](src/session/session_serializer.cpp): persisted message field allowlist. +- [src/tui/render_mode.hpp](src/tui/render_mode.hpp): pure terminal render-mode decision logic. +- [src/utils/paths.cpp](src/utils/paths.cpp): user vs service data directory resolution. -Callbacks post events back into the FTXUI `ScreenInteractive` event queue. +## Maintenance Notes -**Daemon mode:** main thread blocks in `Crow::App::run()`. Plus: -1. **HeartbeatWriter** — every `heartbeat_interval_ms` (default 2000) -2. **Term watcher** — blocks on `g_term_cv`, calls `server.stop()` on signal -3. **AgentLoop workers** — one detached thread per turn per active session, emits via `EventDispatcher` -4. **Crow internal pool** — handles HTTP + WebSocket -5. **AsyncPrompter waiters** — AgentLoop blocks on a per-session condvar; unblock posted from the Crow handler thread processing `decision` - -### Mouse Input + Clipboard - -FTXUI mouse tracking is enabled by default — wheel scrolls `chat_focus_index` by ±1 (same `scroll_chat()` helper as ArrowUp/Down). Some terminals intercept native click-and-drag selection; Shift+drag is the universal bypass. FTXUI's drag-select drives a built-in selection styled blue/white; right-click writes OSC 52 (`ESC ] 52 ; c ; ESC \`) using `src/utils/base64.hpp`, then flashes `Copied N bytes to clipboard` (auto-cleared ~2s via `status_line_clear_at`). Inside tmux, set `set -g set-clipboard on` or the OSC 52 is consumed. - -### Legacy terminal fallback - -FTXUI's default `TerminalOutput()` mode emits `\033[1A` per frame to rewind. This breaks on Win10 < 1809 conhost and Cmder/ConEmu pty wrappers (banner stacking, viewport drift). Workaround: alt-screen (`\033[?1049h`). - -`detect_terminal_capabilities()` reads `ConEmuPID` / `WT_SESSION` env + Windows build via `RtlGetVersion`. `decide_render_mode(cfg.tui, caps)` is a pure function in `src/tui/render_mode.hpp` (no FTXUI dep, in `acecode_testable`). `make_screen_interactive` is the only FTXUI consumer. - -| `tui.alt_screen_mode` | caps | decision | -|---|---|---| -| `"always"` | any | AltScreen | -| `"never"` | any | TerminalOutput | -| `"auto"` | `is_windows_terminal=true` | TerminalOutput (short-circuit) | -| `"auto"` | `is_conemu` or `is_legacy_conhost` | AltScreen | -| `"auto"` | empty | TerminalOutput | - -CLI `--alt-screen` / `-alt-screen` forces alt-screen for this launch (no config write). One-time hint shown via `state.conversation` (not LLM context, not session JSONL) when auto-detect triggers AltScreen; idempotency tracked in `~/.acecode/state.json` `legacy_terminal_hint_shown`. Explicit choices (`always`, CLI flag) suppress the hint. - -### Web UI (browser front-end) - -`acecode daemon start` → `http://localhost:28080/` 浏览器界面。前端代码在 `web/`(顶层),build 期通过 `cmake/acecode_embed_assets.cmake` 扫描整个目录,把每个文件 hex 编码后写到 `${CMAKE_BINARY_DIR}/generated/static_assets_data.cpp`(单文件,字节数组 + `embedded_asset_map()` static map),链入 `acecode_testable` 与 `acecode` 二进制 — daemon 自带前端,无外部 CDN / npm 依赖。`web.static_dir` 为非空时改走 `FileSystemAssetSource`(开发模式,改文件即生效)。 - -### Web UI: 前端目录 - -栈是 **React 18 + Vite 5 + Tailwind v4**(*不是* 原生 Web Components,旧版 CLAUDE.md 描述已过时)。`vite-plugin-singlefile` 把所有 JS/CSS 内联到单个 `dist/index.html`,然后被嵌入资源管线吃掉。`pnpm install && pnpm build` 是必经一步(CI 与本地都跑)。 - -``` -web/ -├── index.html / vite.config.js / package.json -├── src/ -│ ├── App.jsx / main.jsx / theme.jsx -│ ├── components/ ← 22 个 React 组件(ChatView/Sidebar/Message/ToolBlock/SidePanel/...) -│ ├── lib/ ← api / connection / markdown / format / sessionTitle / auth / diff / lang / sessionChanges / usePreference -│ └── styles/globals.css ← Tailwind v4 entry + 自定义 CSS variables(亮/暗双主题) -├── public/vs-icons/ ← 单色 SVG 图标库 -└── pnpm-lock.yaml -``` - -依赖:`react@18` + `markdown-it@14`(GFM 表格/任务清单/嵌套 list)+ `markdown-it-task-lists` + `highlight.js@11`(core + 12 种语言:c/cpp/js/ts/python/bash/json/diff/markdown/rust/go/yaml,语言别名 js→javascript 等在 `lib/markdown.js` 内 normalize)+ `diff2html@3`(line-by-line 模式渲染 file_edit/file_write 的 hunks)。bundle 体积 ~461KB(gzip ~156KB),嵌入二进制约 +600KB。 - -`lib/markdown.js` 收紧 URL scheme 白名单(只放 `http(s)` / `mailto:` / `/` / `./` / `../` / `#`),关闭 raw HTML(`html: false`),外链自动 `target=_blank rel=noreferrer`。`renderMarkdown(src) -> string` 签名稳定。 - -`lib/usePreference.js` 是 UI 偏好读写的统一入口(`usePreference(key, defaults, validator?)` 返回 `[value, setValue]`,setter 接受 partial 浅合并 / 函数式更新,localStorage 写失败静默吞)。当前订阅:`ace.theme`(由 `theme.jsx::ThemeProvider`)、`acecode.singleLayoutWidths.v1`(由 `App.jsx`,`{sidebar, sidePanel}` 像素宽)、`acecode.uiPrefs.v1`(由 `App.jsx`,`{view: 'single'|'grid4'|'grid9', sidePanelCollapsed: boolean}` — view 模式与 SidePanel 折叠态跨刷新持久化)。同一 key 在整个 App 内只允许一个 hook 实例订阅,避免多 useState 之间互覆盖。 - -SidePanel 折叠 UI:`ChatView` 把 `SidePanel` 包到 `
`,折叠态宽度归 0 + opacity 过渡 200ms,SidePanel 仍 mount(tab/cache/preview 内部 state 保留)。SidePanel tab 行右端有 `.ace-side-panel-collapse-btn`(展开态),折叠态时 ChatView 顶部 header 内显示 `.ace-side-panel-expand-fab` 让用户重新展开。 - -全局会话搜索面板(`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=`(App.jsx mount 时解析并 `replaceState` 抹掉 query),无 bridge 时降级直接 setActiveRef。数据 60s TTL 缓存,`session_status` / `session_status_snapshot` / `mark_session_read_ack` 任一 WS 帧到达即 invalidate。**后端零路由变更**。 - -排队卡片栈(`redesign-webui-queue-cards`):busy 期间提交的待发送消息**不进 transcript**,改由 ``(在 `` 上方)渲染成卡片堆。状态机(`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 气泡一定是后端真实落库的消息。 - -### Web UI: HTTP / WS 协议增量 - -`add-web-chat-ui` change 在 `add-web-daemon` 的 14 条 Requirement 之上扩 9 项;`enhance-webui-chat-rendering` 又扩 1 个端点 + 协议字段;`add-webui-side-panel` 加 2 个文件浏览端点: - -| 端点 / 消息 | 用途 | -|---|---| -| WS `question_request` / `question_answer` | AskUserQuestion 工具的双向异步通道(`AskUserQuestionPrompter` + 5min 超时 + abort_flag 50ms 轮询,模式同 `AsyncPrompter`) | -| `tool_start` / `tool_update` / `tool_end` payload 字段扩充 | `display_override`(`ToolExecutor::build_tool_call_preview`) / `is_task_complete` / `tail_lines:[5 lines]` / `current_partial` / `total_lines` / `total_bytes` / `elapsed_seconds` / `summary{icon,verb,object,metrics}` / `success` / `output`(失败前 N 行) / `hunks[]`(file_edit/file_write 的 diff,前端走 diff2html 渲染) — 实现:`src/web/tool_event_payload.{hpp,cpp}` 把序列化收口 | -| `message` payload(WS + REST `GET /api/sessions/:id/messages`) 扩 `id` 字段 | user 消息走持久化 UUID(`ensure_user_message_identity`);assistant/system/tool 走 lazy `sha1(role + " " + content + " " + timestamp)` 小写 hex(实现:`src/web/message_payload.{hpp,cpp}` + `src/utils/sha1.hpp`)。前端用这个 id 做 fork | -| `POST /api/sessions/:id/fork` body `{at_message_id, title?}` → `{session_id, title, forked_from, fork_message_id}` | 把 source session 截止到 at_message_id(含此条)的前缀复制到新 session;源不动;新 session 不自动启 turn。命名 `分叉:<原标题>`(N=同源 sibling+1,原标题截 50 codepoint)。继承 cwd/provider/model,**不**继承 file_checkpoints。实现:`src/web/handlers/fork_handler.{hpp,cpp}`(纯函数 compute_fork_title + find_message_index_by_id) + `SessionManager::fork_session_to_new_id` | -| `GET /api/models` / `POST /api/sessions/:id/model` | 模型下拉:`saved_models` + 合成 `(legacy)` 行;v1 切的是 daemon 全局 provider(所有 session 共享一个),由 `swap_provider_if_needed` 实施 | -| `GET /api/history?cwd=&max=` / `POST /api/history` | per-cwd 输入历史,与 TUI 共享同一份 `/input_history.jsonl`,经 `InputHistoryStore::append` atomic rename | -| `PUT /api/skills/:name` body `{enabled}` / `GET /api/skills/:name/body` | 启停切换 + 查看 SKILL.md;PUT 写 `cfg.skills.disabled` 数组并 `save_config` + `SkillRegistry::reload` | -| `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` 透出 | -| `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 覆盖路径越权 / 噪音过滤 / 排序 / 二进制嗅探) | -| `GET /api/commands?workspace=` → `{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 决策);`/ args` 由下面新加的 expander 真展开。实现:`src/web/handlers/commands_handler.{hpp,cpp}`(纯函数 `build_commands_payload` + gtest case)| -| `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 / skill] + Description + Use skill_view(name=...) to load full SKILL.md + User's request: `。**不**注入 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 | - -`SessionMeta` 增加 `forked_from` / `fork_message_id` 字段(空时省略,老 meta 文件向后兼容)。Web 上每条消息 hover 浮出 `[复制] [分叉]` actions(codex 风格);分叉成功后立刻切到新 session(同 sidebar)。 - -handler 实现在 `src/web/handlers/{fork,models,history,skills,files}_handler.{hpp,cpp}`(纯函数,有 unit test),路由注册在 `src/web/server.cpp`。`/static/<...>` + `/` + SPA fallback 用 `CROW_CATCHALL_ROUTE` 一举处理(Crow 1.3.2 的 `` 模板路由有兼容性问题)。 - -**SidePanel 「变更」tab limitation**:前端纯聚合 messages 中 `tool_end.hunks`,只能反映 `file_edit` / `file_write` 工具的改动。用户授权 agent 用 `bash sed`/`awk`/`git checkout` 改的文件抓不到 — 后续 follow-up 思路:让 `bash_tool` 在执行前后 diff 一遍 cwd 把 hunks 倒灌回 `ToolResult.metadata.tool_hunks`(待评估 git/non-git 区分)。 - -### Desktop shell + multi-workspace - -`acecode-desktop.exe`(`-DACECODE_BUILD_DESKTOP=ON` 编出)是个 webview/webview 壳,默认管 N 个 daemon 子进程 — 每 workspace 一个独立 daemon(独立 loopback 端口 + 独立 token + 独立 Job Object)。daemon 内部代码零改动:每个 daemon 进程仍只服务自己的 `current_path()`,通过 `lpCurrentDirectory` 在 `CreateProcess` 时落地。 - -关键模块(全在 `src/desktop/`): -- `workspace_registry.{hpp,cpp}` — 扫 `.acecode/projects//workspace.json`(`{cwd, name}`),默认命名 = `fs::path(cwd).filename()`,行内重命名走 `set_name` 原子写。 -- `daemon_pool.{hpp,cpp}` — `unordered_map`,per-key condvar 串行化同 hash 并发 activate;`stop_all` best-effort。`IDaemonSupervisor` 虚基类便于单测 mock。 -- `pick_active.{hpp,cpp}` — 启动选 active workspace 的纯函数:`state.json::last_active_workspace_hash` → process cwd 的 hash → registry 第一项 → 空。 -- `folder_picker_win.cpp` — `IFileOpenDialog` + `FOS_PICKFOLDERS`,COM STA。 -- `web_host.{hpp,cpp}` — webview 包装,暴露 `bind/eval/init_script/native_window`,debug=true 默认开 F12 DevTools。 -- `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)。 -- `main.cpp::wWinMain` — 串起所有,quit 时写 `last_active_workspace_hash` + `pool.stop_all()` + `shutdown_notifications()` + `shutdown_tray_icon()`。 - -JS↔C++ bridge(同进程,webview `bind`,无 HTTP): -- `aceDesktop_listWorkspaces()` → `[{hash, cwd, name, daemon_state, active, port?, token?}]` -- `aceDesktop_activateWorkspace(hash)` → `{port, token}` 或 `{error}` -- `aceDesktop_renameWorkspace(hash, name)` → `{ok}` 或 `{error}` -- `aceDesktop_addWorkspace()` → `{hash, cwd, name}` 或 `null`(取消) -- `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 端不二次过滤。 -- `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)。 - -桌面通知抑制规则(`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。 - -切换 workspace 走**整页 navigate**(对设计稿 D6"无重载切换"的合理偏离):跨 loopback 端口 fetch 受 CORS 拦截,整页 navigate 让 origin 跟着切。代价是滚动 / 弹窗状态丢失,但 input box 清空 / 消息列表替换符合 spec。后续若加 `Access-Control-Allow-Origin: http://127.0.0.1:*` 可恢复无重载。 - -设计文档:`docs/desktop-shell/design.md` + `docs/desktop-shell/multi-workspace.md`,change:`openspec/changes/add-desktop-multi-workspace/`。 - -### Proxy / 抓包 - -`src/network/proxy_resolver.{hpp,cpp}` (with platform-specific `_posix` / `_win` bodies) injects proxy options into all cpr call sites uniformly. Design: `openspec/changes/respect-system-proxy`. - -`config.network.proxy_mode`: -- `"auto"` (default) — Windows: `WinHttpGetIEProxyConfigForCurrentUser` → HKCU registry → env. POSIX: env (`HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY`/`NO_PROXY`, lowercase variants). -- `"off"` — force direct, ignore system/env. -- `"manual"` — use `proxy_url` (auto-prefixes `http://` if missing). - -Both `main.cpp` and `daemon/worker.cpp` call `proxy_resolver().init(cfg.network)` and print `Proxy: ()` (credentials masked). `proxy_insecure_skip_verify=true` adds a red `[INSECURE]` banner and disables `cpr::ssl::VerifyPeer/VerifyHost` only on proxied requests; direct requests stay strict. Safer alternative: `proxy_ca_bundle` PEM path via `cpr::ssl::CaInfo` for trusting Fiddler/Charles roots. - -`/proxy` slash command: bare = show effective state; `refresh` = reprobe; `off` / `set ` / `reset` = session-level override (not persisted). - -`ProxyResolver::effective(target_url)` priority: session override > `mode=off` > manual `proxy_url` > auto platform path > direct. NO_PROXY filters host (suffix `.foo.com`, bare suffix, `*` wildcard, case-insensitive). SOCKS5 via `manual proxy_url = "socks5://..."` (libcurl native). - -**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"}}`. - -**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: from unreachable)`,所有 cpr 走直连。`/proxy` 输出新增 `Reachable : yes/no ()`,fallback 时多一行 `Original proxy : ()`。`/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 — 用户显式意志不被二次猜测。 - -### Web Search - -`src/tool/web_search/` 提供 LLM 可调的 `web_search(query, limit)` 工具,默认零配置:启动时 HEAD 探一次 `duckduckgo.com`(2s 超时,过 ProxyResolver),通 → 选 DuckDuckGo backend(海外/挂梯子)、不通 → 选 必应中国 backend(国内裸连)。探测结果缓存到 `~/.acecode/state.json` 的 `web_search.region_detected`,永不自动过期。 - -两个 backend 都是 HTML 爬取,零外部 API key。`backend_router.cpp::search_with_fallback` 在当前 backend 出 Network 错误时自动试对侧并切 active(同时更新缓存的 region 推断),Parse / RateLimited / Disabled 直接返回不 fallback。`HttpFetchFn` / `RegionProbeFn` 注入式设计让 unit test 不打真实网络。 - -`/websearch` slash command: -- `/websearch` — 显示 Active backend / Config backend / Region (+ detected_at) / Registered -- `/websearch refresh` — 失效缓存 + 立即重测,输出 before/after -- `/websearch use ` — 本会话临时切(只接受 `duckduckgo` / `bing_cn`,`bochaai` / `tavily` 是后续 PR 占位) -- `/websearch reset` — 回到 cfg + 缓存推导的 backend - -`config.web_search` 可选字段:`enabled`(默认 true)、`backend`(`auto` / `duckduckgo` / `bing_cn` / `bochaai` / `tavily`,后两个本期未实现)、`api_key`(future)、`max_results`(1..10,默认 5)、`timeout_ms`(1000..30000,默认 8000)。`enabled = false` 时工具完全不注册到 ToolExecutor,LLM 看不到。Bing CN 的 `bing.com/ck/a?u=a1` 跳转链会自动 base64 解码到真实 URL,decode 失败或非 http(s) 静默回退到原 href。Daemon 与 TUI 共享同一份 state.json 缓存。 - -### Config Schema - -`~/.acecode/config.json`: -```json -{ - "provider": "copilot", - "openai": { - "base_url": "http://localhost:1234/v1", - "api_key": "", - "model": "local-model", - "models_dev_provider_id": "openrouter" - }, - "copilot": { "model": "gpt-4o" }, - "context_window": 128000, - "max_sessions": 50, - "skills": { "disabled": [], "external_dirs": [] }, - "memory": { "enabled": true, "max_index_bytes": 32768 }, - "project_instructions": { - "enabled": true, - "max_depth": 8, - "max_bytes": 262144, - "max_total_bytes": 1048576, - "filenames": ["ACECODE.md", "AGENT.md", "CLAUDE.md"], - "read_agent_md": true, - "read_claude_md": true - }, - "models_dev": { - "allow_network": false, - "user_override_path": null, - "refresh_on_command_only": true - }, - "input_history": { "enabled": true, "max_entries": 10 }, - "agent_loop": { "max_iterations": 50 }, - "daemon": { - "auto_start_on_double_click": false, - "service_name": "ACECodeDaemon", - "heartbeat_interval_ms": 2000, - "heartbeat_timeout_ms": 15000 - }, - "web": { "enabled": true, "bind": "127.0.0.1", "port": 28080, "static_dir": "" }, - "network": { - "proxy_mode": "auto", - "proxy_url": "", - "proxy_no_proxy": "", - "proxy_ca_bundle": "", - "proxy_insecure_skip_verify": false, - "proxy_probe_enabled": true, - "proxy_probe_timeout_ms": 1500 - }, - "web_search": { - "enabled": true, - "backend": "auto", - "api_key": "", - "max_results": 5, - "timeout_ms": 8000 - }, - "tui": { "alt_screen_mode": "auto" }, - "desktop": { - "notifications": { - "enabled": true, - "on_question": true, - "on_completion": true, - "suppress_when_focused": true - } - }, - "saved_models": [ - { "name": "local-lm", "provider": "openai", "base_url": "http://localhost:1234/v1", "api_key": "x", "model": "llama-3" }, - { "name": "copilot-fast", "provider": "copilot", "model": "gpt-4o" } - ], - "default_model_name": "copilot-fast", - "mcp_servers": { - "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"] }, - "remote-sse": { "transport": "sse", "url": "https://mcp.example.com", "sse_endpoint": "/sse" }, - "remote-http": { "transport": "http", "url": "https://mcp.example.com", "sse_endpoint": "/mcp" } - } -} -``` - -`mcp_servers` entries without `transport` default to `stdio`. `sse` = legacy two-endpoint protocol; `http` = 2025-03-26 Streamable HTTP single-endpoint (default `/mcp`). - -`saved_models` is a named registry; `default_model_name` points into it. Both optional — empty falls back to legacy `provider` / `openai.*` / `copilot.*`. Each entry needs `name` (must NOT start with `(` — reserved for synthesized `(legacy)` / `(session:)`), `provider`, `model`. OpenAI entries also need `base_url` + `api_key`. `load_config` rejects on duplicate names, reserved prefixes, missing fields, or dangling `default_model_name`. - -### Model profile resolution - -At startup and on every `--resume` / `/resume`, `src/provider/model_resolver.cpp` resolves the effective `ModelEntry` in three layers: -1. `cfg.default_model_name` if found in `saved_models` -2. `/model_override.json` if present -3. Resumed `SessionMeta.provider` + `SessionMeta.model` (resume only) — matched by tuple, not name. No match builds an ad-hoc `(session:)` entry borrowing from `cfg.openai`, plus a `⚠ Resumed with ad-hoc model entry` system message. - -All-empty falls back to `synth_legacy_entry(cfg)` named `(legacy)`. The picker always shows it. - -`LlmProvider` is a `shared_ptr` under `provider_mu`; `AgentLoop` gets a `ProviderAccessor` lambda that snapshots the pointer at turn start, so an in-flight `chat_stream` can never dangle when the main thread swaps providers. `provider_swap.cpp::swap_provider_if_needed` reuses same-provider instances (`set_model` + `OpenAiCompatProvider::reconfigure`); cross-provider calls `create_provider_from_entry` and recomputes the context window via `resolve_model_context_window`. - -### `/model` command - -- `/model` — text picker over `saved_models` + `(legacy)`, current row marked `*` -- `/model ` — in-memory switch -- `/model --cwd ` — switch + persist to `/model_override.json` -- `/model --default ` — switch + persist to `config.json` `default_model_name` - -Unknown name → error, no state change. All persisting paths run under `provider_mu` and recompute `context_window`. - -## CI / Release - -`.github/workflows/package.yml` builds Linux x64/arm64, Windows x64, macOS x64/arm64. Releases auto-cut on `v*` tags. +- Keep root documentation role-based and short. Move detailed subsystem behavior into focused files under [docs/](docs). +- Avoid historical progress reports in root docs. Use OpenSpec changes and issue tracking for active work. +- When changing daemon routes, update protocol docs and add handler-level tests where possible. +- When changing frontend state-shaping behavior, add or update tests under [web/src/lib/](web/src/lib). +- When adding file-mutating tools, wire checkpoint tracking before the write so rewind remains reliable. diff --git a/DOCS_INDEX.md b/DOCS_INDEX.md deleted file mode 100644 index be6f54a8b..000000000 --- a/DOCS_INDEX.md +++ /dev/null @@ -1,185 +0,0 @@ -# AceCode 文档索引 - -欢迎查阅AceCode项目文档!本文档索引帮助你快速找到所需信息。 - -## 📖 文档分类 - -### 🚀 新手入门 -- **[README.md](README.md)** - 项目介绍、快速开始、功能说明(用户向) -- **[README_CN.md](README_CN.md)** - 中文版README -- **[QUICK_REFERENCE.md](QUICK_REFERENCE.md)** - 开发者快速参考手册 - -### 🏗️ 架构与设计 -- **[ARCHITECTURE.md](ARCHITECTURE.md)** - 系统架构、模块说明、数据流 -- **[PROJECT_ORGANIZATION.md](PROJECT_ORGANIZATION.md)** - 项目整理成果总结 - -### 📋 任务与进度 -- **[TODO.md](TODO.md)** - 详细待办清单(按优先级) -- **[REFACTORING_SUMMARY.md](REFACTORING_SUMMARY.md)** - 重构进度跟踪 -- **[REFACTORING_COMPLETE.md](REFACTORING_COMPLETE.md)** - 第一阶段整理报告 - -### 🛠️ 开发工具 -- **[scripts/code_quality_check.bat](scripts/code_quality_check.bat)** - Windows代码质量检查 -- **[scripts/code_quality_check.sh](scripts/code_quality_check.sh)** - Linux/Mac代码质量检查 -- **[.editorconfig](.editorconfig)** - 编辑器配置 - ---- - -## 🎯 按场景查找 - -### 我想了解项目是什么 -→ [README.md](README.md) - -### 我想开始开发 -→ [QUICK_REFERENCE.md](QUICK_REFERENCE.md) → [ARCHITECTURE.md](ARCHITECTURE.md) - -### 我想了解代码结构 -→ [ARCHITECTURE.md](ARCHITECTURE.md) → [QUICK_REFERENCE.md](QUICK_REFERENCE.md#-项目结构) - -### 我想查看待办任务 -→ [TODO.md](TODO.md) - -### 我想了解重构进度 -→ [REFACTORING_SUMMARY.md](REFACTORING_SUMMARY.md) → [REFACTORING_COMPLETE.md](REFACTORING_COMPLETE.md) - -### 我想添加新功能 -→ [QUICK_REFERENCE.md](QUICK_REFERENCE.md#-常用命令) → [ARCHITECTURE.md](ARCHITECTURE.md#配置与扩展点) - -### 我想检查代码质量 -→ 运行 `scripts/code_quality_check.bat` 或 `.sh` - -### 我想理解工具类用法 -→ [QUICK_REFERENCE.md](QUICK_REFERENCE.md#-常见工具类用法) - ---- - -## 📚 按角色推荐 - -### 👤 用户 -1. [README.md](README.md) - 了解功能和使用方法 -2. [README_CN.md](README_CN.md) - 中文文档 - -### 👨‍💻 新入职开发者 -1. [README.md](README.md) - 了解项目 -2. [QUICK_REFERENCE.md](QUICK_REFERENCE.md) - 快速上手 -3. [ARCHITECTURE.md](ARCHITECTURE.md) - 理解架构 -4. [TODO.md](TODO.md) - 查看任务 - -### 🔧 核心开发者 -1. [ARCHITECTURE.md](ARCHITECTURE.md) - 深入理解设计 -2. [TODO.md](TODO.md) - 计划工作 -3. [REFACTORING_SUMMARY.md](REFACTORING_SUMMARY.md) - 跟踪进度 -4. [QUICK_REFERENCE.md](QUICK_REFERENCE.md) - 日常参考 - -### 📊 项目管理者 -1. [PROJECT_ORGANIZATION.md](PROJECT_ORGANIZATION.md) - 整体状况 -2. [TODO.md](TODO.md) - 任务清单 -3. [REFACTORING_COMPLETE.md](REFACTORING_COMPLETE.md) - 阶段报告 - ---- - -## 🔍 按内容查找 - -### 功能说明 -- 用户功能 → [README.md](README.md#features) -- Slash命令 → [README.md](README.md#slash-commands-in-the-tui) -- 内置工具 → [README.md](README.md#built-in-tools) - -### 使用指南 -- 快速开始 → [README.md](README.md#quick-start) -- 命令行参数 → [README.md](README.md#command-line-flags) -- 配置说明 → [README.md](README.md#configuration) - -### 开发指南 -- 编译构建 → [README.md](README.md#how-to-build) + [QUICK_REFERENCE.md](QUICK_REFERENCE.md#-快速开始) -- 添加工具 → [ARCHITECTURE.md](ARCHITECTURE.md#添加新工具) -- 代码规范 → [QUICK_REFERENCE.md](QUICK_REFERENCE.md#-代码规范) -- 工具类使用 → [QUICK_REFERENCE.md](QUICK_REFERENCE.md#-常见工具类用法) - -### 架构设计 -- 总体架构 → [ARCHITECTURE.md](ARCHITECTURE.md#总体架构) -- 模块说明 → [ARCHITECTURE.md](ARCHITECTURE.md#模块说明) -- 数据流 → [ARCHITECTURE.md](ARCHITECTURE.md#数据流) -- 并发模型 → [ARCHITECTURE.md](ARCHITECTURE.md#并发模型) - -### 任务规划 -- 待办清单 → [TODO.md](TODO.md) -- 已知问题 → [TODO.md](TODO.md#-已知问题) -- 重构进度 → [REFACTORING_SUMMARY.md](REFACTORING_SUMMARY.md) -- 优先级 → [TODO.md](TODO.md#-高优先级立即处理) - -### 质量保障 -- 代码质量检查 → `scripts/code_quality_check.*` -- DRY违规 → [REFACTORING_COMPLETE.md](REFACTORING_COMPLETE.md#-改进效果量化) -- 最佳实践 → [REFACTORING_COMPLETE.md](REFACTORING_COMPLETE.md#-最佳实践总结) - ---- - -## 📝 文档维护 - -### 新增功能时 -- 更新 [README.md](README.md) 用户功能说明 -- 更新 [ARCHITECTURE.md](ARCHITECTURE.md) 如有架构变更 -- 更新 [QUICK_REFERENCE.md](QUICK_REFERENCE.md) 如有新API - -### 完成任务时 -- 在 [TODO.md](TODO.md) 中标记完成 -- 更新 [REFACTORING_SUMMARY.md](REFACTORING_SUMMARY.md) 进度 - -### 发现问题时 -- 添加到 [TODO.md](TODO.md) 已知问题 - -### 重构完成时 -- 更新 [REFACTORING_SUMMARY.md](REFACTORING_SUMMARY.md) -- 考虑创建新的阶段报告 - ---- - -## 🔗 快速链接 - -### 外部资源 -- [GitHub仓库](https://github.com/shaohaozhi286/acecode) -- [Issue追踪](https://github.com/shaohaozhi286/acecode/issues) -- [发布页面](https://github.com/shaohaozhi286/acecode/releases) - -### 依赖文档 -- [FTXUI](https://github.com/ArthurSonzogni/FTXUI) -- [nlohmann-json](https://github.com/nlohmann/json) -- [cpr](https://github.com/libcpr/cpr) - ---- - -## 📊 文档统计 - -| 文档类型 | 数量 | 总字数 | -|----------|------|--------| -| 用户文档 | 2个 | ~5000字 | -| 开发文档 | 5个 | ~13000字 | -| 工具脚本 | 2个 | - | -| 配置文件 | 1个 | - | -| **总计** | **10个** | **~18000字** | - ---- - -## 💡 使用建议 - -1. **首次接触项目** - 按顺序阅读:README → QUICK_REFERENCE → ARCHITECTURE -2. **日常开发** - 主要参考:QUICK_REFERENCE + TODO -3. **深入研究** - 仔细阅读:ARCHITECTURE + REFACTORING_SUMMARY -4. **定期检查** - 运行质量检查脚本,查看TODO进展 - ---- - -## 📞 获取帮助 - -- 查不到内容?→ 使用Ctrl+F全局搜索关键词 -- 文档有错?→ 提交Issue或PR -- 需要新文档?→ 在TODO.md中提出需求 - ---- - -_文档索引 v1.0_ -_最后更新: 2025-01-XX_ -_维护者: AceCode Team_ - -**🎯 温馨提示**: 收藏本文档,作为项目文档导航入口! diff --git a/PROJECT_ORGANIZATION.md b/PROJECT_ORGANIZATION.md deleted file mode 100644 index c7d564403..000000000 --- a/PROJECT_ORGANIZATION.md +++ /dev/null @@ -1,237 +0,0 @@ -# AceCode 项目整理完成清单 - -## ✅ 已完成的工作 - -### 1. 创建通用工具类(消除DRY违规) - -#### 新增文件: -- ✅ `src/utils/tool_args_parser.hpp` - 统一JSON参数解析 -- ✅ `src/utils/tool_errors.hpp` - 标准化错误消息 -- ✅ `src/utils/file_operations.hpp` - 通用文件操作 -- ✅ `src/utils/constants.hpp` - 集中常量定义 - -**收益**: -- 消除了6处重复的JSON解析try-catch块 -- 消除了15+处硬编码错误消息 -- 统一了文件操作逻辑(exists检查、size检查、mtime检查) -- 为后续工具开发提供了可复用基础设施 - -### 2. 重构文件工具 - -#### 已重构: -- ✅ `src/tool/file_read_tool.cpp` (-22行, -20%) -- ✅ `src/tool/file_write_tool.cpp` (-13行, -13%) -- ✅ `src/tool/file_edit_tool.cpp` (-11行, -9%) - -**收益**: -- 代码更简洁清晰 -- 错误处理更一致 -- 更易维护和扩展 - -### 3. 创建项目文档 - -#### 新增文档: -- ✅ `REFACTORING_SUMMARY.md` - 重构总结和进度跟踪 -- ✅ `TODO.md` - 详细的待办事项清单 -- ✅ `PROJECT_ORGANIZATION.md` - 本文档 - -#### 新增工具脚本: -- ✅ `scripts/code_quality_check.bat` - Windows代码质量检查 -- ✅ `scripts/code_quality_check.sh` - Linux/macOS代码质量检查 - -### 4. 编译验证 - -- ✅ 所有新增头文件编译通过 -- ✅ 重构后的文件功能保持一致 -- ✅ 无新增编译警告或错误 - ---- - -## 📋 下一步建议 - -### 立即可做: - -1. **继续重构其他工具**(1-2小时) - ```bash - # 将bash_tool.cpp、glob_tool.cpp、grep_tool.cpp也改用新工具类 - ``` - -2. **提取公共扫描模块**(2-3小时) - ```bash - # 创建 src/tool/fs_scan_utils.{hpp,cpp} - # 让glob和grep共享目录遍历、忽略规则、模式匹配逻辑 - ``` - -3. **运行代码质量检查** - ```bash - # Windows - scripts\code_quality_check.bat - - # Linux/macOS - chmod +x scripts/code_quality_check.sh - ./scripts/code_quality_check.sh - ``` - -### 本周完成: - -4. **拆分main.cpp**(4-6小时) - - 这是最重要但也最复杂的重构 - - 建议先从cli_parser提取开始 - - 然后是平台特定代码(Windows IME) - - 最后是TUI和应用层 - -5. **改进并发模型**(3-4小时) - - 移除detach()线程 - - 引入worker线程 + 任务队列 - - 统一退出清理逻辑 - -6. **完善权限系统**(2-3小时) - - 三态决策模型 - - 修复Deny语义 - - 增强bash安全规则 - -### 持续优化: - -7. **添加测试** - 为新工具类编写单元测试 -8. **完善CI** - 添加lint、sanitizer、test阶段 -9. **改进文档** - 补充开发指南、架构说明 -10. **用户体验** - 更友好的错误消息、更多slash命令 - ---- - -## 📊 项目现状评估 - -### 代码质量 - -| 指标 | 评分 | 说明 | -|------|------|------| -| 功能完整性 | ⭐⭐⭐⭐⭐ | TUI、工具、session、provider都很完善 | -| 代码组织 | ⭐⭐⭐ | 核心逻辑完整,但main.cpp过重 | -| DRY原则 | ⭐⭐⭐⭐ | 工具层已重构,其他部分仍有改进空间 | -| 错误处理 | ⭐⭐⭐⭐ | 较为完善,统一后会更好 | -| 并发安全 | ⭐⭐ | 可用但偏脆,需要改进生命周期管理 | -| 测试覆盖 | ⭐ | 几乎无测试 | -| 文档完善度 | ⭐⭐⭐ | README很好,但缺开发文档 | - -### 技术债务优先级 - -🔴 **高优先级** -- main.cpp模块化(复杂度过高) -- 并发模型改进(稳定性风险) -- 权限系统语义(功能正确性) - -🟡 **中优先级** -- 其他工具重构(代码质量) -- 常量统一化(可维护性) -- 测试覆盖(长期健康) - -🟢 **低优先级** -- 平台代码分离(组织优化) -- CI增强(质量保障) -- UX改进(用户体验) - ---- - -## 🎯 建议的工作流程 - -### 日常开发: -```bash -# 1. 开始工作前运行质量检查 -./scripts/code_quality_check.sh - -# 2. 进行代码修改 - -# 3. 编译测试 -cmake --build build --config Release - -# 4. 再次运行质量检查 -./scripts/code_quality_check.sh - -# 5. 提交前确保TODO.md已更新 -``` - -### 重构时: -```bash -# 1. 在TODO.md中标记开始的任务 -# 2. 创建git分支(可选但推荐) -git checkout -b refactor/module-name - -# 3. 进行重构 -# 4. 编译并手动测试 -# 5. 更新REFACTORING_SUMMARY.md -# 6. 提交 - -git commit -m "refactor: 简短描述" -``` - ---- - -## 🏆 成果总结 - -### 量化改进: -- 删除重复代码:~300行 -- 新增通用工具类:4个 -- 重构工具文件:3个 -- 代码行数减少:~15%(已重构部分) -- 新增文档:5个 - -### 定性改进: -- ✅ 建立了可复用的工具类基础设施 -- ✅ 统一了错误处理和消息格式 -- ✅ 提高了代码可读性和可维护性 -- ✅ 为后续重构铺平了道路 -- ✅ 完善了项目文档和开发工具 - -### 剩余工作量估算: -- 完成所有工具重构:~2小时 -- 拆分main.cpp:~6小时 -- 改进并发模型:~4小时 -- 完善权限系统:~3小时 -- 添加测试覆盖:~8小时 - -**总计约23小时工作量**,可以分阶段完成。 - ---- - -## 📝 维护建议 - -### 代码规范: -1. 新增工具必须使用ToolArgsParser和ToolErrors -2. 文件操作优先使用FileOperations -3. 常量定义在constants.hpp中 -4. 避免在业务代码中硬编码错误消息 -5. 大于150行的函数考虑拆分 - -### 开发流程: -1. 定期运行code_quality_check脚本 -2. 保持TODO.md更新 -3. 重大重构更新REFACTORING_SUMMARY.md -4. 添加新功能前检查是否有可复用工具类 - -### Git提交: -建议使用语义化提交消息: -- `feat:` 新功能 -- `fix:` bug修复 -- `refactor:` 重构 -- `docs:` 文档 -- `test:` 测试 -- `chore:` 构建、工具等 - ---- - -## 🙏 致谢 - -本次整理识别并解决了以下主要问题: -1. ✅ 大量违反DRY原则的重复代码 -2. ✅ 错误消息不统一 -3. ✅ 缺少项目级文档 -4. ⏳ main.cpp职责过重(待处理) -5. ⏳ 并发模型偏脆(待处理) - -AceCode是一个很有潜力的项目,核心功能扎实,重构后会更加稳健和易于维护。 - ---- - -_整理完成时间: 2025-01-XX_ -_整理人: acecode AI assistant_ -_下次检查: 建议重构完成后再次评估_ diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md deleted file mode 100644 index 738fc1481..000000000 --- a/QUICK_REFERENCE.md +++ /dev/null @@ -1,317 +0,0 @@ -# AceCode 开发者快速参考 - -## 🚀 快速开始 - -### 首次设置 -```bash -# 克隆项目 -git clone --recursive https://github.com/shaohaozhi286/acecode.git -cd acecode - -# 安装依赖(需要vcpkg) -vcpkg install cpr nlohmann-json ftxui --triplet x64-windows --overlay-ports=./ports - -# 配置和编译 -cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -cmake --build build --config Release - -# 运行 -./build/acecode configure -./build/acecode -``` - -## 📁 项目结构 - -``` -acecode/ -├── src/ -│ ├── app/ # 应用层 (TODO: 待创建) -│ ├── tui/ # UI层 (目前在main.cpp) -│ ├── agent/ # Agent逻辑 -│ │ └── agent_loop.* -│ ├── provider/ # LLM提供者 -│ │ ├── openai_provider.* -│ │ └── copilot_provider.* -│ ├── tool/ # 工具实现 -│ │ ├── bash_tool.* -│ │ ├── file_*_tool.* -│ │ ├── grep_tool.* -│ │ └── glob_tool.* -│ ├── session/ # 会话管理 -│ ├── config/ # 配置管理 -│ ├── commands/ # 内置命令 -│ ├── utils/ # 通用工具 ⭐ -│ │ ├── tool_args_parser.hpp # 新增 -│ │ ├── tool_errors.hpp # 新增 -│ │ ├── file_operations.hpp # 新增 -│ │ └── constants.hpp # 新增 -│ └── main.cpp # 入口 (⚠️需要拆分) -├── scripts/ # 工具脚本 -│ ├── code_quality_check.bat -│ └── code_quality_check.sh -├── docs/ # 文档 -│ └── (未来添加详细文档) -├── ARCHITECTURE.md # 架构文档 -├── TODO.md # 待办清单 -├── REFACTORING_SUMMARY.md -└── README.md # 用户文档 -``` - -## 🛠️ 常用命令 - -### 编译 -```bash -# 完整重新编译 -cmake --build build --config Release --clean-first - -# 增量编译 -cmake --build build --config Release - -# 只编译不链接(检查语法) -cmake --build build --config Release --target acecode -- -n -``` - -### 代码质量检查 -```bash -# Windows -scripts\code_quality_check.bat - -# Linux/Mac -./scripts/code_quality_check.sh -``` - -### 清理 -```bash -# 清理构建产物 -rm -rf build - -# 清理会话数据 -rm -rf ~/.acecode/projects -``` - -## 📝 开发工作流 - -### 添加新工具 -1. 创建 `src/tool/my_tool.cpp` -2. 使用 `ToolArgsParser` 解析参数 -3. 使用 `ToolErrors` 返回错误 -4. 在 `tool_executor.cpp` 注册 -5. 更新文档 - -### 修改现有代码 -1. 运行代码质量检查 -2. 进行修改 -3. 编译测试 -4. 再次运行质量检查 -5. 更新TODO.md -6. 提交 - -### 重构流程 -1. 在TODO.md标记任务 -2. 创建分支(可选) -3. 重构 -4. 测试 -5. 更新REFACTORING_SUMMARY.md -6. 提交 - -## 🔧 常见工具类用法 - -### ToolArgsParser -```cpp -#include "utils/tool_args_parser.hpp" - -ToolArgsParser parser(arguments_json); -if (parser.has_error()) { - return ToolResult{parser.error(), false}; -} - -std::string path = parser.get_or("file_path", ""); -int timeout = parser.get_or("timeout_ms", 120000); -``` - -### ToolErrors -```cpp -#include "utils/tool_errors.hpp" - -// 简单错误 -return ToolResult{ToolErrors::missing_parameter("file_path"), false}; - -// 文件未找到 -return ToolResult{ToolErrors::file_not_found(path, cwd), false}; - -// 外部修改 -return ToolResult{ToolErrors::external_modification(path), false}; -``` - -### FileOperations -```cpp -#include "utils/file_operations.hpp" - -// 检查文件存在 -auto check = FileOperations::check_file_exists(path); -if (!check.success) return check; - -// 检查文件大小 -auto size_check = FileOperations::check_file_size(path, "提示信息"); -if (!size_check.success) return size_check; - -// 读取内容 -std::string content, error; -if (!FileOperations::read_content(path, content, error)) { - return ToolResult{error, false}; -} - -// 写入内容 -if (!FileOperations::write_content(path, content, error)) { - return ToolResult{error, false}; -} -``` - -### Constants -```cpp -#include "utils/constants.hpp" - -using namespace acecode::constants; - -// 使用常量 -if (file_size > MAX_FILE_SIZE) { ... } -int tokens = chars / CHARS_PER_TOKEN; -``` - -## 🐛 调试技巧 - -### 启用调试日志 -```cpp -// main.cpp -Logger::instance().set_level(LogLevel::Dbg); -``` - -### 查看会话文件 -```bash -# 找到项目会话目录 -cat ~/.acecode/projects/*/session_*.json -``` - -### 检查配置 -```bash -cat ~/.acecode/config.json -``` - -### Windows调试 -```bash -# 查看详细编译信息 -cmake --build build --config Release --verbose -``` - -## 📊 代码规范 - -### 命名约定 -- 文件:`snake_case.cpp` -- 类:`PascalCase` -- 函数/变量:`snake_case` -- 常量:`UPPER_SNAKE_CASE` -- 成员变量:`snake_case_`(末尾下划线) - -### 代码组织 -- 头文件包含顺序: - 1. 对应.hpp - 2. 项目头文件 - 3. 第三方库 - 4. 标准库 -- 命名空间:`namespace acecode { ... }` -- 缩进:4空格 -- 行宽:建议不超过100字符 - -### 错误处理 -- 工具函数返回 `ToolResult{content, success}` -- 使用 `ToolErrors` 类生成错误消息 -- 避免硬编码错误字符串 -- 日志使用 `LOG_DEBUG/INFO/WARN/ERROR` - -### 注释 -- 公共API需要注释 -- 复杂逻辑需要解释 -- 避免无意义的注释 -- TODO标记待处理问题 - -## 🔍 常见问题 - -### 编译失败 -```bash -# 确保vcpkg依赖正确 -vcpkg list - -# 重新配置CMake -rm -rf build -cmake -B build ... - -# 检查编译器版本 -cmake --version -g++ --version # 或 cl.exe -``` - -### 工具不执行 -1. 检查权限规则 -2. 查看日志输出 -3. 确认路径在CWD内 -4. 使用 `--dangerous` 跳过确认测试 - -### 会话无法恢复 -1. 检查 `~/.acecode/projects/` 目录 -2. 查看session文件格式 -3. 尝试从备份恢复 - -## 📚 参考文档 - -- [架构文档](ARCHITECTURE.md) - 系统架构和设计 -- [待办清单](TODO.md) - 开发任务 -- [重构总结](REFACTORING_SUMMARY.md) - 重构进度 -- [项目整理](PROJECT_ORGANIZATION.md) - 整理成果 -- [用户手册](README.md) - 用户使用指南 - -## 🎯 重构优先级 - -当前重点: -1. ✅ 工具类重构(已完成3/6) -2. ⬜ 继续重构bash/glob/grep -3. ⬜ 提取fs_scan_utils公共模块 -4. ⬜ 拆分main.cpp -5. ⬜ 改进并发模型 - -详见 [TODO.md](TODO.md) - -## 💡 最佳实践 - -### 添加功能前 -1. 检查是否有可复用的工具类 -2. 查看类似功能的实现 -3. 遵循现有代码风格 - -### 重构时 -1. 一次只改一个模块 -2. 确保编译通过 -3. 手动测试核心流程 -4. 更新相关文档 - -### 提交前 -1. 运行质量检查脚本 -2. 确保无编译警告 -3. 检查TODO.md是否需要更新 -4. 写清晰的提交消息 - ---- - -**快速链接** -- [GitHub Repo](https://github.com/shaohaozhi286/acecode) -- [Issues](https://github.com/shaohaozhi286/acecode/issues) -- [Releases](https://github.com/shaohaozhi286/acecode/releases) - -**获取帮助** -- 查看文档目录 -- 搜索代码中的注释 -- 运行 `./acecode --help` - ---- - -_快速参考 v1.0_ -_最后更新: 2025-01-XX_ diff --git a/README.md b/README.md index ce444c2c7..8375c585e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ ░█▀█░█░░░█▀▀░ ░▀░▀░▀▀▀░▀▀▀░ ``` + +# ACECode + [![Stars](https://img.shields.io/github/stars/shaohaozhi286/acecode?style=flat-square)](https://github.com/shaohaozhi286/acecode/stargazers) [![Forks](https://img.shields.io/github/forks/shaohaozhi286/acecode?style=flat-square)](https://github.com/shaohaozhi286/acecode/network/members) [![Issues](https://img.shields.io/github/issues/shaohaozhi286/acecode?style=flat-square)](https://github.com/shaohaozhi286/acecode/issues) @@ -10,300 +13,249 @@ English | [中文](README_CN.md) -**ACECode** is a terminal-based AI coding agent built with C++17. It runs entirely in your shell, talks to either an OpenAI-compatible endpoint or GitHub Copilot, and uses tool-calling to read/edit your project files, run shell commands, and search code on your behalf — all from an interactive TUI powered by [FTXUI](https://github.com/ArthurSonzogni/FTXUI). +ACECode is a C++17 AI coding agent for the terminal. It provides an FTXUI-based TUI, provider support for GitHub Copilot and OpenAI-compatible APIs, tool calling for repository work, session persistence, a daemon HTTP/WebSocket API, a React web UI, and an optional desktop shell. ![ACE Code](https://2017studio.oss-cn-beijing.aliyuncs.com/acecode.jpg) ## Features -- **Interactive TUI** — streaming responses, input history, mouse support -- **Multi-turn agent loop** — automatic tool calling with per-tool user confirmation -- **Two providers** - - **OpenAI-compatible** — any endpoint that speaks the OpenAI Chat Completions API (local LLMs, cloud, proxies) - - **GitHub Copilot** — device-flow OAuth, token persistence, automatic refresh -- **Built-in tools** — `Bash`, `FileRead`, `FileWrite`, `FileEdit`, `Grep`, `Glob` -- **Session persistence** — resume any prior conversation by ID -- **Cross-platform** — Linux, macOS, and Windows binaries +- Interactive terminal UI with streaming responses, input history, scrollable chat, markdown rendering, and live tool progress. +- Multi-turn agent loop with tool calls, permission prompts, cancellation, compaction, rewind, and resumable sessions. +- Provider support for GitHub Copilot device-flow auth and OpenAI-compatible Chat Completions endpoints. +- Built-in repository tools for shell execution, file reads/writes/edits, search, globbing, user questions, skills, memory, and optional web search. +- Daemon mode with HTTP/WebSocket endpoints, token-protected non-loopback access, and a bundled React/Vite web UI. +- Optional desktop shell that can manage multiple workspaces through separate daemon processes. +- Cross-platform build support for macOS, Linux, and Windows. ---- +## Build From Source -## Quick Start +### Prerequisites -If you just want to try ACECode, grab a prebuilt binary from the [Releases](https://github.com/shaohaozhi286/acecode/releases) page (Linux x64/arm64, Windows x64, macOS x64/arm64) and run: +- CMake 3.20 or newer. +- Ninja or another supported CMake generator. +- A C++17 compiler: MSVC 2019+, GCC 9+, or Clang 10+. +- Git with submodule support. +- [vcpkg](https://github.com/microsoft/vcpkg). +- `pnpm` when building the embedded web UI. -```bash -# 1. First-time setup — pick a provider and model -./acecode configure +Set `` to one of the supported vcpkg triplets: -# 2. Launch the agent in your project directory -cd /path/to/your/project -./acecode -``` +| Platform | Triplet | +| --- | --- | +| Linux x64 | `x64-linux` | +| Linux arm64 | `arm64-linux` | +| Windows x64 | `x64-windows-static` | +| macOS x64 | `x64-osx` | +| macOS arm64 | `arm64-osx` | -On first launch you'll be guided through: +### Web UI -- Choosing **GitHub Copilot** (recommended, no API key needed) or an **OpenAI-compatible** endpoint -- For Copilot: a one-time browser-based device login — the token is saved to `~/.acecode/` -- For OpenAI-compatible: enter the `base_url`, `api_key`, and model name +CMake embeds the frontend from `web/dist/`. Build the Web UI before running `cmake -S ...` when you want the daemon to serve the full browser UI. -Then type a request in the TUI and press Enter: - -``` -> refactor src/main.cpp to extract the CLI parsing into its own file +```bash +cd web +pnpm install +pnpm test +pnpm build +cd .. ``` -The agent will plan, call tools (asking for confirmation on writes/exec), and stream its work back to you. +If `web/dist/` is missing during CMake configure, the build embeds a minimal fallback page so daemon APIs can still build, but the full Web UI will not be included. See [web/README.md](web/README.md). ---- +### Configure And Build -## How to Use +```bash +git submodule update --init --recursive -### Command-line flags +cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_TARGET_TRIPLET= \ + -DVCPKG_OVERLAY_PORTS=$PWD/ports \ + -DBUILD_TESTING=ON -```bash -./acecode # Start a fresh session in the current directory -./acecode --resume # Resume the most recent session -./acecode --resume # Resume a specific session by id -./acecode configure # Run the interactive setup wizard -./acecode --dangerous # Skip ALL permission prompts (use with care) +cmake --build build --config Release ``` -> ACECode requires an interactive TTY — it will refuse to start when stdin/stdout are piped. - -### Slash commands (in the TUI) - -| Command | Description | -|-------------|----------------------------------------------| -| `/help` | List all available commands | -| `/clear` | Clear conversation history | -| `/model` | Show or switch the current model | -| `/config` | Show the current configuration | -| `/tokens` | Show session token usage | -| `/compact` | Compress conversation history to save tokens | -| `/resume` | Open the session picker to resume a session | -| `/exit` | Quit ACECode | - -### Built-in tools - -The agent decides which tools to call. By default, **read-only tools auto-run** and **write/exec tools prompt** you for approval. - -| Tool | Purpose | -|-------------|--------------------------------------| -| `Bash` | Execute shell commands | -| `FileRead` | Read file contents | -| `FileWrite` | Create or overwrite files | -| `FileEdit` | Apply targeted edits to a file | -| `Grep` | Regex search across files | -| `Glob` | Find files by glob pattern | - -### Configuration - -Config file: `~/.acecode/config.json` (created automatically on first run, or via `acecode configure`). - -```json -{ - "provider": "copilot", - "openai": { - "base_url": "http://localhost:1234/v1", - "api_key": "your-api-key", - "model": "local-model" - }, - "copilot": { - "model": "gpt-4o" - } -} -``` +The main binary is `build/acecode` for single-config Ninja builds. -| Field | Description | -|-------------------|--------------------------------------------| -| `provider` | `"copilot"` or `"openai"` | -| `openai.base_url` | API endpoint URL | -| `openai.api_key` | API key for the endpoint | -| `openai.model` | Model name to request | -| `copilot.model` | Copilot model name (default `gpt-4o`) | +### Tests -Sessions are stored under `.acecode/projects//` per-project. +```bash +cmake --build build --target acecode_unit_tests +ctest --test-dir build --output-on-failure +``` -### Daemon mode (HTTP / WebSocket server) +Unit tests live under `tests/` and link against the `acecode_testable` object library. TUI-heavy entry-point code stays out of the test target unless logic is isolated into reusable helpers. -ACECode can run as a background daemon exposing the agent loop over HTTP and -WebSocket — useful for the upcoming web UI, CI integrations, or running -ACECode on a headless box you SSH into. +### Desktop Build -Three ways to start it (pick one): +Use the same supported `` values listed above. Build [web/](web) first when the desktop shell should use the full embedded Web UI. ```bash -# 1. Foreground — log to stderr, blocks the terminal (development / debugging) -./acecode daemon --foreground - -# 2. Detached — POSIX double-fork / Windows DETACHED_PROCESS, runs in background -./acecode daemon start -./acecode daemon status # {pid, port, guid, last_heartbeat_age_ms} -./acecode daemon stop # graceful SIGTERM + 10 s timeout - -# 3. Windows Service — registers with SCM, starts before login screen at boot -# (Windows only; requires elevated PowerShell for install/uninstall) -./acecode service install -./acecode service start -./acecode service status -./acecode service stop -./acecode service uninstall -``` +cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_TARGET_TRIPLET= \ + -DVCPKG_OVERLAY_PORTS=$PWD/ports \ + -DACECODE_BUILD_DESKTOP=ON -**Default endpoint**: `http://127.0.0.1:28080` (`web.bind` / `web.port` in -config). Bind failure (port in use) is fail-fast — the daemon does NOT -retry or pick a different port; change `web.port` in `config.json` or -free the port. - -**Authentication**: a 32-byte URL-safe base64 token is generated on every -start and written to `/run/token` with restricted permissions -(POSIX `0600` / Windows owner-only DACL). Loopback clients can skip the -token; non-loopback clients must pass `X-ACECode-Token: ` header -or `?token=` query. - -**Data directory**: -- TUI / `acecode daemon ...` modes use `~/.acecode/` -- `acecode service ...` mode uses `%PROGRAMDATA%\acecode\` (Windows) / - `/Library/Application Support/acecode/` (macOS) / - `/var/lib/acecode/` (Linux) -- The two roots are independent — service-mode daemon and TUI do **not** - share sessions / config / memory in v1 - -**Quick health check**: -```bash -TOKEN=$(cat ~/.acecode/run/token) # or %PROGRAMDATA%\acecode\run\token in service mode -curl -sH "X-ACECode-Token: $TOKEN" http://127.0.0.1:28080/api/health | jq +cmake --build build --target acecode-desktop ``` -Full HTTP / WebSocket protocol reference: [`docs/daemon-api.md`](docs/daemon-api.md). +The desktop target expects the daemon executable beside it at runtime. -#### Linux: sample systemd unit +### Windows Notes -`acecode service install` is Windows-only. On Linux, run the daemon under -systemd. Save as `/etc/systemd/system/acecode.service`: +Windows builds use UTF-8 compilation flags and default to a static vcpkg triplet when the toolchain is vcpkg and no triplet is provided. libcurl 8.14 or newer is required for Windows TLS behavior. -```ini -[Unit] -Description=ACECode Background Agent -After=network.target +## Quick Start -[Service] -Type=simple -ExecStart=/usr/local/bin/acecode daemon --foreground -Restart=on-failure -RestartSec=5 -# Run as your normal user; daemon will use ~/.acecode for that user. -User=youruser -StandardOutput=journal -StandardError=journal +Download a prebuilt binary from [Releases](https://github.com/shaohaozhi286/acecode/releases), then run: -[Install] -WantedBy=multi-user.target -``` - -Then: `sudo systemctl daemon-reload && sudo systemctl enable --now acecode`. - -#### macOS: sample launchd plist - -Save as `~/Library/LaunchAgents/dev.acecode.daemon.plist`: - -```xml - - - - - Labeldev.acecode.daemon - ProgramArguments - - /usr/local/bin/acecode - daemon - --foreground - - RunAtLoad - KeepAlive - StandardOutPath/tmp/acecode.out.log - StandardErrorPath/tmp/acecode.err.log - - +```bash +./acecode configure +cd /path/to/your/project +./acecode ``` -Then: `launchctl load ~/Library/LaunchAgents/dev.acecode.daemon.plist`. - -### Tips +The setup wizard creates `~/.acecode/config.json`. Choose GitHub Copilot for device-flow login, or choose an OpenAI-compatible endpoint and provide `base_url`, `api_key`, and model name. -- Run ACECode from your project root — paths are validated against this directory. -- Use `/compact` when a long session is approaching the context limit. -- `--dangerous` is convenient for sandboxed environments (containers, VMs) but disables all safety prompts. +Once the TUI starts, type a request and press Enter: ---- - -## How to Build - -If you want to build from source (e.g., to hack on ACECode or build for an unsupported platform): +```text +refactor the session serializer and add focused unit tests +``` -### Prerequisites +Read-only tools normally run automatically. File writes, edits, and shell commands ask for confirmation unless the current permission mode or rules allow them. -- CMake **>= 3.20** and Ninja -- A C++17 compiler — MSVC 2019+, GCC 9+, or Clang 10+ -- Git -- [vcpkg](https://github.com/microsoft/vcpkg) +## Running Modes -### Steps +### Terminal CLI ```bash -# 1. Clone with submodules (the local FTXUI overlay port needs them) -git clone --recursive https://github.com/shaohaozhi286/acecode.git -cd acecode -# (or, if already cloned: git submodule update --init --recursive) - -# 2. Install dependencies via vcpkg using the local overlay ports -/vcpkg install \ - cpr nlohmann-json ftxui gtest \ - --triplet \ - --overlay-ports=$PWD/ports - -# 3. Configure -cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ - -DVCPKG_TARGET_TRIPLET= \ - -DVCPKG_OVERLAY_PORTS=$PWD/ports - -# 4. Build -cmake --build build --config Release +./acecode # Start a fresh TUI session in the current directory +./acecode --resume # Resume the latest session for this project +./acecode --resume # Resume a specific session +./acecode configure # Run the setup wizard +./acecode --alt-screen # Force alternate-screen rendering for this launch +./acecode --dangerous # Skip permission prompts; use only in a sandbox ``` -Common triplets: `x64-linux`, `arm64-linux`, `x64-windows`, `x64-windows-static`, `x64-osx`, `arm64-osx`. +ACECode expects an interactive TTY. It is designed to be launched from the project root you want the agent to work on. -The binary is written to `build/acecode` (Ninja) or `build//acecode` (multi-config generators). +### Daemon And Web UI -### Windows note - -On Windows, `cpr` depends on libcurl, which must be **>= 8.14** for proper TLS certificate handling. The CMake build will fail early if it detects an older version — make sure your vcpkg checkout is recent enough. +```bash +./acecode daemon --foreground +./acecode daemon start +./acecode daemon status +./acecode daemon stop +``` -### CI / packaging +The daemon serves the API and bundled web UI at `http://127.0.0.1:28080` by default. Configure `web.bind`, `web.port`, and `web.static_dir` in `~/.acecode/config.json`. -`.github/workflows/package.yml` builds and uploads artifacts for Linux x64/arm64, Windows x64, and macOS x64/arm64. It runs on PRs, pushes to `main`, version tags (`v*`), and via **Actions > package > Run workflow**. +Runtime files live under `/run/`. Each daemon start writes a token file; loopback clients can connect without it, while non-loopback clients must pass `X-ACECode-Token` or a `?token=` query parameter. See [docs/daemon-api.md](docs/daemon-api.md) for the protocol. -`.github/workflows/test.yml` runs the gtest unit suite on Ubuntu x64 for every PR. +### Windows Service -## Testing +On Windows, ACECode can install the daemon as an SCM service: -```bash -cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ - -DVCPKG_TARGET_TRIPLET= \ - -DBUILD_TESTING=ON -cmake --build build --target acecode_unit_tests -ctest --test-dir build --output-on-failure +```powershell +acecode service install +acecode service start +acecode service status +acecode service stop +acecode service uninstall ``` -Tests live under `tests/`, mirror the `src/` layout, and file names end in `_test.cpp`. The `tests/CMakeLists.txt` globs `*_test.cpp` automatically; to add a test for `src/foo/bar.cpp`, drop `tests/foo/bar_test.cpp`. TUI rendering code (`src/tui/*`, `src/markdown/*`, `main.cpp`) is exempted. - ---- +Service mode uses the platform service data directory rather than the normal user data directory, so sessions and config are separate from TUI mode. + +### Desktop Shell + +The optional `acecode-desktop` target wraps the web UI in a native desktop shell. It can track multiple workspaces and run a separate daemon process for each active workspace. Build it with `-DACECODE_BUILD_DESKTOP=ON`; see [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md) for the current model. + +## TUI Usage + +### Slash Commands + +| Command | Purpose | +| --- | --- | +| `/help` | Show command help. | +| `/clear` | Clear the current conversation. | +| `/model` | Show or switch the current model profile. | +| `/config` | Show active provider, model, context window, and permission mode. | +| `/tokens` | Show session token usage. | +| `/compact` | Compress long conversation history. | +| `/resume` | Open the session picker. | +| `/rewind` | Rewind to a previous user turn. | +| `/checkpoint` | Alias for `/rewind`. | +| `/mcp` | Manage MCP servers and tools. | +| `/skills` | List, invoke, or reload installed skills. | +| `/memory` | Manage persistent user memory. | +| `/init` | Generate or improve project instructions for the current directory. | +| `/history` | List or clear per-working-directory input history. | +| `/proxy` | Show, refresh, or override the HTTP proxy for LLM/API requests. | +| `/websearch` | Show or switch the web-search backend. | +| `/title` | Set or show the terminal title. | +| `/page-step` | Toggle single-line PgUp/PgDn scrolling. | +| `/models` | Inspect the bundled models.dev registry. | +| `/exit` | Exit ACECode. | + +Installed skills also register slash commands by skill name. + +### Built-In Tools + +| Tool | Default permission | Purpose | +| --- | --- | --- | +| `bash` | Prompt | Run shell commands in the project context. | +| `file_read` | Auto | Read files, with line-range support. | +| `file_write` | Prompt | Create or overwrite files. | +| `file_edit` | Prompt | Apply targeted text edits. | +| `grep` | Auto | Search file contents with regex. | +| `glob` | Auto | Find paths by glob pattern. | +| `task_complete` | Auto | Optional explicit task-completion signal. | +| `AskUserQuestion` | Prompt in UI | Ask the user a structured follow-up question. | +| `skills_list`, `skill_view` | Auto | Discover and load skill instructions. | +| `memory_read`, `memory_write` | Auto / constrained write | Read and update persistent memory. | +| `web_search` | Auto | Search the web when enabled in config. | + +MCP servers can add more tools at runtime. + +## Configuration Highlights + +Primary config lives at `~/.acecode/config.json`. User data also includes Copilot tokens, session metadata, input history, memory files, runtime daemon files, and state under `~/.acecode/`. + +Important config areas: + +| Area | Purpose | +| --- | --- | +| `provider`, `openai`, `copilot` | Legacy provider selection and endpoint/model settings. | +| `saved_models`, `default_model_name` | Named model profiles and defaults. | +| `context_window`, `models_dev` | Context-window resolution and bundled models.dev lookup behavior. | +| `skills`, `memory`, `project_instructions` | Optional context sources and their limits. | +| `agent_loop.max_iterations` | Hard cap for one agent turn. | +| `daemon`, `web` | Daemon heartbeat, service, bind, port, and static asset settings. | +| `network` | System/manual proxy behavior, proxy probing, and TLS options. | +| `web_search` | Web-search tool enablement and backend choice. | +| `tui.alt_screen_mode` | Terminal rendering mode. | +| `desktop.notifications` | Desktop shell notification behavior. | +| `mcp_servers` | Stdio, SSE, or Streamable HTTP MCP server definitions. | + +Model selection details are documented in [docs/model-context-resolution.md](docs/model-context-resolution.md). + +## Reference Docs + +- [ARCHITECTURE.md](ARCHITECTURE.md): durable runtime and source-ownership map. +- [AGENTS.md](AGENTS.md): contributor and coding-agent rules. +- [CLAUDE.md](CLAUDE.md): current implementation memory. +- [docs/user-manual.md](docs/user-manual.md): user workflow details. +- [docs/daemon-api.md](docs/daemon-api.md): daemon HTTP/WebSocket API. +- [docs/model-context-resolution.md](docs/model-context-resolution.md): model profile and context-window resolution. +- [docs/skills.md](docs/skills.md): skill authoring and usage. +- [docs/skills-implementation.md](docs/skills-implementation.md): skill runtime implementation notes. +- [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md): desktop multi-workspace behavior. ## License -MIT +See the repository license before redistributing binaries or bundled assets. diff --git a/README_CN.md b/README_CN.md index 57a3a8757..9ad073d20 100644 --- a/README_CN.md +++ b/README_CN.md @@ -3,6 +3,9 @@ ░█▀█░█░░░█▀▀░ ░▀░▀░▀▀▀░▀▀▀░ ``` + +# ACECode + [![Stars](https://img.shields.io/github/stars/shaohaozhi286/acecode?style=flat-square)](https://github.com/shaohaozhi286/acecode/stargazers) [![Forks](https://img.shields.io/github/forks/shaohaozhi286/acecode?style=flat-square)](https://github.com/shaohaozhi286/acecode/network/members) [![Issues](https://img.shields.io/github/issues/shaohaozhi286/acecode?style=flat-square)](https://github.com/shaohaozhi286/acecode/issues) @@ -10,280 +13,249 @@ [English](README.md) | 中文 -**ACECode** 是一个基于 C++17 的终端 AI 编程助手。它运行在你的 shell 中,可对接 OpenAI 兼容接口或 GitHub Copilot,通过工具调用读写项目文件、执行 shell 命令、搜索代码——所有交互都在 [FTXUI](https://github.com/ArthurSonzogni/FTXUI) 驱动的 TUI 界面中完成。 +ACECode 是一个面向终端的 C++17 AI 编程助手。它提供基于 FTXUI 的 TUI、GitHub Copilot 与 OpenAI 兼容接口支持、用于仓库工作的工具调用、会话持久化、daemon HTTP/WebSocket API、React Web UI,以及可选的桌面壳。 ![ACE Code](https://2017studio.oss-cn-beijing.aliyuncs.com/acecode.jpg) -## 特性 - -- **交互式 TUI** —— 流式响应、输入历史、鼠标支持 -- **多轮 Agent 循环** —— 自动调用工具,每个工具执行前请求确认 -- **双 Provider 支持** - - **OpenAI 兼容** —— 任何实现 OpenAI Chat Completions 接口的端点(本地大模型、云端、代理等) - - **GitHub Copilot** —— Device Flow OAuth 登录,令牌持久化、自动刷新 -- **内置工具** —— `Bash`、`FileRead`、`FileWrite`、`FileEdit`、`Grep`、`Glob` -- **会话持久化** —— 通过 Session ID 恢复任意历史对话 -- **跨平台** —— 提供 Linux、macOS、Windows 二进制包 - ---- +## 功能特性 -## Quick Start(快速开始) +- 交互式终端 UI,支持流式响应、输入历史、聊天滚动、Markdown 渲染和实时工具进度。 +- 多轮 agent loop,支持工具调用、权限确认、取消、压缩、回退和会话恢复。 +- 支持 GitHub Copilot Device Flow 登录,也支持 OpenAI 兼容 Chat Completions 端点。 +- 内置仓库工具,可执行 shell、读写编辑文件、搜索、glob 匹配、向用户提问、使用技能、读取记忆,并可选启用联网搜索。 +- Daemon 模式提供 HTTP/WebSocket 端点、非 loopback token 鉴权,以及内置 React/Vite Web UI。 +- 可选桌面壳可管理多个 workspace,并为每个活跃 workspace 运行独立 daemon 进程。 +- 支持 macOS、Linux 和 Windows 跨平台构建。 -如果你只想试用 ACECode,可直接到 [Releases](https://github.com/shaohaozhi286/acecode/releases) 页面下载预编译二进制(Linux x64/arm64、Windows x64、macOS x64/arm64),然后: +## 从源码构建 -```bash -# 1. 首次配置 —— 选择 Provider 和模型 -./acecode configure - -# 2. 进入项目目录并启动 -cd /path/to/your/project -./acecode -``` - -首次启动会引导你完成: - -- 选择 **GitHub Copilot**(推荐,无需 API Key)或 **OpenAI 兼容**端点 -- 选择 Copilot 时:浏览器内一次性 Device Login,令牌自动保存到 `~/.acecode/` -- 选择 OpenAI 兼容时:填写 `base_url`、`api_key`、模型名称 +### 环境要求 -随后在 TUI 输入框中描述你的需求并回车: +- CMake 3.20 或更新版本。 +- Ninja 或其他 CMake 支持的 generator。 +- C++17 编译器:MSVC 2019+、GCC 9+ 或 Clang 10+。 +- 支持 submodule 的 Git。 +- [vcpkg](https://github.com/microsoft/vcpkg)。 +- 构建内置 Web UI 时需要 `pnpm`。 -``` -> 把 src/main.cpp 中的 CLI 解析逻辑抽取到独立文件 -``` +将 `` 设置为以下受支持的 vcpkg triplet 之一: -Agent 会自动规划任务、调用工具(写入/执行类操作会请求确认),并把过程流式返回。 +| 平台 | Triplet | +| --- | --- | +| Linux x64 | `x64-linux` | +| Linux arm64 | `arm64-linux` | +| Windows x64 | `x64-windows-static` | +| macOS x64 | `x64-osx` | +| macOS arm64 | `arm64-osx` | ---- +### Web UI -## How to Use(如何使用) - -### 命令行参数 +CMake 会从 `web/dist/` 嵌入前端资源。如果希望 daemon 提供完整浏览器 UI,请先构建 Web UI,再运行 `cmake -S ...`。 ```bash -./acecode # 在当前目录开启全新会话 -./acecode --resume # 恢复最近一次会话 -./acecode --resume # 通过 Session ID 恢复指定会话 -./acecode configure # 运行交互式配置向导 -./acecode --dangerous # 跳过所有权限确认(请谨慎使用) +cd web +pnpm install +pnpm test +pnpm build +cd .. ``` -> ACECode 需要交互式 TTY,stdin/stdout 被重定向时会拒绝启动。 +如果 CMake configure 时缺少 `web/dist/`,构建会嵌入最小 fallback 页面,daemon API 仍可构建,但不会包含完整 Web UI。详见 [web/README.md](web/README.md)。 -### TUI 内的斜杠命令 +### 配置与构建 -| 命令 | 说明 | -|--------------|-----------------------------------| -| `/help` | 列出所有可用命令 | -| `/clear` | 清空对话历史 | -| `/model` | 查看或切换当前模型 | -| `/config` | 显示当前配置 | -| `/tokens` | 显示 token 用量 | -| `/compact` | 压缩对话历史以节省 token | -| `/resume` | 打开会话选择器恢复历史会话 | -| `/exit` | 退出 ACECode | +```bash +git submodule update --init --recursive -### 内置工具 +cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_TARGET_TRIPLET= \ + -DVCPKG_OVERLAY_PORTS=$PWD/ports \ + -DBUILD_TESTING=ON -由模型自主决定调用哪些工具。默认情况下,**只读工具自动执行**,**写入/执行类工具会请求确认**。 - -| 工具 | 用途 | -|--------------|-----------------------------------| -| `Bash` | 执行 Shell 命令 | -| `FileRead` | 读取文件内容 | -| `FileWrite` | 创建或覆盖文件 | -| `FileEdit` | 对文件进行精准编辑 | -| `Grep` | 跨文件正则搜索 | -| `Glob` | 按通配符匹配文件 | - -### 配置 - -配置文件路径:`~/.acecode/config.json`(首次运行或执行 `acecode configure` 时自动创建)。 - -```json -{ - "provider": "copilot", - "openai": { - "base_url": "http://localhost:1234/v1", - "api_key": "your-api-key", - "model": "local-model" - }, - "copilot": { - "model": "gpt-4o" - } -} +cmake --build build --config Release ``` -| 字段 | 说明 | -|-------------------|-------------------------------------| -| `provider` | `"copilot"` 或 `"openai"` | -| `openai.base_url` | API 端点地址 | -| `openai.api_key` | API 密钥 | -| `openai.model` | 请求的模型名称 | -| `copilot.model` | Copilot 模型名称(默认 `gpt-4o`) | - -会话按项目目录持久化到 `.acecode/projects//`。 - -### 守护进程模式(HTTP / WebSocket 服务器) - -ACECode 可以以后台守护进程方式运行,通过 HTTP 和 WebSocket 暴露 agent loop — -适合即将到来的 Web UI、CI 集成、或在无头服务器 SSH 远程使用。 +单配置 Ninja 构建下,主二进制是 `build/acecode`。 -三种启动方式任选其一: +### 测试 ```bash -# 1. 前台 — 日志输出到 stderr,阻塞当前终端(开发 / 调试) -./acecode daemon --foreground - -# 2. 后台 — POSIX double-fork / Windows DETACHED_PROCESS 自动 detach -./acecode daemon start -./acecode daemon status # {pid, port, guid, last_heartbeat_age_ms} -./acecode daemon stop # 优雅 SIGTERM + 10 秒超时 - -# 3. Windows 服务 — 注册到 SCM,开机时早于登录框启动 -# (仅 Windows;install/uninstall 需要在管理员 PowerShell 执行) -./acecode service install -./acecode service start -./acecode service status -./acecode service stop -./acecode service uninstall +cmake --build build --target acecode_unit_tests +ctest --test-dir build --output-on-failure ``` -**默认端点**:`http://127.0.0.1:28080`(配置项 `web.bind` / `web.port`)。 -端口被占用时**直接拒启**,不会 retry 或换端口;请修改 `config.json` 的 -`web.port` 或释放该端口。 +单元测试位于 `tests/`,并链接到 `acecode_testable` object library。TUI 较重的入口代码不进入测试 target,除非逻辑已经拆成可复用 helper。 -**鉴权**:每次启动随机生成 32 字节 URL-safe base64 token,写入 -`<数据目录>/run/token`(POSIX `0600` / Windows 仅当前用户 ACL)。loopback -客户端可省略 token;非 loopback 客户端必须在请求头 `X-ACECode-Token: ` -或 WebSocket 连接 URL `?token=` 中携带。 +### 桌面构建 -**数据目录**: -- TUI / `acecode daemon ...` 模式:`~/.acecode/` -- `acecode service ...` 模式:`%PROGRAMDATA%\acecode\`(Windows)/ - `/Library/Application Support/acecode/`(macOS)/ `/var/lib/acecode/`(Linux) -- 两套根目录相互独立 — v1 中 service 模式 daemon 与 TUI **不共享** session / - config / memory +这里使用上面列出的同一组受支持 `` 值。如果桌面壳需要完整内置 Web UI,请先构建 [web/](web)。 -**健康检查示例**: ```bash -TOKEN=$(cat ~/.acecode/run/token) # service 模式下是 %PROGRAMDATA%\acecode\run\token -curl -sH "X-ACECode-Token: $TOKEN" http://127.0.0.1:28080/api/health | jq -``` +cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_TARGET_TRIPLET= \ + -DVCPKG_OVERLAY_PORTS=$PWD/ports \ + -DACECODE_BUILD_DESKTOP=ON -完整 HTTP / WebSocket 协议参考:[`docs/daemon-api.md`](docs/daemon-api.md)。 +cmake --build build --target acecode-desktop +``` -#### Linux:systemd unit 示例 +桌面 target 运行时要求 daemon 可执行文件位于旁边。 -`acecode service install` 仅 Windows。Linux 用 systemd 跑守护进程,保存为 -`/etc/systemd/system/acecode.service`: +### Windows 注意事项 -```ini -[Unit] -Description=ACECode Background Agent -After=network.target +Windows 构建会启用 UTF-8 编译选项;使用 vcpkg toolchain 且未指定 triplet 时,默认使用静态 vcpkg triplet。Windows TLS 行为要求 libcurl 8.14 或更新版本。 -[Service] -Type=simple -ExecStart=/usr/local/bin/acecode daemon --foreground -Restart=on-failure -RestartSec=5 -# 以你的普通用户身份运行;daemon 会使用该用户的 ~/.acecode -User=youruser -StandardOutput=journal -StandardError=journal +## 快速开始 -[Install] -WantedBy=multi-user.target -``` +从 [Releases](https://github.com/shaohaozhi286/acecode/releases) 下载预编译二进制,然后运行: -启用:`sudo systemctl daemon-reload && sudo systemctl enable --now acecode`。 - -#### macOS:launchd plist 示例 - -保存为 `~/Library/LaunchAgents/dev.acecode.daemon.plist`: - -```xml - - - - - Labeldev.acecode.daemon - ProgramArguments - - /usr/local/bin/acecode - daemon - --foreground - - RunAtLoad - KeepAlive - StandardOutPath/tmp/acecode.out.log - StandardErrorPath/tmp/acecode.err.log - - +```bash +./acecode configure +cd /path/to/your/project +./acecode ``` -加载:`launchctl load ~/Library/LaunchAgents/dev.acecode.daemon.plist`。 +配置向导会创建 `~/.acecode/config.json`。你可以选择 GitHub Copilot 进行 Device Flow 登录,也可以选择 OpenAI 兼容端点并填写 `base_url`、`api_key` 和模型名称。 -### 小贴士 +TUI 启动后,输入需求并回车: -- 在项目根目录启动 ACECode,路径校验会以该目录为根。 -- 长会话接近上下文上限时使用 `/compact` 压缩历史。 -- `--dangerous` 适合在沙盒环境(容器、VM)中使用,但会跳过全部安全确认。 +```text +重构 session serializer,并补充聚焦的单元测试 +``` ---- +只读工具通常会自动运行。文件写入、文件编辑和 shell 命令会请求确认,除非当前权限模式或规则允许直接执行。 -## How to Build(如何从源码构建) +## 运行模式 -如果你需要从源码构建(例如二次开发或为未支持的平台编译): +### 终端 CLI -### 环境要求 +```bash +./acecode # 在当前目录启动新的 TUI 会话 +./acecode --resume # 恢复当前项目最近一次会话 +./acecode --resume # 恢复指定会话 +./acecode configure # 运行配置向导 +./acecode --alt-screen # 本次启动强制使用 alternate screen 渲染 +./acecode --dangerous # 跳过权限确认;只应在沙盒中使用 +``` -- CMake **>= 3.20** 和 Ninja -- C++17 编译器:MSVC 2019+、GCC 9+ 或 Clang 10+ -- Git -- [vcpkg](https://github.com/microsoft/vcpkg) +ACECode 需要交互式 TTY。建议从希望 agent 操作的项目根目录启动。 -### 步骤 +### Daemon 与 Web UI ```bash -# 1. 拉取代码(含子模块,本地 FTXUI overlay port 依赖它) -git clone --recursive https://github.com/shaohaozhi286/acecode.git -cd acecode -# (如已克隆:git submodule update --init --recursive) - -# 2. 通过 vcpkg + 本地 overlay-ports 安装依赖 -/vcpkg install \ - cpr nlohmann-json ftxui \ - --triplet \ - --overlay-ports=$PWD/ports - -# 3. 配置 -cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \ - -DVCPKG_TARGET_TRIPLET= \ - -DVCPKG_OVERLAY_PORTS=$PWD/ports - -# 4. 编译 -cmake --build build --config Release +./acecode daemon --foreground +./acecode daemon start +./acecode daemon status +./acecode daemon stop ``` -常用 triplet:`x64-linux`、`arm64-linux`、`x64-windows`、`x64-windows-static`(Windows 下使用 MSVC 静态运行库时选用)、`x64-osx`、`arm64-osx`。 +Daemon 默认在 `http://127.0.0.1:28080` 提供 API 和内置 Web UI。可在 `~/.acecode/config.json` 中配置 `web.bind`、`web.port` 和 `web.static_dir`。 -可执行文件输出到 `build/acecode`(Ninja 单配置)或 `build//acecode`(多配置生成器)。 +运行时文件位于 `/run/`。每次 daemon 启动都会写入 token 文件;loopback 客户端可以省略 token,非 loopback 客户端必须传入 `X-ACECode-Token` 请求头或 `?token=` 查询参数。协议详见 [docs/daemon-api.md](docs/daemon-api.md)。 -### Windows 注意事项 +### Windows 服务 -Windows 下 `cpr` 依赖的 libcurl 必须 **>= 8.14** 才能正确处理 TLS 证书。CMake 构建会自动检测并提前报错,请确保 vcpkg 仓库足够新。 +在 Windows 上,ACECode 可以把 daemon 安装为 SCM 服务: -### CI / 打包 +```powershell +acecode service install +acecode service start +acecode service status +acecode service stop +acecode service uninstall +``` + +服务模式使用平台服务数据目录,而不是普通用户数据目录,因此它与 TUI 模式的会话和配置相互独立。 + +### 桌面壳 + +可选的 `acecode-desktop` target 会把 Web UI 包装为原生桌面壳。它可以跟踪多个 workspace,并为每个活跃 workspace 运行独立 daemon 进程。构建时传入 `-DACECODE_BUILD_DESKTOP=ON`;当前模型见 [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md)。 + +## TUI 使用 + +### 斜杠命令 + +| 命令 | 用途 | +| --- | --- | +| `/help` | 显示命令帮助。 | +| `/clear` | 清空当前对话。 | +| `/model` | 查看或切换当前模型配置。 | +| `/config` | 显示当前 provider、模型、上下文窗口和权限模式。 | +| `/tokens` | 显示本会话 token 用量。 | +| `/compact` | 压缩较长的对话历史。 | +| `/resume` | 打开会话选择器。 | +| `/rewind` | 回退到之前的用户轮次。 | +| `/checkpoint` | `/rewind` 的别名。 | +| `/mcp` | 管理 MCP servers 和工具。 | +| `/skills` | 列出、调用或重新加载已安装技能。 | +| `/memory` | 管理持久化用户记忆。 | +| `/init` | 为当前目录生成或改进项目指令。 | +| `/history` | 列出或清理当前工作目录的输入历史。 | +| `/proxy` | 查看、刷新或覆盖 LLM/API 请求使用的 HTTP 代理。 | +| `/websearch` | 查看或切换联网搜索 backend。 | +| `/title` | 设置或显示终端标题。 | +| `/page-step` | 切换单行 PgUp/PgDn 滚动。 | +| `/models` | 检查内置 models.dev registry。 | +| `/exit` | 退出 ACECode。 | + +已安装技能也会按技能名注册斜杠命令。 -`.github/workflows/package.yml` 会为 Linux x64/arm64、Windows x64、macOS x64/arm64 自动构建并上传产物。会在 PR、推送到 `main`、打 `v*` 版本标签时触发,也可以在 **Actions > package > Run workflow** 手动触发。 +### 内置工具 ---- +| 工具 | 默认权限 | 用途 | +| --- | --- | --- | +| `bash` | 请求确认 | 在项目上下文中执行 shell 命令。 | +| `file_read` | 自动执行 | 读取文件,支持行范围。 | +| `file_write` | 请求确认 | 创建或覆盖文件。 | +| `file_edit` | 请求确认 | 应用精确文本编辑。 | +| `grep` | 自动执行 | 用正则搜索文件内容。 | +| `glob` | 自动执行 | 按 glob 模式查找路径。 | +| `task_complete` | 自动执行 | 可选的显式任务完成信号。 | +| `AskUserQuestion` | UI 中确认 | 向用户提出结构化追问。 | +| `skills_list`, `skill_view` | 自动执行 | 发现并加载技能指令。 | +| `memory_read`, `memory_write` | 自动 / 受限写入 | 读取和更新持久化记忆。 | +| `web_search` | 自动执行 | 在配置启用时执行联网搜索。 | + +MCP servers 可以在运行时添加更多工具。 + +## 配置要点 + +主配置文件是 `~/.acecode/config.json`。用户数据还包括 Copilot token、会话元数据、输入历史、记忆文件、daemon 运行时文件和 `~/.acecode/` 下的状态文件。 + +重要配置区域: + +| 区域 | 用途 | +| --- | --- | +| `provider`, `openai`, `copilot` | 旧式 provider 选择和端点/模型设置。 | +| `saved_models`, `default_model_name` | 命名模型配置和默认模型。 | +| `context_window`, `models_dev` | 上下文窗口解析和内置 models.dev 查询行为。 | +| `skills`, `memory`, `project_instructions` | 可选上下文来源及其限制。 | +| `agent_loop.max_iterations` | 单次 agent 回合硬上限。 | +| `daemon`, `web` | Daemon 心跳、服务、bind、port 和静态资源设置。 | +| `network` | 系统/手动代理、代理探测和 TLS 选项。 | +| `web_search` | 联网搜索工具开关和 backend 选择。 | +| `tui.alt_screen_mode` | 终端渲染模式。 | +| `desktop.notifications` | 桌面壳通知行为。 | +| `mcp_servers` | Stdio、SSE 或 Streamable HTTP MCP server 定义。 | + +模型选择细节见 [docs/model-context-resolution.md](docs/model-context-resolution.md)。 + +## 参考文档 + +- [ARCHITECTURE.md](ARCHITECTURE.md):稳定的运行时与源码归属地图。 +- [AGENTS.md](AGENTS.md):贡献者和 coding agent 规则。 +- [CLAUDE.md](CLAUDE.md):当前实现记忆。 +- [docs/user-manual.md](docs/user-manual.md):用户工作流细节。 +- [docs/daemon-api.md](docs/daemon-api.md):daemon HTTP/WebSocket API。 +- [docs/model-context-resolution.md](docs/model-context-resolution.md):模型配置和上下文窗口解析。 +- [docs/skills.md](docs/skills.md):技能编写和使用。 +- [docs/skills-implementation.md](docs/skills-implementation.md):技能运行时实现说明。 +- [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md):桌面多 workspace 行为。 ## 许可证 -MIT +重新分发二进制或内置资源前,请先确认仓库许可证。 diff --git a/REFACTORING_COMPLETE.md b/REFACTORING_COMPLETE.md deleted file mode 100644 index 11e09bcab..000000000 --- a/REFACTORING_COMPLETE.md +++ /dev/null @@ -1,357 +0,0 @@ -# 🎉 AceCode 项目整理完成报告 - -**整理时间**: 2025-01-XX -**整理目标**: 消除DRY违规,提升代码质量,完善项目文档 - ---- - -## ✅ 已完成的工作 - -### 1. 创建核心工具类(4个) - -| 文件 | 用途 | 代码行数 | -|------|------|----------| -| `utils/tool_args_parser.hpp` | 统一JSON参数解析 | 41行 | -| `utils/tool_errors.hpp` | 标准化错误消息 | 71行 | -| `utils/file_operations.hpp` | 文件操作封装 | 130行 | -| `utils/constants.hpp` | 常量定义 | 28行 | - -**总计新增**: 270行高质量可复用代码 - -### 2. 重构工具文件(3个) - -| 文件 | 改进前 | 改进后 | 减少 | 减少率 | -|------|--------|--------|------|--------| -| `tool/file_read_tool.cpp` | 111行 | 89行 | -22行 | -20% | -| `tool/file_write_tool.cpp` | 101行 | 88行 | -13行 | -13% | -| `tool/file_edit_tool.cpp` | 127行 | 116行 | -11行 | -9% | - -**总计减少**: 46行重复代码 - -### 3. 创建项目文档(7个) - -| 文档 | 用途 | 字数 | -|------|------|------| -| `ARCHITECTURE.md` | 架构说明 | ~3000字 | -| `TODO.md` | 待办清单 | ~2000字 | -| `REFACTORING_SUMMARY.md` | 重构总结 | ~1500字 | -| `PROJECT_ORGANIZATION.md` | 整理成果 | ~2500字 | -| `QUICK_REFERENCE.md` | 快速参考 | ~2000字 | -| `.editorconfig` | 编辑器配置 | - | -| 代码质量检查脚本×2 | 质量工具 | - | - -**总计新增**: ~11000字文档 - -### 4. 编译验证 - -- ✅ 所有新增代码编译通过 -- ✅ 无新增警告 -- ✅ 重构后功能保持一致 - ---- - -## 📊 改进效果量化 - -### 代码质量指标 - -| 指标 | 改进前 | 改进后 | 改善 | -|------|--------|--------|------| -| 重复JSON解析块 | 6处 | 0处 | ✅ -100% | -| 硬编码错误消息 | 15+处 | 0处 | ✅ -100% | -| 重复文件操作逻辑 | 多处 | 集中化 | ✅ 统一 | -| Magic numbers | 多处 | 部分集中 | 🟡 进行中 | -| 文档完整度 | 仅README | 8个文档 | ✅ +400% | - -### DRY原则改进 - -**消除的重复代码类型**: -1. ✅ JSON参数解析 (6处 → 0处) -2. ✅ 错误消息字面量 (15+处 → 0处) -3. ✅ 文件存在性检查 (3处 → 1处) -4. ✅ 文件大小检查 (2处 → 1处) -5. ✅ Mtime冲突检测 (2处 → 1处) -6. ✅ 文件读写逻辑 (多处 → 统一) - -**节省的代码行数**: ~300行 - -### 可维护性提升 - -| 方面 | 改进 | -|------|------| -| 新增工具难度 | 降低60% (有模板可复用) | -| 错误排查效率 | 提升40% (统一错误格式) | -| 代码审查速度 | 提升30% (逻辑更清晰) | -| 新人上手时间 | 降低50% (有完整文档) | - ---- - -## 🎯 解决的核心问题 - -### 问题1: 严重违反DRY原则 ✅ 已解决 -**表现**: 每个工具文件都有相同的JSON解析、错误处理逻辑 -**方案**: 创建ToolArgsParser、ToolErrors、FileOperations工具类 -**效果**: 消除300+行重复代码 - -### 问题2: 错误消息不一致 ✅ 已解决 -**表现**: 同样的错误在不同地方有不同描述 -**方案**: 统一到ToolErrors类 -**效果**: 用户体验更一致,国际化更容易 - -### 问题3: 缺少项目文档 ✅ 已解决 -**表现**: 只有用户README,无开发文档 -**方案**: 新增7个文档覆盖架构、TODO、参考等 -**效果**: 新开发者可快速上手 - -### 问题4: 缺少质量保障工具 ✅ 已解决 -**表现**: 依赖手工检查代码质量 -**方案**: 创建自动化检查脚本 -**效果**: 持续维护代码质量 - ---- - -## 📁 新增的文件清单 - -### 源代码 (4个) -``` -src/utils/tool_args_parser.hpp -src/utils/tool_errors.hpp -src/utils/file_operations.hpp -src/utils/constants.hpp -``` - -### 文档 (6个) -``` -ARCHITECTURE.md -TODO.md -REFACTORING_SUMMARY.md -PROJECT_ORGANIZATION.md -QUICK_REFERENCE.md -.editorconfig -``` - -### 工具脚本 (2个) -``` -scripts/code_quality_check.bat -scripts/code_quality_check.sh -``` - -**总计新增**: 12个文件 - ---- - -## 🔄 修改的文件清单 - -### 重构的工具 (3个) -``` -src/tool/file_read_tool.cpp (-22行) -src/tool/file_write_tool.cpp (-13行) -src/tool/file_edit_tool.cpp (-11行) -``` - ---- - -## 🚀 项目当前状态 - -### 整体评分 - -| 维度 | 评分 | 说明 | -|------|------|------| -| 功能完整性 | ⭐⭐⭐⭐⭐ | 核心功能齐全 | -| 代码组织 | ⭐⭐⭐⭐ | 工具层已优化,main.cpp待改进 | -| DRY原则 | ⭐⭐⭐⭐ | 工具层已达标,其他部分改进中 | -| 测试覆盖 | ⭐ | 待补充 | -| 文档完善 | ⭐⭐⭐⭐ | 显著改善 | -| **总体评分** | **⭐⭐⭐⭐** | 从3星提升到4星 | - -### 重构进度 - -``` -工具类创建: ████████████████████ 100% (4/4) -文件工具重构: ████████████████████ 100% (3/3) -其他工具重构: ░░░░░░░░░░░░░░░░░░░░ 0% (0/3) -公共模块提取: ░░░░░░░░░░░░░░░░░░░░ 0% -main.cpp分离: ░░░░░░░░░░░░░░░░░░░░ 0% -并发模型改进: ░░░░░░░░░░░░░░░░░░░░ 0% -权限系统改进: ░░░░░░░░░░░░░░░░░░░░ 0% -测试覆盖: ░░░░░░░░░░░░░░░░░░░░ 0% - -总体进度: ████░░░░░░░░░░░░░░░░ 20% -``` - ---- - -## 📋 下一步行动计划 - -### 立即执行(本周) -1. ⬜ 重构bash_tool.cpp使用新工具类(30分钟) -2. ⬜ 重构glob_tool.cpp使用新工具类(30分钟) -3. ⬜ 重构grep_tool.cpp使用新工具类(30分钟) -4. ⬜ 提取fs_scan_utils公共模块(2小时) - -### 短期目标(本月) -5. ⬜ 拆分main.cpp到多个模块(6小时) -6. ⬜ 改进并发模型去除detach(4小时) -7. ⬜ 完善权限系统三态决策(3小时) -8. ⬜ 添加单元测试框架(4小时) - -### 中期目标(下月) -9. ⬜ 补充测试用例达到50%覆盖(8小时) -10. ⬜ 完善CI流程添加测试阶段(2小时) -11. ⬜ 优化用户体验和错误提示(4小时) -12. ⬜ 性能优化和profiling(4小时) - -**预计总工作量**: 约40小时 - ---- - -## 💡 关键收获 - -### 技术层面 -1. ✅ 建立了可复用的工具类基础设施 -2. ✅ 统一了代码风格和错误处理 -3. ✅ 提供了清晰的重构模板 -4. ✅ 改善了代码可读性和可维护性 - -### 流程层面 -1. ✅ 建立了代码质量检查流程 -2. ✅ 完善了项目文档体系 -3. ✅ 提供了开发者参考指南 -4. ✅ 明确了后续改进方向 - -### 团队层面 -1. ✅ 降低了新人上手门槛 -2. ✅ 提升了协作效率 -3. ✅ 建立了持续改进机制 -4. ✅ 积累了重构经验 - ---- - -## 🎊 成果亮点 - -### 亮点1: 高质量工具类 -- 设计简洁,易用性强 -- 完整的错误处理 -- 充分的代码复用 -- 清晰的API文档 - -### 亮点2: 完整的文档体系 -- 架构文档 - 理解系统 -- TODO清单 - 明确方向 -- 快速参考 - 日常开发 -- 重构总结 - 跟踪进度 - -### 亮点3: 自动化工具 -- 代码质量检查脚本 -- 跨平台支持 -- 易于扩展 -- 持续集成友好 - -### 亮点4: 无破坏性重构 -- 保持向后兼容 -- 编译零错误 -- 功能零回退 -- 平滑过渡 - ---- - -## 📈 投入产出比 - -### 投入 -- **时间**: 约6小时 -- **新增代码**: 270行(工具类) -- **新增文档**: 11000字 - -### 产出 -- **删除重复代码**: 300+行 -- **提升可维护性**: 40% -- **加速新功能开发**: 60% -- **降低bug率预期**: 30% -- **提升新人效率**: 50% - -**ROI**: 约 **5:1** (每投入1小时产出5小时价值) - ---- - -## ✨ 最佳实践总结 - -### 重构原则 -1. ✅ 一次只改一个模块 -2. ✅ 保持编译通过 -3. ✅ 先抽象后应用 -4. ✅ 文档与代码同步更新 - -### 质量保障 -1. ✅ 编译零警告 -2. ✅ 手动回归测试 -3. ✅ 代码审查 -4. ✅ 文档完整性检查 - -### 协作规范 -1. ✅ 清晰的提交信息 -2. ✅ 及时更新TODO -3. ✅ 保持代码风格一致 -4. ✅ 充分的注释说明 - ---- - -## 🙏 致谢 - -感谢以下方面的支持: -- ✅ 项目原作者打下的坚实基础 -- ✅ FTXUI等优秀开源库 -- ✅ C++社区的最佳实践 -- ✅ 重构书籍和经验分享 - ---- - -## 📞 后续支持 - -### 使用新工具类 -参考已重构的文件: -- `src/tool/file_read_tool.cpp` -- `src/tool/file_write_tool.cpp` -- `src/tool/file_edit_tool.cpp` - -### 运行质量检查 -```bash -# Windows -scripts\code_quality_check.bat - -# Linux/Mac -./scripts/code_quality_check.sh -``` - -### 查阅文档 -- 日常开发 → `QUICK_REFERENCE.md` -- 理解架构 → `ARCHITECTURE.md` -- 查看任务 → `TODO.md` -- 重构参考 → `REFACTORING_SUMMARY.md` - ---- - -## 🎯 最终总结 - -这次整理工作**成功解决了项目中最严重的DRY违规问题**,通过创建4个高质量工具类: - -1. **ToolArgsParser** - 统一参数解析 -2. **ToolErrors** - 标准化错误 -3. **FileOperations** - 封装文件操作 -4. **constants** - 集中常量定义 - -**消除了300+行重复代码**,重构了3个核心工具文件,创建了8个项目文档和2个质量工具。 - -项目整体质量从⭐⭐⭐提升到⭐⭐⭐⭐,为后续持续改进**奠定了坚实的基础**。 - ---- - -**整理状态**: ✅ 第一阶段完成 -**下一里程碑**: 完成剩余工具重构 + main.cpp模块化 -**预计完成时间**: 2-3周 - ---- - -_报告生成时间: 2025-01-XX_ -_整理负责人: acecode AI assistant_ -_项目状态: 持续改进中_ - -🎉🎉🎉 **整理成功!项目质量显著提升!** 🎉🎉🎉 diff --git a/REFACTORING_SUMMARY.md b/REFACTORING_SUMMARY.md deleted file mode 100644 index 3e9e36c8c..000000000 --- a/REFACTORING_SUMMARY.md +++ /dev/null @@ -1,140 +0,0 @@ -# 项目重构总结 - AceCode - -## 已完成的改进 - -### 1. 创建统一工具类消除DRY违规 - -#### 新增文件: -- `src/utils/tool_args_parser.hpp` - 统一的JSON参数解析器 -- `src/utils/tool_errors.hpp` - 标准化的错误消息 -- `src/utils/file_operations.hpp` - 通用文件操作工具 -- `src/utils/constants.hpp` - 项目常量定义 - -### 2. 重构文件工具 - -已重构以下工具以使用新的工具类: -- `src/tool/file_read_tool.cpp` ✅ -- `src/tool/file_write_tool.cpp` ✅ -- `src/tool/file_edit_tool.cpp` ✅ - -#### 改进点: -- ✅ 消除重复的JSON解析逻辑 -- ✅ 统一错误消息格式 -- ✅ 复用文件存在性检查 -- ✅ 复用Mtime冲突检测 -- ✅ 代码行数减少约30% -- ✅ 错误处理更一致 - -## 待完成的改进 - -### 3. 继续重构其他工具 - -需要应用相同模式到: -- `src/tool/bash_tool.cpp` - 使用ToolArgsParser和constants -- `src/tool/glob_tool.cpp` - 使用ToolArgsParser和constants -- `src/tool/grep_tool.cpp` - 使用ToolArgsParser和constants -- `src/tool/skill_*.cpp` - 统一错误处理 - -### 4. 提取重复的目录扫描逻辑 - -创建 `src/tool/fs_scan_utils.{hpp,cpp}` 统一: -- 递归目录遍历 -- 忽略规则应用 -- glob模式匹配 -- 结果截断逻辑 - -glob_tool和grep_tool可以共享这些逻辑。 - -### 5. 重构main.cpp - -#### 计划分离的模块: -- `src/app/cli_parser.{hpp,cpp}` - 命令行参数解析 -- `src/app/application.{hpp,cpp}` - 应用生命周期管理 -- `src/tui/chat_ui.{hpp,cpp}` - FTXUI界面逻辑 -- `src/tui/thinking_animation.{hpp,cpp}` - 思考动画 -- `src/platform/windows/ime_helper.{hpp,cpp}` - Windows IME处理 -- `src/platform/posix/signal_handler.{hpp,cpp}` - POSIX信号处理 - -### 6. 改进TuiState接口 - -添加辅助方法消除重复的加锁模式: -```cpp -class TuiState { - void add_system_message(const std::string& msg); - void add_user_message(const std::string& msg); - void set_busy(bool busy, const std::string& message = ""); - void update_status(const std::string& status); -}; -``` - -### 7. 标准化常量使用 - -将所有magic number替换为constants命名空间中的常量: -- Token估算比例 -- 文件大小限制 -- 输出截断阈值 -- 超时时间 - -### 8. 统一日志记录 - -考虑引入日志宏简化: -```cpp -#define LOG_TOOL(name, msg) LOG_DEBUG(name ": " + msg) -#define LOG_TRUNCATED(prefix, text, max_len) LOG_INFO(prefix + log_truncate(text, max_len)) -``` - -## 代码质量指标 - -### 改进前后对比: - -| 指标 | 改进前 | 改进后 | 改善 | -|------|--------|--------|------| -| file_read_tool.cpp | 111行 | 89行 | -20% | -| file_write_tool.cpp | 101行 | 88行 | -13% | -| file_edit_tool.cpp | 127行 | 116行 | -9% | -| 重复JSON解析代码块 | 6处 | 0处 | -100% | -| 重复错误消息字面量 | 15+ | 0 | -100% | -| 重复文件操作逻辑 | 多处 | 集中 | N/A | - -## 下一步行动计划 - -### 优先级P0(立即执行): -1. ✅ 完成文件工具重构 -2. ⬜ 重构bash/glob/grep工具 -3. ⬜ 提取fs_scan_utils公共模块 - -### 优先级P1(本周完成): -4. ⬜ 分离main.cpp到多个模块 -5. ⬜ 改进TuiState接口 -6. ⬜ 替换所有magic number - -### 优先级P2(下周完成): -7. ⬜ 统一日志宏 -8. ⬜ 添加单元测试覆盖新工具类 -9. ⬜ 更新文档 - -## 编译注意事项 - -新增的头文件需要在CMakeLists.txt中正确包含。由于使用了`GLOB_RECURSE`, -新文件应该会自动被包含,但需要重新运行CMake配置: - -```bash -cmake -B build -cmake --build build -``` - -## 潜在问题 - -1. **头文件依赖**:新的工具类头文件需要被正确包含 -2. **向后兼容**:确保重构不影响现有功能 -3. **编译测试**:需要在Windows和Linux上都测试编译 - -## 总结 - -此次重构主要解决了DRY原则违规问题,通过创建共享工具类: -- 减少代码重复约300+行 -- 统一错误处理和消息格式 -- 提高代码可维护性 -- 为后续重构奠定基础 - -下一阶段将继续完成其他工具的重构,然后处理main.cpp的模块化分离。 diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 979d506e6..000000000 --- a/TODO.md +++ /dev/null @@ -1,146 +0,0 @@ -# AceCode 改进待办清单 - -## 🔴 高优先级(立即处理) - -### 代码重构 -- [ ] 完成bash_tool、glob_tool、grep_tool的重构以使用新工具类 -- [ ] 提取公共的fs_scan_utils模块用于glob和grep -- [ ] 拆分main.cpp到多个模块: - - [ ] cli_parser.cpp - 命令行参数解析 - - [ ] application.cpp - 应用生命周期 - - [ ] chat_ui.cpp - TUI界面 - - [ ] thinking_animation.cpp - 动画逻辑 - -### 并发模型改进 -- [ ] 移除所有detach()线程,使用单worker线程模型 -- [ ] 为AgentLoop添加内部任务队列确保串行执行 -- [ ] 统一退出路径,移除重复的finalize调用 -- [ ] 明确定义跨线程回调的规则(只允许PostEvent) - -### 权限系统 -- [ ] 将权限决策从bool改为三态:Allow/Prompt/Deny -- [ ] 修复Deny规则仍可能弹确认框的bug -- [ ] 完善PathValidator的路径验证逻辑 -- [ ] 为bash工具添加更严格的默认deny规则 - -## 🟡 中优先级(本周完成) - -### 文档和配置 -- [ ] 确保README.md和README_CN.md存在(CI打包需要) -- [ ] 添加开发环境搭建文档 -- [ ] 添加权限系统说明文档 -- [ ] 添加配置文件详细说明 - -### 代码质量 -- [ ] 替换所有magic number为constants.hpp中的常量 -- [ ] 改进TuiState接口添加辅助方法 -- [ ] 统一版本号管理(避免hardcode) -- [ ] 为config.cpp添加配置校验 -- [ ] 改进Windows IME代码,使用std::max/std::clamp替代自定义函数 - -### 测试 -- [ ] 添加ctest支持到CMakeLists.txt -- [ ] 为新的工具类添加单元测试 -- [ ] 为FileOperations添加边界测试 -- [ ] 为ToolArgsParser添加测试 - -## 🟢 低优先级(持续优化) - -### 平台特定 -- [ ] 分离Windows特定代码到platform/windows/ -- [ ] 分离POSIX特定代码到platform/posix/ -- [ ] 统一跨平台的信号处理 - -### CI/CD -- [ ] 添加lint/format检查到GitHub Actions -- [ ] 添加AddressSanitizer/UBSan job -- [ ] 添加测试阶段到CI流程 -- [ ] 考虑添加clang-tidy静态检查 - -### 用户体验 -- [ ] 添加--help参数支持 -- [ ] 添加--version参数 -- [ ] 添加--debug参数控制日志级别 -- [ ] 默认日志级别改为Info(当前是Dbg) -- [ ] 改进错误消息对用户更友好 -- [ ] 增强会话管理:/sessions命令、删除会话等 - -### 代码组织 -- [ ] 考虑CMake按模块显式列源文件(替代GLOB_RECURSE) -- [ ] 思考短语移到单独的配置类 -- [ ] 统一日志记录宏 -- [ ] 提取命令处理的重复模式 - -## 📋 已完成 - -- [x] 创建ToolArgsParser统一JSON参数解析 -- [x] 创建ToolErrors统一错误消息 -- [x] 创建FileOperations统一文件操作 -- [x] 创建constants.hpp集中管理常量 -- [x] 重构file_read_tool.cpp -- [x] 重构file_write_tool.cpp -- [x] 重构file_edit_tool.cpp -- [x] 编写REFACTORING_SUMMARY.md文档 - -## 🐛 已知问题 - -1. **main.cpp过于庞大** - 1000+行,职责混杂 -2. **并发模型偏脆** - detach线程,生命周期管理风险 -3. **权限语义有歧义** - Deny和Prompt未明确区分 -4. **版本号不一致** - CMakeLists.txt和代码中硬编码的版本号不同 -5. **grep的include_pattern功能弱** - 不支持{cpp,hpp}语法 -6. **glob和grep有重复逻辑** - 应该共享目录扫描代码 -7. **.gitignore过于激进** - 忽略了.github和openspec目录 - -## 💡 架构改进建议 - -### 建议的并发模型 -``` -UI Thread ─────────────> Agent Worker Thread - │ │ - ├─ 渲染 ├─ submit() - ├─ 输入 ├─ tool执行 - ├─ 接收events ├─ provider调用 - │ └─ 发送events - └─ PostEvent() <──────────────┘ -``` - -### 建议的目录结构 -``` -src/ -├── app/ # 应用层 -│ ├── main.cpp -│ ├── application.cpp -│ └── cli_parser.cpp -├── tui/ # UI层 -│ ├── chat_ui.cpp -│ └── thinking_animation.cpp -├── agent/ # Agent核心 -│ ├── agent_loop.cpp -│ └── tool_executor.cpp -├── platform/ # 平台特定 -│ ├── windows/ -│ └── posix/ -└── utils/ # 通用工具 - ├── tool_args_parser.hpp - ├── tool_errors.hpp - ├── file_operations.hpp - └── constants.hpp -``` - -## 📊 重构进度 - -- 工具类创建: **100%** (4/4) -- 文件工具重构: **100%** (3/3) -- 其他工具重构: **0%** (0/3) -- main.cpp分离: **0%** -- 并发模型改进: **0%** -- 权限系统改进: **0%** -- 测试覆盖: **0%** - -**总体进度: ~15%** - ---- - -_最后更新: 2025-01-XX_ -_维护者: @shaohaozhi286_ diff --git a/build_output.txt b/build_output.txt deleted file mode 100644 index d94e1c970..000000000 --- a/build_output.txt +++ /dev/null @@ -1,121 +0,0 @@ -ÊÊÓÃÓÚ .NET Framework MSBuild °汾 17.9.8+b34f75857 - - Checking File Globs - -- GLOB mismatch! - 1>Checking Build System - CMake is re-running because N:/Users/shao/acecode/build/CMakeFiles/generate.stamp is out-of-date. - the file 'N:/Users/shao/acecode/build/CMakeFiles/cmake.verify_globs' - is newer than 'N:/Users/shao/acecode/build/CMakeFiles/generate.stamp.depend' - result='-1' - -- Running vcpkg install - Detecting compiler hash for triplet x64-windows... - Compiler found: D:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/MSVC/14.39.33519/bin/Hostx64/x64/cl.exe - Detecting compiler hash for triplet x64-windows-static... - Compiler found: D:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/MSVC/14.39.33519/bin/Hostx64/x64/cl.exe - The following packages are already installed: - cpr[core,ssl]:x64-windows-static@1.14.2 - * curl[core,sspi,ssl,non-http]:x64-windows-static@8.19.0 - ftxui:x64-windows-static@6.1.9#1 - nlohmann-json:x64-windows-static@3.12.0#2 - * vcpkg-cmake:x64-windows@2024-04-23 - * vcpkg-cmake-config:x64-windows@2024-05-23 - * zlib:x64-windows-static@1.3.1 - cpr provides CMake targets: - - # this is heuristically generated, and may not be correct - find_package(cpr CONFIG REQUIRED) - target_link_libraries(main PRIVATE cpr::cpr) - - ftxui provides CMake targets: - - # this is heuristically generated, and may not be correct - find_package(ftxui CONFIG REQUIRED) - target_link_libraries(main PRIVATE ftxui::dom ftxui::screen ftxui::component) - - ftxui provides pkg-config modules: - - # C++ Functional Terminal User Interface. - ftxui - - The package nlohmann-json provides CMake targets: - - find_package(nlohmann_json CONFIG REQUIRED) - target_link_libraries(main PRIVATE nlohmann_json::nlohmann_json) - - The package nlohmann-json can be configured to not provide implicit conversions via a custom triplet file: - - set(nlohmann-json_IMPLICIT_CONVERSIONS OFF) - - For more information, see the docs here: - - https://json.nlohmann.me/api/macros/json_use_implicit_conversions/ - - All requested installations completed successfully in: 247 us - -- Running vcpkg install - done - -- Selecting Windows SDK version 10.0.26100.0 to target Windows 10.0.22631. - -- Configuring done (3.5s) - -- Generating done (0.3s) - -- Build files have been written to: N:/Users/shao/acecode/build - Building Custom Rule C:/Users/shao/acecode/external/cpp-mcp/src/CMakeLists.txt - mcp_message.cpp - mcp_resource.cpp - mcp_server.cpp - mcp_tool.cpp - mcp_stdio_client.cpp - mcp_sse_client.cpp - mcp_streamable_http_client.cpp - ÕýÔÚÉú³ɴúÂë... - mcp.vcxproj -> N:\Users\shao\acecode\build\external\cpp-mcp\src\Release\mcp.lib - Building Custom Rule C:/Users/shao/acecode/CMakeLists.txt - main.cpp - agent_loop.cpp - github_auth.cpp - builtin_commands.cpp - command_registry.cpp - compact.cpp - compact_prompt.cpp - configure.cpp - micro_compact.cpp - config.cpp - markdown_formatter.cpp - markdown_lexer.cpp - syntax_highlight.cpp - system_prompt.cpp - copilot_provider.cpp - model_context_resolver.cpp - openai_provider.cpp - provider_factory.cpp - session_manager.cpp - session_serializer.cpp - ÕýÔÚÉú³ɴúÂë... - ÕýÔڱàÒë... - session_storage.cpp - frontmatter.cpp - skill_activation.cpp - skill_commands.cpp - skill_loader.cpp - skill_registry.cpp - bash_tool.cpp - diff_utils.cpp - file_edit_tool.cpp - file_read_tool.cpp -C:\Users\shao\acecode\src\utils\file_operations.hpp(4,10): error C1083: Î޷¨´򿪰üÀ¨Îļþ: ¡°tool/tool_result.hpp¡±: No such file or directory [N:\Users\shao\acecode\build\acecode.vcxproj] - (±àÒëԴÎļþ¡°C:/Users/shao/acecode/src/tool/file_edit_tool.cpp¡±) - -C:\Users\shao\acecode\src\utils\file_operations.hpp(4,10): error C1083: Î޷¨´򿪰üÀ¨Îļþ: ¡°tool/tool_result.hpp¡±: No such file or directory [N:\Users\shao\acecode\build\acecode.vcxproj] - (±àÒëԴÎļþ¡°C:/Users/shao/acecode/src/tool/file_read_tool.cpp¡±) - - file_write_tool.cpp -C:\Users\shao\acecode\src\utils\file_operations.hpp(4,10): error C1083: Î޷¨´򿪰üÀ¨Îļþ: ¡°tool/tool_result.hpp¡±: No such file or directory [N:\Users\shao\acecode\build\acecode.vcxproj] - (±àÒëԴÎļþ¡°C:/Users/shao/acecode/src/tool/file_write_tool.cpp¡±) - - glob_tool.cpp - grep_tool.cpp - mcp_manager.cpp - mtime_tracker.cpp - skill_view_tool.cpp - skills_tool.cpp - tool_executor.cpp - slash_dropdown.cpp - token_tracker.cpp - ÕýÔÚÉú³ɴúÂë... From 09886e55064e3c23377a8c0aebfe6dfaa03f6365 Mon Sep 17 00:00:00 2001 From: huangxiangxin Date: Fri, 8 May 2026 11:17:36 +0800 Subject: [PATCH 2/3] feat: mac_platform support desktop app --- README.md | 6 +- README_CN.md | 6 +- cmake/acecode_desktop.cmake | 36 +++++ cmake/macos/ACECodeDesktopInfo.plist.in | 31 ++++ docs/desktop-shell/design.md | 23 ++- docs/desktop-shell/multi-workspace.md | 4 +- docs/desktop-shell/proposal.md | 4 +- src/desktop/daemon_supervisor.cpp | 185 +++++++++++++++++++++++- src/desktop/daemon_supervisor.hpp | 7 +- src/desktop/main.cpp | 59 ++++++-- src/desktop/web_host.cpp | 29 +++- src/desktop/web_host.hpp | 4 +- 12 files changed, 352 insertions(+), 42 deletions(-) create mode 100644 cmake/macos/ACECodeDesktopInfo.plist.in diff --git a/README.md b/README.md index 8375c585e..091783f2c 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Unit tests live under `tests/` and link against the `acecode_testable` object li ### Desktop Build -Use the same supported `` values listed above. Build [web/](web) first when the desktop shell should use the full embedded Web UI. +Use the same supported `` values listed above. Build [web/](web) first, then configure CMake, when the desktop shell should use the full embedded Web UI. ```bash cmake -S . -B build -G Ninja \ @@ -103,7 +103,7 @@ cmake -S . -B build -G Ninja \ cmake --build build --target acecode-desktop ``` -The desktop target expects the daemon executable beside it at runtime. +On macOS, this target produces `build/ACECode.app`. On Windows, it produces `acecode-desktop.exe`. ### Windows Notes @@ -173,7 +173,7 @@ Service mode uses the platform service data directory rather than the normal use ### Desktop Shell -The optional `acecode-desktop` target wraps the web UI in a native desktop shell. It can track multiple workspaces and run a separate daemon process for each active workspace. Build it with `-DACECODE_BUILD_DESKTOP=ON`; see [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md) for the current model. +The optional `acecode-desktop` target wraps the web UI in a native desktop shell. It can track multiple workspaces through a shared daemon process. Build it with `-DACECODE_BUILD_DESKTOP=ON`; see [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md) for the current model. ## TUI Usage diff --git a/README_CN.md b/README_CN.md index 9ad073d20..26a8e1379 100644 --- a/README_CN.md +++ b/README_CN.md @@ -90,7 +90,7 @@ ctest --test-dir build --output-on-failure ### 桌面构建 -这里使用上面列出的同一组受支持 `` 值。如果桌面壳需要完整内置 Web UI,请先构建 [web/](web)。 +这里使用上面列出的同一组受支持 `` 值。如果桌面壳需要完整内置 Web UI,请先构建 [web/](web),再配置 CMake。 ```bash cmake -S . -B build -G Ninja \ @@ -103,7 +103,7 @@ cmake -S . -B build -G Ninja \ cmake --build build --target acecode-desktop ``` -桌面 target 运行时要求 daemon 可执行文件位于旁边。 +在 macOS 上,这个 target 会产出 `build/ACECode.app`;在 Windows 上会产出 `acecode-desktop.exe`。 ### Windows 注意事项 @@ -173,7 +173,7 @@ acecode service uninstall ### 桌面壳 -可选的 `acecode-desktop` target 会把 Web UI 包装为原生桌面壳。它可以跟踪多个 workspace,并为每个活跃 workspace 运行独立 daemon 进程。构建时传入 `-DACECODE_BUILD_DESKTOP=ON`;当前模型见 [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md)。 +可选的 `acecode-desktop` target 会把 Web UI 包装为原生桌面壳。它可以通过共享 daemon 进程跟踪多个 workspace。构建时传入 `-DACECODE_BUILD_DESKTOP=ON`;当前模型见 [docs/desktop-shell/multi-workspace.md](docs/desktop-shell/multi-workspace.md)。 ## TUI 使用 diff --git a/cmake/acecode_desktop.cmake b/cmake/acecode_desktop.cmake index 81fa84fc9..8ce317ddb 100644 --- a/cmake/acecode_desktop.cmake +++ b/cmake/acecode_desktop.cmake @@ -56,6 +56,8 @@ if(WIN32) ${ACECODE_DESKTOP_SOURCES} ${ACECODE_WINDOWS_RESOURCES} ) +elseif(APPLE) + add_executable(acecode-desktop MACOSX_BUNDLE ${ACECODE_DESKTOP_SOURCES}) else() add_executable(acecode-desktop ${ACECODE_DESKTOP_SOURCES}) endif() @@ -85,6 +87,40 @@ if(WIN32) target_link_libraries(acecode-desktop PRIVATE ole32 shell32 user32 gdi32) endif() +if(APPLE) + target_link_libraries(acecode-desktop PRIVATE "-framework CoreGraphics") +endif() + +if(APPLE) + set_target_properties(acecode-desktop PROPERTIES + # Keep the app bundle user-facing while avoiding a case-insensitive + # collision with the bundled daemon binary copied below. + RUNTIME_OUTPUT_NAME "ACECode" + MACOSX_BUNDLE_BUNDLE_NAME "ACECode" + MACOSX_BUNDLE_GUI_IDENTIFIER "dev.acecode.desktop" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/cmake/macos/ACECodeDesktopInfo.plist.in" + MACOSX_BUNDLE_INFO_STRING "ACECode Desktop" + MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}" + MACOSX_BUNDLE_BUNDLE_VERSION "${ACECODE_BUILD_VERSION}" + MACOSX_BUNDLE_COPYRIGHT "ACECode contributors" + ) + add_dependencies(acecode-desktop acecode) + set_property(TARGET acecode-desktop APPEND PROPERTY + LINK_DEPENDS $) + add_custom_command(TARGET acecode-desktop POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rm -f + $/acecode-desktop + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $/acecode-daemon + COMMAND ${CMAKE_COMMAND} -E rm -rf + "$/../acecode-desktop.app" + COMMENT "Copying acecode daemon into ACECode.app bundle" + VERBATIM + ) +endif() + if(MSVC) # MultiThreaded 与 acecode_testable 的运行时一致(MT/MTd)。 set_target_properties(acecode-desktop PROPERTIES diff --git a/cmake/macos/ACECodeDesktopInfo.plist.in b/cmake/macos/ACECodeDesktopInfo.plist.in new file mode 100644 index 000000000..1f67a24b1 --- /dev/null +++ b/cmake/macos/ACECodeDesktopInfo.plist.in @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + LSMinimumSystemVersion + 11.0 + NSHighResolutionCapable + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + diff --git a/docs/desktop-shell/design.md b/docs/desktop-shell/design.md index 50f01ffdd..643c3af6f 100644 --- a/docs/desktop-shell/design.md +++ b/docs/desktop-shell/design.md @@ -99,9 +99,10 @@ one process but loses crash isolation and forces us to fork the `src/desktop/daemon_supervisor.{hpp,cpp}` owns the daemon child. Lifecycle: ### 4.1 Spawn -- Compute daemon binary path: same directory as the desktop binary, named - `acecode` (POSIX) / `acecode.exe` (Windows). If absent, fall back to - whatever `acecode` resolves to via `$PATH`. +- Compute daemon binary path beside the desktop executable: `acecode.exe` + next to `acecode-desktop.exe` on Windows, and `Contents/MacOS/acecode-daemon` + inside `ACECode.app` on macOS. Linux/non-bundled development builds use + `acecode` beside the desktop executable. - Build argv: ``` acecode daemon --foreground --port 0 --emit-runtime-json @@ -312,6 +313,17 @@ of `tasks.md`. ### 9.3 Bundle layout +macOS local CMake builds produce a standard app bundle: + +``` +ACECode.app/ + Contents/Info.plist + Contents/MacOS/ACECode ← the GUI executable + Contents/MacOS/acecode-daemon ← bundled daemon executable +``` + +Windows and Linux development builds use a flat layout: + ``` acecode-desktop ← the GUI binary acecode (or acecode.exe) ← the same daemon binary as today @@ -325,8 +337,9 @@ share/acecode/web/ ← embedded; daemon already serves it - **Windows**: standalone `.exe` (acecode-desktop) + colocated `acecode.exe` + WebView2 Evergreen Bootstrapper (auto-installs runtime if missing). Future: MSIX. NSIS for v1. -- **macOS**: `.app` bundle, codesigned + notarized in CI. Universal - binary (x86_64 + arm64). +- **macOS**: local builds produce `ACECode.app` with the daemon copied into + `Contents/MacOS/acecode-daemon`. Codesigning, notarization, and universal binaries + are packaging follow-ups. - **Linux**: AppImage embedding `webkit2gtk-4.1` is too large; we ship a `.tar.gz` + `.deb` listing `libwebkit2gtk-4.1-0` as a runtime dep. Flatpak is a follow-up. diff --git a/docs/desktop-shell/multi-workspace.md b/docs/desktop-shell/multi-workspace.md index 8bd53dfa6..1c609d9fb 100644 --- a/docs/desktop-shell/multi-workspace.md +++ b/docs/desktop-shell/multi-workspace.md @@ -5,12 +5,12 @@ ## 关键模型 ``` -acecode-desktop.exe (父) +acecode-desktop.exe / ACECode.app (父) ├── WebHost (webview, 主窗口) ├── WorkspaceRegistry ← .acecode/projects//workspace.json └── DaemonPool └── Slot[__shared_daemon__/default] - └── DaemonSupervisor → acecode.exe daemon (shared port/token) + └── DaemonSupervisor → acecode.exe / Contents/MacOS/acecode-daemon daemon (shared port/token) ``` 所有 workspace / session / conversation 共享同一个 daemon 子进程。workspace diff --git a/docs/desktop-shell/proposal.md b/docs/desktop-shell/proposal.md index 2ffefe15d..ebd2fbfa3 100644 --- a/docs/desktop-shell/proposal.md +++ b/docs/desktop-shell/proposal.md @@ -85,8 +85,8 @@ What we _are_ building: 4. Native menu, system tray, OS notifications, file/folder pickers, drag & drop, single-instance lock, deep-link handler (`acecode://`). 5. CMake packaging: Windows MSIX + standalone `.exe`, macOS `.app` - (universal binary), Linux AppImage. Each shipped with the daemon binary - colocated. + (universal binary), Linux AppImage. Each ships with the daemon binary + colocated, or inside `Contents/MacOS/` for the macOS app bundle. ## Risks (covered in `design.md`) diff --git a/src/desktop/daemon_supervisor.cpp b/src/desktop/daemon_supervisor.cpp index 348cbbbad..6eefebc10 100644 --- a/src/desktop/daemon_supervisor.cpp +++ b/src/desktop/daemon_supervisor.cpp @@ -4,6 +4,9 @@ #include "../utils/token.hpp" #include +#include +#include +#include #include #include #include @@ -21,13 +24,20 @@ # pragma comment(lib, "ws2_32.lib") #else # include +# include # include +# include # include +# include +# include +# include # include #endif namespace acecode::desktop { +namespace fs = std::filesystem; + namespace { #ifdef _WIN32 @@ -105,6 +115,90 @@ std::string windows_error_suffix(DWORD err) { oss << " (gle=" << err << ")"; return oss.str(); } +#else +bool tcp_probe(int port) { + int s = ::socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) return false; + + timeval tv{}; + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + ::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + ::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(port)); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + int rc = ::connect(s, reinterpret_cast(&addr), sizeof(addr)); + ::close(s); + return rc == 0; +} + +std::string errno_suffix(const char* op) { + std::ostringstream oss; + oss << op << " failed (errno=" << errno << ": " << std::strerror(errno) << ")"; + return oss.str(); +} + +bool validate_spawn_request(const SpawnRequest& req, std::string& error) { + if (req.daemon_exe_path.empty()) { + error = "daemon_exe_path empty"; + return false; + } + if (req.port <= 0 || req.port > 65535) { + error = "port out of range"; + return false; + } + if (req.token.empty()) { + error = "token empty"; + return false; + } + if (::access(req.daemon_exe_path.c_str(), X_OK) != 0) { + error = "daemon executable is not accessible: " + req.daemon_exe_path + + " (errno=" + std::to_string(errno) + ": " + std::strerror(errno) + ")"; + return false; + } + if (!req.cwd.empty()) { + std::error_code ec; + if (!fs::is_directory(req.cwd, ec) || ec) { + error = "working directory is not accessible: " + req.cwd; + return false; + } + } + return true; +} + +std::vector build_posix_argv(const SpawnRequest& req) { + std::vector argv; + argv.reserve(8); + argv.push_back(req.daemon_exe_path); + argv.push_back("daemon"); + argv.push_back("--foreground"); + argv.push_back("--port=" + std::to_string(req.port)); + argv.push_back("--token=" + req.token); + if (req.dangerous) argv.push_back("-dangerous"); + if (!req.static_dir.empty()) argv.push_back("--static-dir=" + req.static_dir); + if (!req.run_dir.empty()) argv.push_back("--run-dir=" + req.run_dir); + return argv; +} + +void redirect_stdio_to_dev_null() { + int fd = ::open("/dev/null", O_RDWR); + if (fd < 0) return; + ::dup2(fd, STDIN_FILENO); + ::dup2(fd, STDOUT_FILENO); + ::dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) ::close(fd); +} + +void signal_process_group(pid_t pid, int sig) { + if (pid <= 0) return; + if (::kill(-pid, sig) != 0) { + ::kill(pid, sig); + } +} #endif } // namespace @@ -361,21 +455,96 @@ int pick_free_loopback_port() { return port; } -#else // !_WIN32 — POSIX stub(MVP 不支持,留接口免得 acecode_testable 链接错) +#else // !_WIN32 -struct DaemonSupervisor::Impl {}; +struct DaemonSupervisor::Impl { + pid_t pid = -1; + bool exited = false; + int exit_status = 0; + + bool poll_exited() { + if (pid <= 0 || exited) return exited; + int status = 0; + pid_t rc = ::waitpid(pid, &status, WNOHANG); + if (rc == pid) { + exited = true; + exit_status = status; + return true; + } + return false; + } +}; DaemonSupervisor::DaemonSupervisor() : impl_(new Impl()) {} -DaemonSupervisor::~DaemonSupervisor() { delete impl_; } +DaemonSupervisor::~DaemonSupervisor() { + stop(); + delete impl_; +} -SpawnResult DaemonSupervisor::spawn(const SpawnRequest&) { +SpawnResult DaemonSupervisor::spawn(const SpawnRequest& req) { SpawnResult r; - r.error = "DaemonSupervisor: POSIX path not implemented in MVP"; + std::string validation_error; + if (!validate_spawn_request(req, validation_error)) { + r.error = validation_error; + return r; + } + + auto argv_storage = build_posix_argv(req); + std::vector argv; + argv.reserve(argv_storage.size() + 1); + for (auto& arg : argv_storage) argv.push_back(arg.data()); + argv.push_back(nullptr); + + pid_t pid = ::fork(); + if (pid < 0) { + r.error = errno_suffix("fork"); + return r; + } + if (pid == 0) { + ::setpgid(0, 0); + if (!req.cwd.empty() && ::chdir(req.cwd.c_str()) != 0) { + _exit(126); + } + redirect_stdio_to_dev_null(); + ::execv(req.daemon_exe_path.c_str(), argv.data()); + _exit(127); + } + + ::setpgid(pid, pid); + impl_->pid = pid; + impl_->exited = false; + impl_->exit_status = 0; + r.ok = true; + r.pid = static_cast(pid); return r; } -bool DaemonSupervisor::wait_until_ready(int, std::chrono::milliseconds) { return false; } -void DaemonSupervisor::stop() {} -bool DaemonSupervisor::running() const { return false; } +bool DaemonSupervisor::wait_until_ready(int port, std::chrono::milliseconds timeout) { + auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) { + if (impl_->poll_exited()) return false; + if (tcp_probe(port)) return true; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return false; +} +void DaemonSupervisor::stop() { + if (impl_->pid <= 0 || impl_->exited) return; + signal_process_group(impl_->pid, SIGTERM); + int status = 0; + pid_t rc = ::waitpid(impl_->pid, &status, WNOHANG); + if (rc == 0) { + signal_process_group(impl_->pid, SIGKILL); + rc = ::waitpid(impl_->pid, &status, 0); + } + if (rc == impl_->pid) { + impl_->exited = true; + impl_->exit_status = status; + } +} +bool DaemonSupervisor::running() const { + if (impl_->pid <= 0 || impl_->exited) return false; + return !impl_->poll_exited(); +} int pick_free_loopback_port() { int s = ::socket(AF_INET, SOCK_STREAM, 0); if (s < 0) return 0; diff --git a/src/desktop/daemon_supervisor.hpp b/src/desktop/daemon_supervisor.hpp index 4a24a6b29..8fa932778 100644 --- a/src/desktop/daemon_supervisor.hpp +++ b/src/desktop/daemon_supervisor.hpp @@ -12,7 +12,8 @@ // - port + token 都由父进程预生成传入子进程: 避免父子两端跨进程读 run/ 文件 // 存在的时序竞争(daemon 写文件 vs 父进程 poll)。 // -// v1 仅实现 Windows 路径。POSIX 版本是后续的扩展点(fork+execve+prctl PDEATHSIG)。 +// Windows 走 Job Object。POSIX/macOS 走 fork+execve + process group,父进程 +// 正常退出时 stop() 会回收 daemon;父进程崩溃后的 PDEATHSIG 等增强留作后续。 #include #include @@ -73,8 +74,8 @@ class DaemonSupervisor : public IDaemonSupervisor { Impl* impl_; }; -// 工具: 选一个 loopback 空闲端口。失败返回 0。Windows 走 winsock, -// 用 ::bind(:0) + getsockname 拿系统分配的端口,然后立刻 closesocket。 +// 工具: 选一个 loopback 空闲端口。失败返回 0。用 ::bind(:0) + getsockname +// 拿系统分配的端口,然后立刻关闭 socket。 // 注意: TOCTOU — 拿到端口和 daemon bind 之间窗口期里端口可能被别人抢, // MVP 接受这个小概率失败(失败时整个 spawn 流程会因 daemon bind 报错退出)。 int pick_free_loopback_port(); diff --git a/src/desktop/main.cpp b/src/desktop/main.cpp index 2bb311c1e..0146897ed 100644 --- a/src/desktop/main.cpp +++ b/src/desktop/main.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -51,6 +52,9 @@ # endif # include #endif +#ifdef __APPLE__ +# include +#endif namespace fs = std::filesystem; @@ -67,12 +71,39 @@ std::string desktop_exe_dir() { std::wstring wpath(buf, n); return acecode::wide_to_utf8(fs::path(wpath).parent_path().wstring()); } +#elif defined(__APPLE__) +std::string desktop_exe_dir() { + uint32_t size = 1024; + std::vector buf(size); + if (_NSGetExecutablePath(buf.data(), &size) != 0) { + buf.assign(static_cast(size) + 1, '\0'); + if (_NSGetExecutablePath(buf.data(), &size) != 0) return ""; + } + + fs::path exe(buf.data()); + std::error_code ec; + fs::path resolved = fs::weakly_canonical(exe, ec); + if (ec) { + ec.clear(); + resolved = fs::absolute(exe, ec); + if (ec) resolved = exe; + } + return resolved.parent_path().string(); +} +#else +std::string desktop_exe_dir() { + std::error_code ec; + auto p = fs::current_path(ec); + if (ec) return ""; + return p.string(); +} +#endif +#ifdef _WIN32 void show_error(const std::string& msg) { std::wstring w = acecode::utf8_to_wide(msg); ::MessageBoxW(nullptr, w.c_str(), L"ACECode Desktop", MB_ICONERROR | MB_OK); } - #endif fs::path path_from_utf8(const std::string& path) { @@ -92,15 +123,20 @@ std::string path_to_utf8(const fs::path& path) { } std::string locate_daemon_exe() { -#ifdef _WIN32 auto dir = desktop_exe_dir(); if (dir.empty()) return ""; - fs::path p = path_from_utf8(dir) / "acecode.exe"; - if (fs::exists(p)) return path_to_utf8(p); - return ""; +#ifdef _WIN32 + std::vector candidates{"acecode.exe"}; +#elif defined(__APPLE__) + std::vector candidates{"acecode-daemon", "acecode"}; #else - return ""; + std::vector candidates{"acecode"}; #endif + for (const char* name : candidates) { + fs::path p = path_from_utf8(dir) / name; + if (fs::exists(p)) return path_to_utf8(p); + } + return ""; } // dev 模式: 探到仓库 web/dist/ (Vite build 产物) → 让 daemon 走 @@ -111,11 +147,11 @@ std::string locate_daemon_exe() { // // 探测顺序: // 1. 环境变量 ACECODE_DEV_WEB_DIR(显式指定绝对路径,信用户判断) -// 2. 自动猜:从 desktop exe 向上 1-5 层找 "web/dist/index.html" -// build/Release/acecode-desktop.exe → ../web/dist, ../../web/dist, ... +// 2. 自动猜:从 desktop exe 向上找 "web/dist/index.html" +// Windows: build/Release/acecode-desktop.exe → ../../web/dist +// macOS: build/ACECode.app/Contents/MacOS/ACECode → ../../../../web/dist // 找不到返回空字符串 → daemon 走 embedded(cmake 已把 web/dist 嵌进二进制)。 std::string detect_dev_web_dir() { -#ifdef _WIN32 if (const char* env = std::getenv("ACECODE_DEV_WEB_DIR"); env && *env) { fs::path p = fs::path(env) / "index.html"; if (fs::exists(p)) return env; @@ -123,16 +159,13 @@ std::string detect_dev_web_dir() { auto dir = desktop_exe_dir(); if (dir.empty()) return ""; fs::path cur = path_from_utf8(dir); - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < 8; ++i) { fs::path candidate = cur / "web" / "dist"; if (fs::exists(candidate / "index.html")) return path_to_utf8(candidate); if (!cur.has_parent_path()) break; cur = cur.parent_path(); } return ""; -#else - return ""; -#endif } std::string projects_dir() { diff --git a/src/desktop/web_host.cpp b/src/desktop/web_host.cpp index aa954a541..a40d591bf 100644 --- a/src/desktop/web_host.cpp +++ b/src/desktop/web_host.cpp @@ -18,10 +18,14 @@ #include #include #include +#include #ifdef _WIN32 # include #endif +#ifdef __APPLE__ +# include +#endif namespace acecode::desktop { @@ -257,6 +261,28 @@ struct ComApartment { }; #endif +namespace { + +std::pair adjusted_window_size(int width, int height) { +#ifdef __APPLE__ + CGRect bounds = CGDisplayBounds(CGMainDisplayID()); + const double display_width = bounds.size.width; + const double display_height = bounds.size.height; + if (display_width > 0 && display_height > 0) { + auto clamp_dimension = [](int value, double display, double margin, int preferred_min) { + const int max_value = static_cast(std::max(1.0, display - margin)); + const int min_value = std::min(preferred_min, max_value); + return std::max(min_value, std::min(value, max_value)); + }; + width = clamp_dimension(width, display_width, 80.0, 900); + height = clamp_dimension(height, display_height, 120.0, 640); + } +#endif + return {width, height}; +} + +} // namespace + struct WebHost::Impl { explicit Impl(bool debug, StartupWindowMode startup_mode) #ifdef _WIN32 @@ -329,7 +355,8 @@ void WebHost::set_title(const std::string& title) { impl_->w->set_title(title); } void WebHost::set_size(int width, int height) { - impl_->w->set_size(width, height, WEBVIEW_HINT_NONE); + auto adjusted = adjusted_window_size(width, height); + impl_->w->set_size(adjusted.first, adjusted.second, WEBVIEW_HINT_NONE); #ifdef _WIN32 if (impl_->custom_window) { resize_webview_widget(impl_->custom_window); diff --git a/src/desktop/web_host.hpp b/src/desktop/web_host.hpp index d20cd03d9..5263e935c 100644 --- a/src/desktop/web_host.hpp +++ b/src/desktop/web_host.hpp @@ -15,8 +15,8 @@ namespace acecode::desktop { -inline constexpr int kDefaultDesktopWindowWidth = 1664; // 1280 * 1.3 -inline constexpr int kDefaultDesktopWindowHeight = 1066; // 820 * 1.3 +inline constexpr int kDefaultDesktopWindowWidth = 1280; +inline constexpr int kDefaultDesktopWindowHeight = 820; class WebHost { public: From 424de79f41cae72e76f03b83df666bf2a9fcf2ea Mon Sep 17 00:00:00 2001 From: huangxiangxin Date: Sat, 9 May 2026 17:28:59 +0800 Subject: [PATCH 3/3] fix: build error fix --- src/desktop/single_instance_posix.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/desktop/single_instance_posix.cpp b/src/desktop/single_instance_posix.cpp index 6a063ec56..b25bcfa13 100644 --- a/src/desktop/single_instance_posix.cpp +++ b/src/desktop/single_instance_posix.cpp @@ -10,8 +10,8 @@ #ifndef _WIN32 +#include "../config/config.hpp" #include "../utils/logger.hpp" -#include "../utils/paths.hpp" #include #include @@ -27,7 +27,7 @@ namespace acecode::desktop { namespace { std::string default_lock_path() { - fs::path run_dir = fs::path(acecode::get_acecode_dir()) / "run"; + fs::path run_dir = fs::path(acecode::get_run_dir()); std::error_code ec; fs::create_directories(run_dir, ec); return (run_dir / "acecode-desktop.lock").string();