@yuaone/core 0.9.7 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/dist/__tests__/context-manager.test.js +5 -9
  2. package/dist/__tests__/context-manager.test.js.map +1 -1
  3. package/dist/agent-coordinator.d.ts +172 -0
  4. package/dist/agent-coordinator.d.ts.map +1 -0
  5. package/dist/agent-coordinator.js +390 -0
  6. package/dist/agent-coordinator.js.map +1 -0
  7. package/dist/agent-loop.d.ts +83 -29
  8. package/dist/agent-loop.d.ts.map +1 -1
  9. package/dist/agent-loop.js +697 -335
  10. package/dist/agent-loop.js.map +1 -1
  11. package/dist/agent-reputation.d.ts +72 -0
  12. package/dist/agent-reputation.d.ts.map +1 -0
  13. package/dist/agent-reputation.js +222 -0
  14. package/dist/agent-reputation.js.map +1 -0
  15. package/dist/arch-summarizer.d.ts +48 -0
  16. package/dist/arch-summarizer.d.ts.map +1 -0
  17. package/dist/arch-summarizer.js +239 -0
  18. package/dist/arch-summarizer.js.map +1 -0
  19. package/dist/autonomous/explicit-planner.d.ts +45 -0
  20. package/dist/autonomous/explicit-planner.d.ts.map +1 -0
  21. package/dist/autonomous/explicit-planner.js +99 -0
  22. package/dist/autonomous/explicit-planner.js.map +1 -0
  23. package/dist/autonomous/incident-debugger.d.ts +78 -0
  24. package/dist/autonomous/incident-debugger.d.ts.map +1 -0
  25. package/dist/autonomous/incident-debugger.js +324 -0
  26. package/dist/autonomous/incident-debugger.js.map +1 -0
  27. package/dist/autonomous/index.d.ts +15 -0
  28. package/dist/autonomous/index.d.ts.map +1 -0
  29. package/dist/autonomous/index.js +10 -0
  30. package/dist/autonomous/index.js.map +1 -0
  31. package/dist/autonomous/patch-tournament.d.ts +82 -0
  32. package/dist/autonomous/patch-tournament.d.ts.map +1 -0
  33. package/dist/autonomous/patch-tournament.js +150 -0
  34. package/dist/autonomous/patch-tournament.js.map +1 -0
  35. package/dist/autonomous/research-agent.d.ts +66 -0
  36. package/dist/autonomous/research-agent.d.ts.map +1 -0
  37. package/dist/autonomous/research-agent.js +210 -0
  38. package/dist/autonomous/research-agent.js.map +1 -0
  39. package/dist/autonomous/task-memory.d.ts +63 -0
  40. package/dist/autonomous/task-memory.d.ts.map +1 -0
  41. package/dist/autonomous/task-memory.js +143 -0
  42. package/dist/autonomous/task-memory.js.map +1 -0
  43. package/dist/budget-governor-v2.d.ts +93 -0
  44. package/dist/budget-governor-v2.d.ts.map +1 -0
  45. package/dist/budget-governor-v2.js +345 -0
  46. package/dist/budget-governor-v2.js.map +1 -0
  47. package/dist/capability-graph.d.ts +102 -0
  48. package/dist/capability-graph.d.ts.map +1 -0
  49. package/dist/capability-graph.js +397 -0
  50. package/dist/capability-graph.js.map +1 -0
  51. package/dist/capability-self-model.d.ts +144 -0
  52. package/dist/capability-self-model.d.ts.map +1 -0
  53. package/dist/capability-self-model.js +312 -0
  54. package/dist/capability-self-model.js.map +1 -0
  55. package/dist/checkpoint-manager.d.ts +94 -0
  56. package/dist/checkpoint-manager.d.ts.map +1 -0
  57. package/dist/checkpoint-manager.js +225 -0
  58. package/dist/checkpoint-manager.js.map +1 -0
  59. package/dist/continuation-engine.js +1 -1
  60. package/dist/continuation-engine.js.map +1 -1
  61. package/dist/dag-orchestrator.d.ts +0 -3
  62. package/dist/dag-orchestrator.d.ts.map +1 -1
  63. package/dist/dag-orchestrator.js +0 -1
  64. package/dist/dag-orchestrator.js.map +1 -1
  65. package/dist/evidence-chain.d.ts +99 -0
  66. package/dist/evidence-chain.d.ts.map +1 -0
  67. package/dist/evidence-chain.js +200 -0
  68. package/dist/evidence-chain.js.map +1 -0
  69. package/dist/execution-engine.d.ts.map +1 -1
  70. package/dist/execution-engine.js +0 -1
  71. package/dist/execution-engine.js.map +1 -1
  72. package/dist/failure-signature-memory.d.ts +61 -0
  73. package/dist/failure-signature-memory.d.ts.map +1 -0
  74. package/dist/failure-signature-memory.js +278 -0
  75. package/dist/failure-signature-memory.js.map +1 -0
  76. package/dist/index.d.ts +52 -5
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +48 -7
  79. package/dist/index.js.map +1 -1
  80. package/dist/language-detector.d.ts.map +1 -1
  81. package/dist/language-detector.js +122 -43
  82. package/dist/language-detector.js.map +1 -1
  83. package/dist/llm-client.d.ts +0 -7
  84. package/dist/llm-client.d.ts.map +1 -1
  85. package/dist/llm-client.js +15 -122
  86. package/dist/llm-client.js.map +1 -1
  87. package/dist/mcp-client.js +1 -1
  88. package/dist/mcp-client.js.map +1 -1
  89. package/dist/memory.d.ts.map +1 -1
  90. package/dist/memory.js +0 -15
  91. package/dist/memory.js.map +1 -1
  92. package/dist/meta-learning-collector.d.ts +64 -0
  93. package/dist/meta-learning-collector.d.ts.map +1 -0
  94. package/dist/meta-learning-collector.js +169 -0
  95. package/dist/meta-learning-collector.js.map +1 -0
  96. package/dist/meta-learning-engine.d.ts +61 -0
  97. package/dist/meta-learning-engine.d.ts.map +1 -0
  98. package/dist/meta-learning-engine.js +250 -0
  99. package/dist/meta-learning-engine.js.map +1 -0
  100. package/dist/overhead-governor.d.ts +105 -0
  101. package/dist/overhead-governor.d.ts.map +1 -0
  102. package/dist/overhead-governor.js +239 -0
  103. package/dist/overhead-governor.js.map +1 -0
  104. package/dist/playbook-library.d.ts +75 -0
  105. package/dist/playbook-library.d.ts.map +1 -0
  106. package/dist/playbook-library.js +241 -0
  107. package/dist/playbook-library.js.map +1 -0
  108. package/dist/project-executive.d.ts +97 -0
  109. package/dist/project-executive.d.ts.map +1 -0
  110. package/dist/project-executive.js +223 -0
  111. package/dist/project-executive.js.map +1 -0
  112. package/dist/research-loop.d.ts +79 -0
  113. package/dist/research-loop.d.ts.map +1 -0
  114. package/dist/research-loop.js +363 -0
  115. package/dist/research-loop.js.map +1 -0
  116. package/dist/resolve-memory-path.d.ts +32 -0
  117. package/dist/resolve-memory-path.d.ts.map +1 -0
  118. package/dist/resolve-memory-path.js +97 -0
  119. package/dist/resolve-memory-path.js.map +1 -0
  120. package/dist/safe-bounds.d.ts +101 -0
  121. package/dist/safe-bounds.d.ts.map +1 -0
  122. package/dist/safe-bounds.js +140 -0
  123. package/dist/safe-bounds.js.map +1 -0
  124. package/dist/sandbox-tiers.d.ts +5 -0
  125. package/dist/sandbox-tiers.d.ts.map +1 -1
  126. package/dist/sandbox-tiers.js +14 -6
  127. package/dist/sandbox-tiers.js.map +1 -1
  128. package/dist/security.d.ts.map +1 -1
  129. package/dist/security.js +3 -0
  130. package/dist/security.js.map +1 -1
  131. package/dist/self-improvement-loop.d.ts +64 -0
  132. package/dist/self-improvement-loop.d.ts.map +1 -0
  133. package/dist/self-improvement-loop.js +156 -0
  134. package/dist/self-improvement-loop.js.map +1 -0
  135. package/dist/session-persistence.d.ts +5 -0
  136. package/dist/session-persistence.d.ts.map +1 -1
  137. package/dist/session-persistence.js +19 -3
  138. package/dist/session-persistence.js.map +1 -1
  139. package/dist/skill-loader.d.ts +16 -9
  140. package/dist/skill-loader.d.ts.map +1 -1
  141. package/dist/skill-loader.js +52 -116
  142. package/dist/skill-loader.js.map +1 -1
  143. package/dist/skill-registry.d.ts +60 -0
  144. package/dist/skill-registry.d.ts.map +1 -0
  145. package/dist/skill-registry.js +162 -0
  146. package/dist/skill-registry.js.map +1 -0
  147. package/dist/stall-detector.d.ts +56 -0
  148. package/dist/stall-detector.d.ts.map +1 -0
  149. package/dist/stall-detector.js +142 -0
  150. package/dist/stall-detector.js.map +1 -0
  151. package/dist/strategy-learner.d.ts +57 -0
  152. package/dist/strategy-learner.d.ts.map +1 -0
  153. package/dist/strategy-learner.js +160 -0
  154. package/dist/strategy-learner.js.map +1 -0
  155. package/dist/strategy-market.d.ts +73 -0
  156. package/dist/strategy-market.d.ts.map +1 -0
  157. package/dist/strategy-market.js +200 -0
  158. package/dist/strategy-market.js.map +1 -0
  159. package/dist/sub-agent.d.ts +0 -3
  160. package/dist/sub-agent.d.ts.map +1 -1
  161. package/dist/sub-agent.js +0 -10
  162. package/dist/sub-agent.js.map +1 -1
  163. package/dist/system-prompt.d.ts +0 -2
  164. package/dist/system-prompt.d.ts.map +1 -1
  165. package/dist/system-prompt.js +97 -490
  166. package/dist/system-prompt.js.map +1 -1
  167. package/dist/task-classifier.d.ts.map +1 -1
  168. package/dist/task-classifier.js +2 -54
  169. package/dist/task-classifier.js.map +1 -1
  170. package/dist/tool-synthesizer.d.ts +149 -0
  171. package/dist/tool-synthesizer.d.ts.map +1 -0
  172. package/dist/tool-synthesizer.js +455 -0
  173. package/dist/tool-synthesizer.js.map +1 -0
  174. package/dist/trace-pattern-extractor.d.ts +76 -0
  175. package/dist/trace-pattern-extractor.d.ts.map +1 -0
  176. package/dist/trace-pattern-extractor.js +321 -0
  177. package/dist/trace-pattern-extractor.js.map +1 -0
  178. package/dist/trace-recorder.d.ts +38 -0
  179. package/dist/trace-recorder.d.ts.map +1 -0
  180. package/dist/trace-recorder.js +94 -0
  181. package/dist/trace-recorder.js.map +1 -0
  182. package/dist/trust-economics.d.ts +50 -0
  183. package/dist/trust-economics.d.ts.map +1 -0
  184. package/dist/trust-economics.js +148 -0
  185. package/dist/trust-economics.js.map +1 -0
  186. package/dist/types.d.ts +272 -3
  187. package/dist/types.d.ts.map +1 -1
  188. package/dist/types.js.map +1 -1
  189. package/dist/yuan-md-loader.d.ts +22 -0
  190. package/dist/yuan-md-loader.d.ts.map +1 -0
  191. package/dist/yuan-md-loader.js +75 -0
  192. package/dist/yuan-md-loader.js.map +1 -0
  193. package/package.json +1 -1
