-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsession_manager.hpp
More file actions
322 lines (276 loc) · 14.4 KB
/
Copy pathsession_manager.hpp
File metadata and controls
322 lines (276 loc) · 14.4 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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#pragma once
#include "file_checkpoint_store.hpp"
#include "compact_checkpoint.hpp"
#include "session_storage.hpp"
#include "session_trajectory.hpp"
#include "session_writer_lease.hpp"
#include "thread_goal_store.hpp"
#include "../provider/llm_provider.hpp"
#include <string>
#include <vector>
#include <fstream>
#include <memory>
#include <mutex>
#include <optional>
namespace acecode {
enum class ArchiveCurrentSessionResult {
Archived,
NoActiveSession,
PersistenceFailed,
};
class SessionManager {
public:
// Prepare a new session (lazy: files created on first message)
void start_session(const std::string& cwd,
const std::string& provider,
const std::string& model,
const std::string& preset_session_id = "",
const std::string& model_preset = "",
const std::string& surface = "tui",
bool no_workspace = false);
// Called for each message produced during conversation.
// Appends to JSONL and periodically updates metadata.
void on_message(const ChatMessage& msg);
// Replace the current active JSONL transcript, preserving checkpoint
// metadata for user turns that remain in the supplied message list.
bool replace_active_messages(const std::vector<ChatMessage>& messages);
// Append a compact checkpoint to the current JSONL transcript without
// rewriting older human-visible rows.
bool append_compact_checkpoint(const CompactCheckpoint& checkpoint);
// File checkpoint integration for /rewind. begin_user_turn_checkpoint()
// creates the per-user snapshot; track_file_write_before() updates that
// snapshot immediately before a write tool mutates a file.
void begin_user_turn_checkpoint(const std::string& user_message_uuid);
void track_file_write_before(const std::string& file_path);
std::optional<TurnNetDiffRecord> finalize_user_turn_net_diff(
const std::string& user_message_uuid);
bool file_checkpoint_can_restore(const std::string& user_message_uuid) const;
FileCheckpointDiffStats file_checkpoint_diff_stats(const std::string& user_message_uuid) const;
FileCheckpointRestoreResult rewind_files_to_checkpoint(const std::string& user_message_uuid) const;
// Finalize current session: flush and write final metadata. Safe to call multiple times.
void finalize();
// Resume a previous session by ID. Returns loaded messages.
// Reopens the JSONL file for continued append.
std::vector<ChatMessage> resume_session(const std::string& session_id);
// Read the active session transcript without mutating session state.
// Returns empty when no active JSONL has been created yet.
std::vector<ChatMessage> load_active_messages() const;
// Read the SessionMeta for a previously persisted session by ID, without
// mutating any in-memory state. Returns empty SessionMeta (id == "") when
// the meta file is missing. Used by main.cpp's resume path so it can apply
// the persisted provider/model to the runtime LlmProvider before the
// session is re-activated. (openspec model-profiles task 6.1.)
SessionMeta load_session_meta(const std::string& session_id) const;
// True when the current project has a canonical transcript for session_id.
bool has_session_file(const std::string& session_id) const;
// True when the current project contains incompatible old PID-suffixed
// session data. Empty session_id checks for any old data in the project.
bool has_incompatible_session_data(const std::string& session_id = "") const;
// Last recoverable session error, such as a writer lease conflict.
std::string last_error() const;
// After main.cpp swaps the provider, call this so subsequent meta updates
// record the new provider/model name. Pure setter; thread-safe.
bool set_active_provider(const std::string& provider, const std::string& model);
bool set_active_provider(const std::string& provider,
const std::string& model,
const std::string& model_preset);
std::string current_model_preset() const;
// End current session (mark it done) so next on_message starts a new one.
void end_current_session();
// Fork the active session into a fresh session id containing retained_prefix
// plus retained checkpoint metadata. The previous full transcript remains
// untouched on disk. Used by /rewind (TUI) and POST /api/sessions/:id/fork
// (web; with title/forked_from/fork_message_id non-empty).
//
// 这个版本会把 manager 切到新 session,后续 on_message 写新 jsonl;
// 老文件保持只读。
std::string fork_active_session(const std::vector<ChatMessage>& retained_prefix);
// 写一个全新 session 到磁盘(JSONL + meta),不动当前 active session 状态。
// 用于 web POST /api/sessions/:id/fork:fork 操作完成后,源 session 仍然
// 是 manager 的 active session,新 session 是磁盘上独立文件,后续由调用方
// 通过 SessionRegistry 装载 + 注册另一个 SessionManager。
//
// 失败(IO 异常)时会清理半个文件,返回空字符串。
// file_checkpoint 元消息(is_meta + subtype="file_checkpoint")自动过滤,
// 新 session 不继承 checkpoint(spec 的明确决定)。
std::string fork_session_to_new_id(
const std::vector<ChatMessage>& retained_prefix,
const std::string& title,
const std::string& forked_from_id,
const std::string& fork_message_id);
// Cleanup old sessions beyond max_sessions limit.
void cleanup_old_sessions(int max_sessions);
// List sessions for the current project
std::vector<SessionMeta> list_sessions() const;
// Get current session ID (empty if no active session)
std::string current_session_id() const;
bool has_active_session() const;
// Ensure a canonical session id and metadata file exist, then return the id.
// Used by goal commands/tools, which can create state before the first chat
// message is written.
std::string ensure_active_session_id();
// Directory for full tool outputs persisted out of the prompt context.
// Creates the active session lazily, matching on_message/goal behavior.
std::string ensure_tool_results_dir();
// Narrow allowlist for file_read: persisted tool results live in ACECode's
// session store, outside cwd, but the model needs to read them back.
bool is_tool_result_artifact_path(const std::string& path) const;
ThreadGoalStore* goal_store();
ThreadGoalStore* existing_goal_store();
const ThreadGoalStore* goal_store() const;
// Set the in-memory title for the current session. Persisted to .meta.json
// on the next update_meta() (every 5 messages, or finalize). Pass empty
// string to clear. Explicit user titles take precedence over generated
// titles.
void set_session_title(std::string title);
bool try_set_generated_session_title(std::string title);
bool try_set_generated_session_title_for_session(const std::string& session_id,
std::string title);
// Start the initial hidden title request, or consume one pending retry.
// The returned text is the original visible input that the worker must use.
std::optional<std::string> begin_auto_title_generation(std::string visible_input);
// Complete one worker attempt. When the main turn already completed, a
// failed initial attempt atomically reserves and returns the single retry.
std::optional<std::string> finish_auto_title_generation_for_session(
const std::string& session_id,
bool succeeded);
// Mark the visible turn outcome. A completed turn atomically reserves and
// returns a retry when the initial title attempt already failed.
std::optional<std::string> mark_auto_title_turn_finished(
const std::string& status);
// Set the in-memory archive state for the current session and persist it
// immediately when metadata already exists.
void set_session_archived(bool archived);
// Atomically persist archived=true for the active session. Unlike the
// general setter, this reports whether an active session existed and
// whether the metadata commit succeeded so callers can gate lifecycle
// changes such as clearing the TUI.
ArchiveCurrentSessionResult archive_current_session();
// Mark the current session as a spawn_subagent child of parent_id.
// Persisted to .meta.json (immediately when metadata already exists,
// otherwise on lazy creation). Pass empty string to clear.
void set_parent_session_id(std::string parent_id);
std::string current_parent_session_id() const;
// Persist the current expert identity selected for this session.
void set_expert_binding(std::string expert_id, std::string member_id = {});
// Mutate and persist the expert binding plus an optional unsubmitted
// composer draft with one metadata update. A missing draft leaves it
// unchanged; an engaged empty string explicitly clears it.
bool set_expert_binding_and_input_draft(
std::string expert_id,
std::string member_id,
std::optional<std::string> input_draft);
std::string current_expert_id() const;
std::string current_expert_member_id() const;
// Persist the daemon-owned LOOP/run that directly created this session.
// This is provenance only and does not restore LOOP runtime policy.
void set_loop_origin(std::string loop_id, std::string loop_run_id);
// 会话当前的 worktree 状态(enter_worktree / --worktree 写入,
// exit_worktree 清空)。持久化到 .meta.json,resume 时恢复。
void set_active_worktree(const WorktreeSessionInfo& info);
void clear_active_worktree();
WorktreeSessionInfo active_worktree() const;
// Return the current in-memory title (empty when unset).
std::string current_title() const;
std::string current_title_source() const;
// Persisted unsubmitted chat input draft for the active session.
void set_input_draft(std::string draft);
std::string current_input_draft() const;
// Persisted runtime state for the active session.
void set_permission_mode(std::string mode, bool persist_immediately = true);
std::string current_permission_mode() const;
void set_pre_plan_permission_mode(std::string mode, bool persist_immediately = true);
std::string current_pre_plan_permission_mode() const;
void set_todos(std::vector<TodoItem> todos, bool persist_immediately = true);
std::vector<TodoItem> current_todos() const;
std::string ensure_plan_file_path();
std::string current_plan_file_path() const;
std::string read_plan_file() const;
bool write_plan_file(const std::string& content, std::string* error = nullptr);
bool is_plan_file_path(const std::string& path) const;
void record_token_usage(const TokenUsage& usage);
TokenUsage current_last_token_usage() const;
TokenUsage current_session_token_usage() const;
int current_turn_count() const;
// Append one model-invisible diagnostic record to the active session's
// trajectory sidecar. timestamp_ms <= 0 uses the current system clock.
// Failure is non-fatal for the canonical transcript and is reported by
// the return value plus a warning log.
bool record_trajectory_event(
std::string type,
nlohmann::json payload = nlohmann::json::object(),
std::int64_t timestamp_ms = 0);
// Resolved sidecar path for the current session. Empty until a session id
// has been allocated.
std::string current_trajectory_path() const;
private:
bool ensure_created(); // Lazy creation of session files on first message
bool update_meta(
std::optional<std::string> updated_at_override = std::nullopt);
bool try_set_generated_session_title_locked(std::string title);
// Adopt a user title that another process persisted while this one held
// the session in memory (Desktop's per-workspace daemon pool). Called
// before every meta write so the in-memory title never silently
// overwrites a rename that landed on disk from elsewhere. An explicit
// local title write is the only operation allowed to outrank the disk.
void adopt_foreign_user_title_locked();
void reset_auto_title_state_locked();
std::string extract_summary(const std::string& content) const;
bool acquire_writer_lease_locked();
void refresh_writer_lease_locked();
void release_writer_lease_locked();
bool record_trajectory_event_locked(
std::string type,
nlohmann::json payload,
std::int64_t timestamp_ms);
std::string cwd_;
std::string provider_name_;
std::string model_name_;
std::string model_preset_;
std::string surface_ = "tui";
bool no_workspace_ = false;
std::string project_dir_;
std::string session_id_;
std::string jsonl_path_;
std::string meta_path_str_;
std::uint64_t trajectory_sequence_ = 0;
bool trajectory_sequence_initialized_ = false;
bool started_ = false; // start_session() called
bool created_ = false; // Files actually created (lazy)
bool finalized_ = false; // finalize() called
int message_count_ = 0;
int turn_count_ = 0;
std::string last_user_summary_;
std::string created_at_;
std::string pending_title_;
std::string title_source_;
std::string auto_title_input_;
std::string auto_title_session_id_;
int auto_title_generation_attempts_ = 0;
int auto_title_cycle_turn_count_ = 0;
bool auto_title_generation_in_flight_ = false;
bool auto_title_retry_pending_ = false;
bool auto_title_first_turn_completed_ = false;
bool auto_title_cycle_exhausted_ = false;
bool user_title_touched_ = false;
bool local_user_title_write_pending_ = false;
std::string input_draft_;
std::string permission_mode_ = "default";
std::string pre_plan_permission_mode_;
TokenUsage last_token_usage_;
TokenUsage session_token_usage_;
std::vector<TodoItem> todos_;
std::string last_error_;
bool writer_lease_active_ = false;
bool archived_ = false;
std::string parent_session_id_;
std::string expert_id_;
std::string expert_member_id_;
std::string loop_id_;
std::string loop_run_id_;
WorktreeSessionInfo worktree_;
FileCheckpointStore checkpoint_store_;
std::unique_ptr<ThreadGoalStore> goal_store_;
mutable std::mutex mu_;
};
} // namespace acecode