-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsubagent_host.cpp
More file actions
187 lines (172 loc) · 6.98 KB
/
Copy pathsubagent_host.cpp
File metadata and controls
187 lines (172 loc) · 6.98 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
178
179
180
181
182
183
184
185
186
187
#include "subagent_host.hpp"
#include "../agent_loop.hpp"
#include "../session/session_manager.hpp"
#include "../session/session_storage.hpp"
#include "../session/session_user_message_search.hpp"
#include "../utils/logger.hpp"
#include <algorithm>
namespace acecode::tui {
SubagentHost::SubagentHost(Deps deps)
: registry_(deps.registry_deps), client_(registry_), deps_(std::move(deps)) {}
void SubagentHost::on_spawned(const std::string& child_id,
const std::string& prompt) {
if (child_id.empty()) return;
{
std::lock_guard<std::mutex> lk(mu_);
running_.push_back({child_id, "", prompt,
std::chrono::steady_clock::now()});
publish_locked();
}
// 订阅子会话事件流。回调在子会话 loop 线程触发;不显式退订 ——
// dispatcher 生命周期 = SessionEntry,destroy/clear 时一起消亡。
//
// 竞态兜底:on_spawn 在 send_input 之后被调用,极快的 turn(stub /
// 瞬时失败)可能在订阅建立前就发完 BusyChanged(false)。订阅后补查一次:
// 已空闲且已有 assistant 答复 → 视为已结束,直接移除。
client_.subscribe(child_id, [this, child_id](const SessionEvent& evt) {
switch (evt.kind) {
case SessionEventKind::BusyChanged:
// 右侧列只显示运行中:本轮结束即移除(用户决策)。
if (!evt.payload.value("busy", false)) {
remove_task(child_id);
}
break;
case SessionEventKind::SessionUpdated: {
const std::string title =
evt.payload.value("title", std::string{});
if (!title.empty()) update_title(child_id, title);
break;
}
case SessionEventKind::PermissionRequest:
if (deps_.on_permission_request) {
deps_.on_permission_request(child_id, title_for(child_id),
evt.payload);
}
break;
default:
break;
}
});
if (auto entry = registry_.acquire(child_id)) {
if (entry->loop && !entry->loop->is_busy() && entry->sm) {
for (const auto& msg : entry->sm->load_active_messages()) {
if (msg.role == "assistant" && !msg.content.empty()) {
remove_task(child_id);
break;
}
}
}
}
}
std::vector<SubagentTaskSnapshot> SubagentHost::running_tasks() const {
std::lock_guard<std::mutex> lk(mu_);
return running_;
}
std::vector<SubagentHost::TaskListEntry>
SubagentHost::list_tasks(const std::string& project_dir) const {
const std::string parent =
deps_.parent_session_id ? deps_.parent_session_id() : std::string{};
std::vector<TaskListEntry> out;
std::vector<std::string> running_ids;
{
std::lock_guard<std::mutex> lk(mu_);
for (const auto& t : running_) {
out.push_back({t.id, t.title.empty() ? t.prompt : t.title,
/*running=*/true, 0});
running_ids.push_back(t.id);
}
}
if (!parent.empty() && !project_dir.empty()) {
for (const auto& meta : SessionStorage::list_sessions(project_dir)) {
if (meta.parent_session_id != parent) continue;
if (std::find(running_ids.begin(), running_ids.end(), meta.id) !=
running_ids.end()) {
continue;
}
out.push_back({meta.id,
meta.title.empty() ? meta.summary : meta.title,
/*running=*/false, meta.message_count});
}
}
return out;
}
bool SubagentHost::abort_task(const std::string& id) {
auto entry = registry_.acquire(id);
if (!entry || !entry->loop) return false;
entry->loop->abort();
// BusyChanged(false) 事件随后到达并移除任务;这里不提前动列表,
// 避免「中止请求发出但子会话还在收尾」期间右侧列消失误导用户。
return true;
}
int SubagentHost::clear_settled(const std::string& project_dir) {
const std::string parent =
deps_.parent_session_id ? deps_.parent_session_id() : std::string{};
if (parent.empty() || project_dir.empty()) return 0;
std::vector<std::string> running_ids;
{
std::lock_guard<std::mutex> lk(mu_);
for (const auto& t : running_) running_ids.push_back(t.id);
}
int removed = 0;
SessionUserMessageIndex search_index(project_dir);
for (const auto& meta : SessionStorage::list_sessions(project_dir)) {
if (meta.parent_session_id != parent) continue;
if (std::find(running_ids.begin(), running_ids.end(), meta.id) !=
running_ids.end()) {
continue;
}
registry_.destroy(meta.id); // 不在 registry 时是 no-op
SessionStorage::purge_session_files(project_dir, meta.id);
{
// 与 Web 端 purge 一致:永久删除必须连用户消息搜索索引一起清,
// 否则子会话的用户输入全文残留在索引数据库。
std::string index_error;
if (!search_index.remove_session(meta.id, &index_error)) {
LOG_WARN("[subagent] purge failed to remove search index for " +
meta.id + ": " + index_error);
}
}
++removed;
LOG_INFO("[subagent] purged settled task " + meta.id);
}
return removed;
}
void SubagentHost::respond_permission(const std::string& session_id,
const std::string& request_id,
const std::string& choice) {
PermissionDecision decision;
decision.request_id = request_id;
decision.choice =
parse_permission_choice(choice).value_or(PermissionDecisionChoice::Deny);
client_.respond_permission(session_id, decision);
}
void SubagentHost::publish_locked() {
if (deps_.publish_tasks) deps_.publish_tasks(running_);
}
void SubagentHost::remove_task(const std::string& id) {
std::lock_guard<std::mutex> lk(mu_);
const auto before = running_.size();
running_.erase(std::remove_if(running_.begin(), running_.end(),
[&](const auto& t) { return t.id == id; }),
running_.end());
if (running_.size() != before) publish_locked();
}
void SubagentHost::update_title(const std::string& id,
const std::string& title) {
std::lock_guard<std::mutex> lk(mu_);
for (auto& t : running_) {
if (t.id == id && t.title != title) {
t.title = title;
publish_locked();
return;
}
}
}
std::string SubagentHost::title_for(const std::string& id) const {
std::lock_guard<std::mutex> lk(mu_);
for (const auto& t : running_) {
if (t.id == id) return t.title.empty() ? t.prompt : t.title;
}
return id;
}
} // namespace acecode::tui