@@ -0,0 +1,312 @@
1
+ /**
2
+ * @module capability-self-model
3
+ * @description Capability-Aware Self Model — tracks tool availability, environment
4
+ * strengths/weaknesses, and generates self-assessments for planning.
5
+ *
6
+ * Reduces overconfidence and improves plan quality by surfacing:
7
+ * - Which tools are available and which require approval
8
+ * - Which environment+taskType combinations have low success rates
9
+ * - Human-readable planning constraints derived from recorded outcomes
10
+ *
11
+ * Storage: ~/.yuan/self-model/capability-state.json
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const model = new CapabilitySelfModel();
16
+ *
17
+ * // Record outcomes over time
18
+ * model.recordOutcome("typescript", "bugfix", true);
19
+ * model.recordOutcome("python", "refactor", false);
20
+ *
21
+ * // Check tool availability before planning
22
+ * const check = model.canUse("shell_exec");
23
+ * // { allowed: true, requiresApproval: true }
24
+ *
25
+ * // Generate self-assessment at planning time
26
+ * const assessment = model.assess();
27
+ * // assessment.planningConstraints → ["Weak at Python refactor (40% success)", ...]
28
+ * // assessment.overallConfidence → 0.67
29
+ * ```
30
+ */
31
+ import { EventEmitter } from "node:events";
32
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
33
+ import { homedir } from "node:os";
34
+ import { join } from "node:path";
35
+ const DEFAULT_TOOLS = {
36
+ read_file: { requiresApproval: false, isReadOnly: true },
37
+ write_file: { requiresApproval: true, isReadOnly: false },
38
+ shell_exec: { requiresApproval: true, isReadOnly: false },
39
+ grep: { requiresApproval: false, isReadOnly: true },
40
+ glob: { requiresApproval: false, isReadOnly: true },
41
+ git_ops: { requiresApproval: true, isReadOnly: false },
42
+ };
43
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
44
+ /** Laplace-smoothed success rate: (successes + 1) / (total + 2) */
45
+ function laplaceRate(successCount, totalCount) {
46
+ return (successCount + 1) / (totalCount + 2);
47
+ }
48
+ /** Derive a rating from a smoothed success rate and raw sample count. */
49
+ function deriveRating(successRate, sampleCount) {
50
+ if (sampleCount < 3)
51
+ return "unknown";
52
+ if (successRate >= 0.75)
53
+ return "strong";
54
+ if (successRate >= 0.50)
55
+ return "adequate";
56
+ if (successRate >= 0.25)
57
+ return "weak";
58
+ return "weak";
59
+ }
60
+ /** Composite key for environment+taskType lookup. */
61
+ function envKey(environment, taskType) {
62
+ return `${environment}::${taskType}`;
63
+ }
64
+ // ─── Class ───────────────────────────────────────────────────────────────────
65
+ /**
66
+ * Capability-Aware Self Model.
67
+ *
68
+ * Maintains an in-memory + persisted record of:
69
+ * 1. Tool availability (registered via `registerTool`)
70
+ * 2. Environment+taskType success rates (updated via `recordOutcome`)
71
+ *
72
+ * Emits `agent:self_model_updated` whenever state changes.
73
+ */
74
+ export class CapabilitySelfModel extends EventEmitter {
75
+ storageFile;
76
+ tools;
77
+ environments;
78
+ constructor(storageDir) {
79
+ super();
80
+ const dir = storageDir ?? join(homedir(), ".yuan", "self-model");
81
+ this.storageFile = join(dir, "capability-state.json");
82
+ const loaded = this._load(dir);
83
+ this.tools = loaded.tools;
84
+ this.environments = loaded.environments;
85
+ // Pre-register default tools only if they weren't already in persisted state.
86
+ for (const [name, opts] of Object.entries(DEFAULT_TOOLS)) {
87
+ if (!this.tools.has(name)) {
88
+ this._upsertTool(name, {
89
+ requiresApproval: opts.requiresApproval,
90
+ isReadOnly: opts.isReadOnly,
91
+ isAvailable: true,
92
+ });
93
+ }
94
+ }
95
+ }
96
+ // ─── Public API ────────────────────────────────────────────────────────────
97
+ /**
98
+ * Register (or update) a tool's availability metadata.
99
+ *
100
+ * @param toolName - Unique tool identifier.
101
+ * @param opts - Approval/read-only/availability flags.
102
+ */
103
+ registerTool(toolName, opts) {
104
+ this._upsertTool(toolName, opts);
105
+ this._save();
106
+ this.emit("agent:self_model_updated", {
107
+ kind: "agent:self_model_updated",
108
+ action: "tool_registered",
109
+ toolName,
110
+ timestamp: new Date().toISOString(),
111
+ });
112
+ }
113
+ /**
114
+ * Record a success or failure for a specific environment+taskType combination.
115
+ * Updates the Laplace-smoothed success rate and re-derives the rating.
116
+ *
117
+ * @param environment - e.g. "typescript", "python"
118
+ * @param taskType - e.g. "bugfix", "refactor"
119
+ * @param success - Whether the task succeeded.
120
+ */
121
+ recordOutcome(environment, taskType, success) {
122
+ const key = envKey(environment, taskType);
123
+ const existing = this.environments.get(key) ?? {
124
+ environment,
125
+ taskType,
126
+ successRate: 0.5,
127
+ sampleCount: 0,
128
+ rating: "unknown",
129
+ };
130
+ // Track raw counts (store them as hidden fields via type cast).
131
+ const raw = existing;
132
+ const successCount = (raw._successCount ?? 0) + (success ? 1 : 0);
133
+ const totalCount = (raw._totalCount ?? 0) + 1;
134
+ const smoothed = laplaceRate(successCount, totalCount);
135
+ const rating = deriveRating(smoothed, totalCount);
136
+ const updated = {
137
+ environment,
138
+ taskType,
139
+ successRate: smoothed,
140
+ sampleCount: totalCount,
141
+ rating,
142
+ _successCount: successCount,
143
+ _totalCount: totalCount,
144
+ };
145
+ this.environments.set(key, updated);
146
+ this._save();
147
+ this.emit("agent:self_model_updated", {
148
+ kind: "agent:self_model_updated",
149
+ action: "outcome_recorded",
150
+ environment,
151
+ timestamp: new Date().toISOString(),
152
+ });
153
+ }
154
+ /**
155
+ * Generate a self-assessment snapshot.
156
+ * Intended to be called at planning time to inform plan quality.
157
+ */
158
+ assess() {
159
+ const now = new Date().toISOString();
160
+ const availableTools = Array.from(this.tools.values());
161
+ const allEnvs = Array.from(this.environments.values());
162
+ const strengths = allEnvs.filter((e) => e.rating === "strong" || e.rating === "adequate");
163
+ const weaknesses = allEnvs.filter((e) => e.rating === "weak" || (e.rating === "unknown" && e.sampleCount >= 3));
164
+ const planningConstraints = this._buildConstraints(availableTools, allEnvs);
165
+ const overallConfidence = this._computeConfidence(allEnvs);
166
+ return {
167
+ availableTools,
168
+ strengths,
169
+ weaknesses,
170
+ planningConstraints,
171
+ overallConfidence,
172
+ generatedAt: now,
173
+ };
174
+ }
175
+ /**
176
+ * Check whether a specific tool can be used.
177
+ *
178
+ * @param toolName - Tool to check.
179
+ * @returns `{ allowed, requiresApproval, reason? }`
180
+ */
181
+ canUse(toolName) {
182
+ const tool = this.tools.get(toolName);
183
+ if (!tool) {
184
+ return {
185
+ allowed: false,
186
+ requiresApproval: false,
187
+ reason: `Tool "${toolName}" is not registered.`,
188
+ };
189
+ }
190
+ if (!tool.isAvailable) {
191
+ return {
192
+ allowed: false,
193
+ requiresApproval: tool.requiresApproval,
194
+ reason: `Tool "${toolName}" is currently unavailable.`,
195
+ };
196
+ }
197
+ return {
198
+ allowed: true,
199
+ requiresApproval: tool.requiresApproval,
200
+ };
201
+ }
202
+ /**
203
+ * Return weak and unknown (with sufficient samples) environment+taskType areas.
204
+ * Use this to add caution to planning prompts.
205
+ */
206
+ getWeakAreas() {
207
+ return Array.from(this.environments.values()).filter((e) => e.rating === "weak" ||
208
+ (e.rating === "unknown" && e.sampleCount >= 3));
209
+ }
210
+ // ─── Internal ──────────────────────────────────────────────────────────────
211
+ /** Upsert a tool into the in-memory map (does NOT save or emit). */
212
+ _upsertTool(toolName, opts) {
213
+ const now = new Date().toISOString();
214
+ const existing = this.tools.get(toolName);
215
+ this.tools.set(toolName, {
216
+ toolName,
217
+ isAvailable: opts.isAvailable ?? existing?.isAvailable ?? true,
218
+ requiresApproval: opts.requiresApproval,
219
+ isReadOnly: opts.isReadOnly,
220
+ lastChecked: now,
221
+ });
222
+ }
223
+ /**
224
+ * Build human-readable planning constraints from tool and environment state.
225
+ */
226
+ _buildConstraints(tools, envs) {
227
+ const constraints = [];
228
+ // Tool constraints
229
+ for (const tool of tools) {
230
+ if (!tool.isAvailable) {
231
+ constraints.push(`Tool "${tool.toolName}" is currently unavailable.`);
232
+ }
233
+ else if (tool.requiresApproval) {
234
+ constraints.push(`"${tool.toolName}" requires approval before use.`);
235
+ }
236
+ }
237
+ // Environment weakness constraints
238
+ for (const env of envs) {
239
+ if (env.rating === "weak") {
240
+ const pct = Math.round(env.successRate * 100);
241
+ constraints.push(`Weak at ${env.environment} ${env.taskType} (${pct}% success, n=${env.sampleCount}) — apply extra caution.`);
242
+ }
243
+ else if (env.rating === "unknown" && env.sampleCount >= 3) {
244
+ const pct = Math.round(env.successRate * 100);
245
+ constraints.push(`Uncertain performance in ${env.environment} ${env.taskType} (${pct}% success, n=${env.sampleCount}).`);
246
+ }
247
+ }
248
+ return constraints;
249
+ }
250
+ /**
251
+ * Compute overall confidence as a weighted average of environment success rates.
252
+ * Weights are proportional to sample counts.
253
+ * Returns 0.5 if no environment data is available.
254
+ */
255
+ _computeConfidence(envs) {
256
+ if (envs.length === 0)
257
+ return 0.5;
258
+ let weightedSum = 0;
259
+ let totalWeight = 0;
260
+ for (const env of envs) {
261
+ const w = Math.max(env.sampleCount, 1);
262
+ weightedSum += env.successRate * w;
263
+ totalWeight += w;
264
+ }
265
+ return totalWeight > 0 ? weightedSum / totalWeight : 0.5;
266
+ }
267
+ /** Load persisted state from disk. Non-fatal on any error. */
268
+ _load(storageDir) {
269
+ const empty = {
270
+ tools: new Map(),
271
+ environments: new Map(),
272
+ };
273
+ try {
274
+ if (!existsSync(storageDir)) {
275
+ mkdirSync(storageDir, { recursive: true });
276
+ }
277
+ if (!existsSync(this.storageFile))
278
+ return empty;
279
+ const raw = readFileSync(this.storageFile, "utf8");
280
+ const parsed = JSON.parse(raw);
281
+ const tools = new Map();
282
+ for (const t of parsed.tools ?? []) {
283
+ tools.set(t.toolName, t);
284
+ }
285
+ const environments = new Map();
286
+ for (const e of parsed.environments ?? []) {
287
+ environments.set(envKey(e.environment, e.taskType), e);
288
+ }
289
+ return { tools, environments };
290
+ }
291
+ catch {
292
+ // Non-fatal: storage failures should not crash the agent loop
293
+ return empty;
294
+ }
295
+ }
296
+ /** Atomically persist current state to disk. Non-fatal on any error. */
297
+ _save() {
298
+ const tmpFile = `${this.storageFile}.tmp`;
299
+ try {
300
+ const state = {
301
+ tools: Array.from(this.tools.values()),
302
+ environments: Array.from(this.environments.values()),
303
+ };
304
+ writeFileSync(tmpFile, JSON.stringify(state, null, 2), "utf8");
305
+ renameSync(tmpFile, this.storageFile);
306
+ }
307
+ catch {
308
+ // Non-fatal: storage failures should not crash the agent loop
309
+ }
310
+ }
311
+ }
312
+ //# sourceMappingURL=capability-self-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-self-model.js","sourceRoot":"","sources":["../src/capability-self-model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @module checkpoint-manager
3
+ * @description Task-level Checkpoint / Rollback / Resume Manager (Phase 6).
4
+ *
5
+ * Saves git-backed snapshots at key task milestones so that interrupted tasks
6
+ * can be resumed or rolled back mid-execution. Complements session-persistence
7
+ * (which handles session-level replay) — this module focuses on intra-task
8
+ * rollback using git stash as a workspace snapshot mechanism.
9
+ *
10
+ * Storage: ~/.yuan/checkpoints/{taskId}.json (array of TaskCheckpoint)
11
+ * All writes are atomic (write to .tmp then renameSync).
12
+ * All git operations are wrapped in try/catch — git failures are non-fatal.
13
+ */
14
+ import { EventEmitter } from "node:events";
15
+ export interface TaskCheckpoint {
16
+ /** UUID */
17
+ id: string;
18
+ taskId: string;
19
+ sessionId: string;
20
+ /** Human-readable label, e.g. "after_plan", "pre_verification" */
21
+ label: string;
22
+ iteration: number;
23
+ completedSteps: string[];
24
+ pendingSteps: string[];
25
+ /** git stash hash or tag captured at save time, if git is available */
26
+ gitRef?: string;
27
+ /** Serializable agent state snapshot */
28
+ agentState: Record<string, unknown>;
29
+ tokenUsed: number;
30
+ createdAt: string;
31
+ }
32
+ export interface RollbackResult {
33
+ success: boolean;
34
+ restoredTo: string;
35
+ gitRestored: boolean;
36
+ error?: string;
37
+ }
38
+ export interface CheckpointManagerConfig {
39
+ /** Directory where checkpoint files are stored. Default: ~/.yuan/checkpoints */
40
+ storageDir?: string;
41
+ /** Project root used for git operations. Default: process.cwd() */
42
+ projectPath?: string;
43
+ /** Max checkpoints kept per task (oldest removed first). Default: 5 */
44
+ maxCheckpoints?: number;
45
+ }
46
+ export declare class CheckpointManager extends EventEmitter {
47
+ private readonly storageDir;
48
+ private readonly projectPath;
49
+ private readonly maxCheckpoints;
50
+ constructor(config?: CheckpointManagerConfig);
51
+ /**
52
+ * Save a checkpoint at current state.
53
+ * Attempts a git stash to capture workspace state; falls back gracefully if
54
+ * git is unavailable or the working tree is clean.
55
+ */
56
+ save(taskId: string, sessionId: string, label: string, state: {
57
+ iteration: number;
58
+ completedSteps: string[];
59
+ pendingSteps: string[];
60
+ agentState: Record<string, unknown>;
61
+ tokenUsed: number;
62
+ }): Promise<TaskCheckpoint>;
63
+ /**
64
+ * List all checkpoints for a task, oldest first.
65
+ */
66
+ list(taskId: string): TaskCheckpoint[];
67
+ /**
68
+ * Return the most recent checkpoint for a task, or null if none.
69
+ */
70
+ latest(taskId: string): TaskCheckpoint | null;
71
+ /**
72
+ * Roll back workspace to the state captured in a checkpoint.
73
+ * If `gitRef` is present, applies the stash via `git stash apply`.
74
+ */
75
+ rollback(checkpointId: string): Promise<RollbackResult>;
76
+ /**
77
+ * Delete all checkpoints for a task (call after successful completion).
78
+ */
79
+ clear(taskId: string): void;
80
+ /**
81
+ * Attempt `git stash` and return the stash ref (e.g. "stash@{0}").
82
+ * Returns undefined if git is unavailable, not a repo, or the tree is clean.
83
+ */
84
+ private tryGitStash;
85
+ private checkpointFilePath;
86
+ private loadFile;
87
+ private writeFile;
88
+ /**
89
+ * Scan all checkpoint files to find a checkpoint by its ID.
90
+ * Returns the checkpoint and its taskId, or undefined if not found.
91
+ */
92
+ private findCheckpoint;
93
+ }
94
+ //# sourceMappingURL=checkpoint-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-manager.d.ts","sourceRoot":"","sources":["../src/checkpoint-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS3C,MAAM,WAAW,cAAc;IAC7B,WAAW;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,MAAM,WAAW,uBAAuB;IACtC,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,MAAM,GAAE,uBAA4B;IAWhD;;;;OAIG;IACG,IAAI,CACR,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,SAAS,EAAE,MAAM,CAAC;KACnB,GACA,OAAO,CAAC,cAAc,CAAC;IAqC1B;;OAEG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE;IAItC;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAM7C;;;OAGG;IACG,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA6C7D;;OAEG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAa3B;;;OAGG;IACH,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,SAAS;IASjB;;;OAGG;IACH,OAAO,CAAC,cAAc;CA+BvB"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * @module checkpoint-manager
3
+ * @description Task-level Checkpoint / Rollback / Resume Manager (Phase 6).
4
+ *
5
+ * Saves git-backed snapshots at key task milestones so that interrupted tasks
6
+ * can be resumed or rolled back mid-execution. Complements session-persistence
7
+ * (which handles session-level replay) — this module focuses on intra-task
8
+ * rollback using git stash as a workspace snapshot mechanism.
9
+ *
10
+ * Storage: ~/.yuan/checkpoints/{taskId}.json (array of TaskCheckpoint)
11
+ * All writes are atomic (write to .tmp then renameSync).
12
+ * All git operations are wrapped in try/catch — git failures are non-fatal.
13
+ */
14
+ import { EventEmitter } from "node:events";
15
+ import * as fs from "node:fs";
16
+ import * as path from "node:path";
17
+ import * as os from "node:os";
18
+ import { execSync } from "node:child_process";
19
+ import { randomUUID } from "node:crypto";
20
+ export class CheckpointManager extends EventEmitter {
21
+ storageDir;
22
+ projectPath;
23
+ maxCheckpoints;
24
+ constructor(config = {}) {
25
+ super();
26
+ this.storageDir =
27
+ config.storageDir ?? path.join(os.homedir(), ".yuan", "checkpoints");
28
+ this.projectPath = config.projectPath ?? process.cwd();
29
+ this.maxCheckpoints = config.maxCheckpoints ?? 5;
30
+ fs.mkdirSync(this.storageDir, { recursive: true });
31
+ }
32
+ // ─── Public API ─────────────────────────────────────────────────────────────
33
+ /**
34
+ * Save a checkpoint at current state.
35
+ * Attempts a git stash to capture workspace state; falls back gracefully if
36
+ * git is unavailable or the working tree is clean.
37
+ */
38
+ async save(taskId, sessionId, label, state) {
39
+ const gitRef = this.tryGitStash();
40
+ const checkpoint = {
41
+ id: randomUUID(),
42
+ taskId,
43
+ sessionId,
44
+ label,
45
+ iteration: state.iteration,
46
+ completedSteps: [...state.completedSteps],
47
+ pendingSteps: [...state.pendingSteps],
48
+ agentState: { ...state.agentState },
49
+ tokenUsed: state.tokenUsed,
50
+ createdAt: new Date().toISOString(),
51
+ ...(gitRef !== undefined ? { gitRef } : {}),
52
+ };
53
+ // Load existing, append, cap, and write back atomically.
54
+ const existing = this.loadFile(taskId);
55
+ existing.push(checkpoint);
56
+ if (existing.length > this.maxCheckpoints) {
57
+ existing.splice(0, existing.length - this.maxCheckpoints);
58
+ }
59
+ this.writeFile(taskId, existing);
60
+ this.emit("event", {
61
+ kind: "agent:checkpoint_saved",
62
+ checkpointId: checkpoint.id,
63
+ taskId,
64
+ label,
65
+ iteration: state.iteration,
66
+ timestamp: Date.now(),
67
+ });
68
+ return checkpoint;
69
+ }
70
+ /**
71
+ * List all checkpoints for a task, oldest first.
72
+ */
73
+ list(taskId) {
74
+ return this.loadFile(taskId);
75
+ }
76
+ /**
77
+ * Return the most recent checkpoint for a task, or null if none.
78
+ */
79
+ latest(taskId) {
80
+ const checkpoints = this.loadFile(taskId);
81
+ if (checkpoints.length === 0)
82
+ return null;
83
+ return checkpoints[checkpoints.length - 1];
84
+ }
85
+ /**
86
+ * Roll back workspace to the state captured in a checkpoint.
87
+ * If `gitRef` is present, applies the stash via `git stash apply`.
88
+ */
89
+ async rollback(checkpointId) {
90
+ // Search all task files for the checkpoint.
91
+ const found = this.findCheckpoint(checkpointId);
92
+ if (!found) {
93
+ return {
94
+ success: false,
95
+ restoredTo: "",
96
+ gitRestored: false,
97
+ error: `Checkpoint ${checkpointId} not found`,
98
+ };
99
+ }
100
+ const { checkpoint } = found;
101
+ let gitRestored = false;
102
+ let error;
103
+ if (checkpoint.gitRef) {
104
+ try {
105
+ execSync(`git stash apply ${checkpoint.gitRef}`, {
106
+ cwd: this.projectPath,
107
+ stdio: "pipe",
108
+ });
109
+ gitRestored = true;
110
+ }
111
+ catch (err) {
112
+ error = err instanceof Error ? err.message : String(err);
113
+ }
114
+ }
115
+ this.emit("event", {
116
+ kind: "agent:checkpoint_restored",
117
+ checkpointId,
118
+ taskId: checkpoint.taskId,
119
+ label: checkpoint.label,
120
+ gitRestored,
121
+ timestamp: Date.now(),
122
+ });
123
+ return {
124
+ success: true,
125
+ restoredTo: checkpoint.label,
126
+ gitRestored,
127
+ ...(error !== undefined ? { error } : {}),
128
+ };
129
+ }
130
+ /**
131
+ * Delete all checkpoints for a task (call after successful completion).
132
+ */
133
+ clear(taskId) {
134
+ const filePath = this.checkpointFilePath(taskId);
135
+ try {
136
+ if (fs.existsSync(filePath)) {
137
+ fs.unlinkSync(filePath);
138
+ }
139
+ }
140
+ catch {
141
+ // Non-fatal: best-effort cleanup.
142
+ }
143
+ }
144
+ // ─── Private Helpers ────────────────────────────────────────────────────────
145
+ /**
146
+ * Attempt `git stash` and return the stash ref (e.g. "stash@{0}").
147
+ * Returns undefined if git is unavailable, not a repo, or the tree is clean.
148
+ */
149
+ tryGitStash() {
150
+ try {
151
+ const output = execSync("git stash", {
152
+ cwd: this.projectPath,
153
+ stdio: "pipe",
154
+ encoding: "utf-8",
155
+ });
156
+ // "No local changes to save" means nothing was stashed.
157
+ if (output.includes("No local changes to save"))
158
+ return undefined;
159
+ // git stash outputs "Saved working directory and index state ..."
160
+ // The most recent stash is always stash@{0}.
161
+ return "stash@{0}";
162
+ }
163
+ catch {
164
+ return undefined;
165
+ }
166
+ }
167
+ checkpointFilePath(taskId) {
168
+ // Sanitize taskId to prevent path traversal.
169
+ const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
170
+ return path.join(this.storageDir, `${safe}.json`);
171
+ }
172
+ loadFile(taskId) {
173
+ const filePath = this.checkpointFilePath(taskId);
174
+ try {
175
+ if (!fs.existsSync(filePath))
176
+ return [];
177
+ const raw = fs.readFileSync(filePath, "utf-8");
178
+ const parsed = JSON.parse(raw);
179
+ return Array.isArray(parsed) ? parsed : [];
180
+ }
181
+ catch {
182
+ return [];
183
+ }
184
+ }
185
+ writeFile(taskId, checkpoints) {
186
+ const filePath = this.checkpointFilePath(taskId);
187
+ const tmpPath = `${filePath}.${Date.now()}-${Math.random()
188
+ .toString(36)
189
+ .slice(2, 8)}.tmp`;
190
+ fs.writeFileSync(tmpPath, JSON.stringify(checkpoints), "utf-8");
191
+ fs.renameSync(tmpPath, filePath);
192
+ }
193
+ /**
194
+ * Scan all checkpoint files to find a checkpoint by its ID.
195
+ * Returns the checkpoint and its taskId, or undefined if not found.
196
+ */
197
+ findCheckpoint(checkpointId) {
198
+ let files;
199
+ try {
200
+ files = fs
201
+ .readdirSync(this.storageDir)
202
+ .filter((f) => f.endsWith(".json"));
203
+ }
204
+ catch {
205
+ return undefined;
206
+ }
207
+ for (const file of files) {
208
+ try {
209
+ const raw = fs.readFileSync(path.join(this.storageDir, file), "utf-8");
210
+ const checkpoints = JSON.parse(raw);
211
+ if (!Array.isArray(checkpoints))
212
+ continue;
213
+ const match = checkpoints.find((c) => c.id === checkpointId);
214
+ if (match) {
215
+ return { checkpoint: match, taskId: match.taskId };
216
+ }
217
+ }
218
+ catch {
219
+ continue;
220
+ }
221
+ }
222
+ return undefined;
223
+ }
224
+ }
225
+ //# sourceMappingURL=checkpoint-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-manager.js","sourceRoot":"","sources":["../src/checkpoint-manager.ts"],"names":[],"mappings":"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"}
@@ -116,7 +116,7 @@ export class ContinuationEngine {
116
116
  ...safeCheckpoint,
117
117
  createdAt: (safeCheckpoint.createdAt ?? new Date()).toISOString(),
118
118
  };
119
- const json = JSON.stringify(serialized, null, 2);
119
+ const json = JSON.stringify(serialized);
120
120
  // Atomic write: tmp에 쓰고 rename
121
121
  await writeFile(tmpPath, json, "utf-8");
122
122
  await rename(tmpPath, filePath);