-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathserver.hpp
More file actions
177 lines (157 loc) · 7.95 KB
/
Copy pathserver.hpp
File metadata and controls
177 lines (157 loc) · 7.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#pragma once
// WebServer: daemon 的 HTTP/WebSocket server(openspec add-web-daemon
// Section 9 + 10)。基于 Crow,内部维护 crow::SimpleApp + 路由注册 +
// per-WS-connection 状态。
//
// 生命周期:
// - 构造时不监听端口,仅初始化路由与共享状态
// - run() 阻塞当前线程跑 Crow event loop;直到 stop() 调用或进程收到信号
// - stop() 触发 Crow app.stop(),同时关闭所有 WebSocket 连接
//
// 线程模型:
// - HTTP handler 跑在 Crow worker 线程池
// - WebSocket onmessage 跑在对应连接的 IO 线程
// - SessionClient::subscribe 注册的 listener 由 AgentLoop worker 线程触发,
// 通过 conn->send_text 发送(send 内部加锁,Crow 保证安全)
#include "../config/config.hpp"
#include "../upgrade/upgrade.hpp"
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <string>
#include <vector>
namespace acecode {
class LlmProvider;
class HookManager;
class McpManager;
class PtySessionRegistry;
class SessionClient;
class SessionRegistry;
class SkillRegistry;
class SkillUsageStore;
class ExpertRegistry;
class ToolExecutor;
} // namespace acecode
namespace acecode::loop {
class LoopStore;
}
namespace acecode::desktop {
class WorkspaceRegistry;
} // namespace acecode::desktop
namespace acecode::web {
class RemoteWebProxyController;
struct NativeSaveFilePickResult {
std::optional<std::string> path;
std::string error;
};
struct WebServerDeps {
const WebConfig* web_cfg = nullptr;
const DaemonConfig* daemon_cfg = nullptr;
AppConfig* app_config = nullptr; // mutable: /api/mcp PUT 改这个
// Optional process-wide AppConfig lock shared with SessionRegistry lazy
// model reload. Tests/embedders may omit it and use the owned fallback.
std::shared_mutex* app_config_mutex = nullptr;
// 显式 config 落盘路径。空 = 走 save_config(cfg) 默认 ~/.acecode/config.json。
// daemon worker 启动时填入实际路径;测试 fixture 必须填入临时文件,
// 否则 PUT /api/mcp 会污染真实用户配置(历史 bug,见 web_server_smoke_test)。
std::string config_path;
std::string cwd;
std::string projects_dir;
std::string no_workspace_cache_root;
std::string logs_dir;
std::string feedback_output_dir;
std::string token; // 启动期生成,空 = 不强制 (loopback only)
std::string guid;
std::int64_t pid = 0;
std::int64_t start_time_unix_ms = 0;
bool desktop_managed = false;
int desktop_protocol_version = 0;
SessionClient* session_client = nullptr;
SessionRegistry* session_registry = nullptr;
ExpertRegistry* expert_registry = nullptr;
HookManager* hook_manager = nullptr;
ToolExecutor* tools = nullptr;
// MCP 运行时管理器(daemon 持有)。/api/mcp/toggle 用它做免重启的运行时
// enable/disable;null(测试 fixture / 无 MCP)时 toggle 只落盘配置。
McpManager* mcp_manager = nullptr;
acecode::desktop::WorkspaceRegistry* workspace_registry = nullptr;
// 非 const:PUT /api/skills/:name 要写 cfg.skills.disabled 后调 set_disabled + reload。
SkillRegistry* skill_registry = nullptr;
// Skill usage/dormancy state shared with the TUI session. Null disables
// usage fields on /api/skills (headless or daemon-only processes).
SkillUsageStore* skill_usage_store = nullptr;
// Daemon-global provider handle retained for routes/fixtures that inspect
// process-level provider state. Current web session model switching is
// session-scoped through SessionRegistry.
std::shared_ptr<LlmProvider>* provider = nullptr;
std::mutex* provider_mu = nullptr;
bool native_folder_picker_enabled = false;
std::function<std::optional<std::string>()> native_folder_picker;
// 会话导出等显式写盘操作使用的原生“另存为”回调。参数是预填文件名,
// 返回值是用户确认的完整文件路径;空 path + 空 error 表示取消。
std::function<NativeSaveFilePickResult(const std::string&)> native_save_file_picker;
// POST /api/open-in-explorer 的执行回调:输入 UTF-8 绝对文件/目录路径,成功返回
// std::nullopt,失败返回错误信息。null = 端点 501(与 native_folder_picker
// 同款门控,仅 desktop 壳启动的 daemon 填入;webapp 兼容模式的右键菜单依赖它)。
std::function<std::optional<std::string>(const std::string&)> open_in_explorer;
// Remote Web connection candidates. Empty uses live interface discovery;
// tests can inject deterministic non-loopback addresses.
std::function<std::vector<std::string>()> remote_web_hosts;
// Current computer name used as the first/default remote connection.
// Empty uses live OS discovery.
std::function<std::optional<std::string>()> remote_web_computer_name;
// Daemon-owned reverse proxy lifecycle. Null means proxy-backed remote Web
// mode is unavailable (primarily legacy/test embeddings).
RemoteWebProxyController* remote_web_proxy = nullptr;
std::function<int(const AppConfig&,
acecode::upgrade::UpgradeProgressCallback,
acecode::upgrade::UpgradeCancelCheck,
std::string*)> run_update_command;
bool dangerous = false;
// Web 控制台 PTY 会话注册表(add-console-dock)。null = 控制台不可用,
// /api/health 报 console.available=false,PTY 路由 404。
PtySessionRegistry* pty_registry = nullptr;
// Daemon-owned LOOP persistence. Null keeps older/test servers compatible
// and makes /api/loops report an explicit unsupported response.
acecode::loop::LoopStore* loop_store = nullptr;
std::function<void()> on_loops_changed;
};
class WebServer {
public:
explicit WebServer(WebServerDeps deps);
~WebServer();
WebServer(const WebServer&) = delete;
WebServer& operator=(const WebServer&) = delete;
// 阻塞跑 event loop。返回 0 = 正常退出,非 0 = 启动 / 运行错误。
int run();
// 触发 Crow 优雅停服。可由信号 handler / 其它线程调用。
void stop();
// 登记一个刚 spawn 的子会话:给它挂一个不依赖 WS 客户端订阅的常驻事件
// 监听器,使其 busy/idle 转换能广播 session_status —— 否则未被任何 WS 连接
// 订阅的子会话永不广播状态,父会话前端(useSubagentTasks)在 spawn_subagent
// wait=true 阻塞期间发现不了它(既无 tool_end 也无 status),子代理的权限请求
// 永远冒泡不到主会话 UI。daemon 的 SubagentToolDeps.on_spawn 在子会话创建后、
// 首条输入入队前调用本方法。线程安全(可由 agent worker 线程调用)。
void track_subagent(const std::string& child_session_id);
// 连接器钩子改写磁盘 config.json 后,把 saved_models 重读进内存(线程安全)。
void refresh_saved_models_from_disk();
// 持 AppConfig 独占锁(Impl::app_config_mu)执行 fn —— daemon 侧非 web
// 组件(remote-control binder 等)读写 cfg_mut 时,借这里与全部 HTTP
// 路由 / 钩子刷新互斥。fn 内不得再调 WebServer 会拿该锁的方法(不可重入)。
void with_app_config_lock(const std::function<void()>& fn) const;
void broadcast_remote_control_session_selected(
const std::string& session_id,
const std::string& workspace_hash,
const std::string& cwd,
bool no_workspace,
const std::string& title,
const std::string& updated_at);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acecode::web