1515 */
1616package org .noear .solon .codecli .command .builtin ;
1717
18- import org .noear .snack4 .Feature ;
19- import org .noear .snack4 .ONode ;
20- import org .noear .snack4 .Options ;
2118import org .noear .solon .codecli .config .AgentFlags ;
2219import org .slf4j .Logger ;
2320import org .slf4j .LoggerFactory ;
2421
25- import java .io .OutputStreamWriter ;
26- import java .io .Writer ;
27- import java .nio .charset .StandardCharsets ;
2822import java .nio .file .*;
29- import java .time .Instant ;
3023
3124/**
32- * Loop 状态管理器 — 负责 .soloncode/loops/<loopId>/ 目录的创建、读写、清理。
33- *
34- * <p>状态目录结构:
35- * <pre>
36- * .soloncode/loops/<loopId>/
37- * └── history.json # 结构化执行历史
38- * </pre>
25+ * Loop 状态管理器 — 负责 .soloncode/loops/<loopId>/ 目录的创建、清理。
3926 *
4027 * @author noear
4128 * @since 3.9.1
4229 */
4330public class LoopStateManager {
4431 private static final Logger LOG = LoggerFactory .getLogger (LoopStateManager .class );
4532
46- private static final String HISTORY_FILE = "history.json" ;
4733 /**
4834 * 获取 loop 状态目录的根路径(.soloncode/loops/)
4935 */
@@ -59,7 +45,7 @@ public static Path getStateDir(String workspace, String loopId) {
5945 }
6046
6147 /**
62- * 初始化状态目录(创建目录和 history.json)
48+ * 初始化状态目录
6349 *
6450 * @return 状态目录路径
6551 */
@@ -68,72 +54,13 @@ public static String init(String workspace, String loopId, String prompt) {
6854 try {
6955 Files .createDirectories (stateDir );
7056
71- // 创建空的 history.json
72- if (!Files .exists (stateDir .resolve (HISTORY_FILE ))) {
73- writeFile (stateDir .resolve (HISTORY_FILE ), "[]" );
74- }
75-
7657 return stateDir .toString ();
7758 } catch (Exception e ) {
7859 LOG .warn ("Failed to init loop state dir '{}': {}" , stateDir , e .getMessage ());
7960 return stateDir .toString ();
8061 }
8162 }
8263
83- /**
84- * 追加一条执行历史
85- */
86- public static void appendHistory (String workspace , String loopId , String result , int iteration ) {
87- appendHistory (workspace , loopId , result , iteration , "NONE" );
88- }
89-
90- /**
91- * 追加一条执行历史
92- */
93- public static void appendHistory (String workspace , String loopId , String result , int iteration , String stopReason ) {
94- appendHistory (workspace , loopId , LoopExecutionResult .fromText (result ), iteration , stopReason );
95- }
96-
97- /**
98- * 追加一条结构化执行历史(含 Goal 状态)
99- */
100- public static void appendHistory (String workspace , String loopId , LoopExecutionResult result , int iteration , String stopReason ) {
101- try {
102- Path historyFile = getStateDir (workspace , loopId ).resolve (HISTORY_FILE );
103- if (!Files .exists (historyFile )) {
104- writeFile (historyFile , "[]" );
105- }
106-
107- String json = new String (Files .readAllBytes (historyFile ), StandardCharsets .UTF_8 );
108- ONode root = ONode .ofJson (json ,Feature .Write_PrettyFormat );
109- if (!root .isArray ()) {
110- root = ONode .ofJson ("[]" ,Feature .Write_PrettyFormat );
111- }
112-
113- ONode entry = new ONode ();
114- entry .set ("iteration" , iteration );
115- entry .set ("time" , Instant .now ().toString ());
116- entry .set ("result" , result != null && result .getFinalResult () != null ? result .getFinalResult () : "ok" );
117- if (result != null ) {
118- entry .set ("submitted" , result .isSubmitted ());
119- entry .set ("completed" , result .isCompleted ());
120- entry .set ("goalAchieved" , result .isGoalAchieved ());
121- if (result .getErrorMessage () != null ) entry .set ("error" , result .getErrorMessage ());
122- }
123- entry .set ("stopReason" , stopReason != null ? stopReason : "NONE" );
124-
125- // ★ P0: 记录 goal 状态(如果有)
126- // 由于这里没有 LoopTask 引用,goal 状态由调用方在 stopReason 中体现
127- // 例如:"GOAL_ACHIEVED", "BUDGET_LIMITED", "MAX_ITERATIONS_REACHED"
128-
129- root .add (entry );
130-
131- writeFile (historyFile , root .toJson ());
132- } catch (Exception e ) {
133- LOG .warn ("Failed to append history for loop '{}': {}" , loopId , e .getMessage ());
134- }
135- }
136-
13764 /**
13865 * 清理状态目录
13966 */
@@ -154,16 +81,4 @@ public static void cleanup(String workspace, String loopId) {
15481 LOG .warn ("Failed to cleanup loop state '{}': {}" , loopId , e .getMessage ());
15582 }
15683 }
157-
158- // ==================== 内部工具方法 ====================
159-
160- private static void writeFile (Path file , String content ) throws Exception {
161- Path tempFile = file .resolveSibling (file .getFileName () + ".tmp" );
162- try (Writer w = new OutputStreamWriter (Files .newOutputStream (tempFile ,
163- StandardOpenOption .CREATE , StandardOpenOption .TRUNCATE_EXISTING ),
164- StandardCharsets .UTF_8 )) {
165- w .write (content );
166- }
167- Files .move (tempFile , file , StandardCopyOption .REPLACE_EXISTING , StandardCopyOption .ATOMIC_MOVE );
168- }
16984}
0 commit comments