gencode-ai 0.1.3 → 0.3.0

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 (260) hide show
  1. package/README.md +2 -1
  2. package/dist/agent/agent.d.ts +44 -2
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +130 -11
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/types.d.ts +11 -1
  7. package/dist/agent/types.d.ts.map +1 -1
  8. package/dist/checkpointing/checkpoint-manager.d.ts +87 -0
  9. package/dist/checkpointing/checkpoint-manager.d.ts.map +1 -0
  10. package/dist/checkpointing/checkpoint-manager.js +281 -0
  11. package/dist/checkpointing/checkpoint-manager.js.map +1 -0
  12. package/dist/checkpointing/index.d.ts +29 -0
  13. package/dist/checkpointing/index.d.ts.map +1 -0
  14. package/dist/checkpointing/index.js +29 -0
  15. package/dist/checkpointing/index.js.map +1 -0
  16. package/dist/checkpointing/types.d.ts +98 -0
  17. package/dist/checkpointing/types.d.ts.map +1 -0
  18. package/dist/checkpointing/types.js +7 -0
  19. package/dist/checkpointing/types.js.map +1 -0
  20. package/dist/cli/components/App.d.ts.map +1 -1
  21. package/dist/cli/components/App.js +171 -14
  22. package/dist/cli/components/App.js.map +1 -1
  23. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  24. package/dist/cli/components/CommandSuggestions.js +5 -0
  25. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  26. package/dist/cli/components/Messages.d.ts +7 -1
  27. package/dist/cli/components/Messages.d.ts.map +1 -1
  28. package/dist/cli/components/Messages.js +12 -3
  29. package/dist/cli/components/Messages.js.map +1 -1
  30. package/dist/cli/components/ModeIndicator.d.ts +42 -0
  31. package/dist/cli/components/ModeIndicator.d.ts.map +1 -0
  32. package/dist/cli/components/ModeIndicator.js +52 -0
  33. package/dist/cli/components/ModeIndicator.js.map +1 -0
  34. package/dist/cli/components/ModelSelector.d.ts +4 -3
  35. package/dist/cli/components/ModelSelector.d.ts.map +1 -1
  36. package/dist/cli/components/ModelSelector.js +54 -37
  37. package/dist/cli/components/ModelSelector.js.map +1 -1
  38. package/dist/cli/components/PlanApproval.d.ts +36 -0
  39. package/dist/cli/components/PlanApproval.d.ts.map +1 -0
  40. package/dist/cli/components/PlanApproval.js +154 -0
  41. package/dist/cli/components/PlanApproval.js.map +1 -0
  42. package/dist/cli/components/ProviderManager.d.ts +2 -2
  43. package/dist/cli/components/ProviderManager.d.ts.map +1 -1
  44. package/dist/cli/components/ProviderManager.js +137 -156
  45. package/dist/cli/components/ProviderManager.js.map +1 -1
  46. package/dist/cli/components/theme.d.ts +2 -0
  47. package/dist/cli/components/theme.d.ts.map +1 -1
  48. package/dist/cli/components/theme.js +3 -0
  49. package/dist/cli/components/theme.js.map +1 -1
  50. package/dist/cli/index.js +30 -13
  51. package/dist/cli/index.js.map +1 -1
  52. package/dist/config/index.d.ts +2 -2
  53. package/dist/config/index.d.ts.map +1 -1
  54. package/dist/config/index.js +1 -1
  55. package/dist/config/index.js.map +1 -1
  56. package/dist/config/levels.d.ts +5 -5
  57. package/dist/config/levels.d.ts.map +1 -1
  58. package/dist/config/levels.js +20 -20
  59. package/dist/config/levels.js.map +1 -1
  60. package/dist/config/merger.js +1 -1
  61. package/dist/config/merger.js.map +1 -1
  62. package/dist/config/providers-config.d.ts +8 -5
  63. package/dist/config/providers-config.d.ts.map +1 -1
  64. package/dist/config/providers-config.js +19 -22
  65. package/dist/config/providers-config.js.map +1 -1
  66. package/dist/config/test-utils.d.ts +2 -2
  67. package/dist/config/test-utils.d.ts.map +1 -1
  68. package/dist/config/test-utils.js +4 -4
  69. package/dist/config/test-utils.js.map +1 -1
  70. package/dist/config/types.d.ts +23 -17
  71. package/dist/config/types.d.ts.map +1 -1
  72. package/dist/config/types.js +14 -14
  73. package/dist/config/types.js.map +1 -1
  74. package/dist/index.d.ts +1 -0
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +2 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/memory/memory-manager.d.ts +25 -12
  79. package/dist/memory/memory-manager.d.ts.map +1 -1
  80. package/dist/memory/memory-manager.js +241 -112
  81. package/dist/memory/memory-manager.js.map +1 -1
  82. package/dist/memory/test-utils.d.ts +1 -1
  83. package/dist/memory/test-utils.d.ts.map +1 -1
  84. package/dist/memory/test-utils.js +3 -3
  85. package/dist/memory/test-utils.js.map +1 -1
  86. package/dist/memory/types.d.ts +20 -10
  87. package/dist/memory/types.d.ts.map +1 -1
  88. package/dist/memory/types.js +13 -13
  89. package/dist/memory/types.js.map +1 -1
  90. package/dist/migration/migrate.d.ts +24 -0
  91. package/dist/migration/migrate.d.ts.map +1 -0
  92. package/dist/migration/migrate.js +164 -0
  93. package/dist/migration/migrate.js.map +1 -0
  94. package/dist/permissions/persistence.d.ts +2 -2
  95. package/dist/permissions/persistence.js +4 -4
  96. package/dist/permissions/persistence.js.map +1 -1
  97. package/dist/planning/index.d.ts +13 -0
  98. package/dist/planning/index.d.ts.map +1 -0
  99. package/dist/planning/index.js +15 -0
  100. package/dist/planning/index.js.map +1 -0
  101. package/dist/planning/plan-file.d.ts +59 -0
  102. package/dist/planning/plan-file.d.ts.map +1 -0
  103. package/dist/planning/plan-file.js +278 -0
  104. package/dist/planning/plan-file.js.map +1 -0
  105. package/dist/planning/state.d.ts +127 -0
  106. package/dist/planning/state.d.ts.map +1 -0
  107. package/dist/planning/state.js +261 -0
  108. package/dist/planning/state.js.map +1 -0
  109. package/dist/planning/tools/enter-plan-mode.d.ts +25 -0
  110. package/dist/planning/tools/enter-plan-mode.d.ts.map +1 -0
  111. package/dist/planning/tools/enter-plan-mode.js +98 -0
  112. package/dist/planning/tools/enter-plan-mode.js.map +1 -0
  113. package/dist/planning/tools/exit-plan-mode.d.ts +24 -0
  114. package/dist/planning/tools/exit-plan-mode.d.ts.map +1 -0
  115. package/dist/planning/tools/exit-plan-mode.js +149 -0
  116. package/dist/planning/tools/exit-plan-mode.js.map +1 -0
  117. package/dist/planning/types.d.ts +100 -0
  118. package/dist/planning/types.d.ts.map +1 -0
  119. package/dist/planning/types.js +28 -0
  120. package/dist/planning/types.js.map +1 -0
  121. package/dist/pricing/calculator.d.ts +21 -0
  122. package/dist/pricing/calculator.d.ts.map +1 -0
  123. package/dist/pricing/calculator.js +59 -0
  124. package/dist/pricing/calculator.js.map +1 -0
  125. package/dist/pricing/index.d.ts +7 -0
  126. package/dist/pricing/index.d.ts.map +1 -0
  127. package/dist/pricing/index.js +7 -0
  128. package/dist/pricing/index.js.map +1 -0
  129. package/dist/pricing/models.d.ts +20 -0
  130. package/dist/pricing/models.d.ts.map +1 -0
  131. package/dist/pricing/models.js +322 -0
  132. package/dist/pricing/models.js.map +1 -0
  133. package/dist/pricing/types.d.ts +30 -0
  134. package/dist/pricing/types.d.ts.map +1 -0
  135. package/dist/pricing/types.js +5 -0
  136. package/dist/pricing/types.js.map +1 -0
  137. package/dist/prompts/index.d.ts +5 -4
  138. package/dist/prompts/index.d.ts.map +1 -1
  139. package/dist/prompts/index.js +11 -8
  140. package/dist/prompts/index.js.map +1 -1
  141. package/dist/providers/anthropic.d.ts +2 -1
  142. package/dist/providers/anthropic.d.ts.map +1 -1
  143. package/dist/providers/anthropic.js +24 -10
  144. package/dist/providers/anthropic.js.map +1 -1
  145. package/dist/providers/gemini.d.ts +2 -1
  146. package/dist/providers/gemini.d.ts.map +1 -1
  147. package/dist/providers/gemini.js +28 -14
  148. package/dist/providers/gemini.js.map +1 -1
  149. package/dist/providers/index.d.ts +20 -10
  150. package/dist/providers/index.d.ts.map +1 -1
  151. package/dist/providers/index.js +48 -24
  152. package/dist/providers/index.js.map +1 -1
  153. package/dist/providers/openai.d.ts +2 -1
  154. package/dist/providers/openai.d.ts.map +1 -1
  155. package/dist/providers/openai.js +19 -8
  156. package/dist/providers/openai.js.map +1 -1
  157. package/dist/providers/registry.d.ts +48 -34
  158. package/dist/providers/registry.d.ts.map +1 -1
  159. package/dist/providers/registry.js +72 -88
  160. package/dist/providers/registry.js.map +1 -1
  161. package/dist/providers/store.d.ts +43 -17
  162. package/dist/providers/store.d.ts.map +1 -1
  163. package/dist/providers/store.js +112 -19
  164. package/dist/providers/store.js.map +1 -1
  165. package/dist/providers/types.d.ts +25 -0
  166. package/dist/providers/types.d.ts.map +1 -1
  167. package/dist/providers/vertex-ai.d.ts +15 -7
  168. package/dist/providers/vertex-ai.d.ts.map +1 -1
  169. package/dist/providers/vertex-ai.js +63 -23
  170. package/dist/providers/vertex-ai.js.map +1 -1
  171. package/dist/session/manager.d.ts +4 -0
  172. package/dist/session/manager.d.ts.map +1 -1
  173. package/dist/session/manager.js +8 -0
  174. package/dist/session/manager.js.map +1 -1
  175. package/dist/session/types.js +1 -1
  176. package/dist/session/types.js.map +1 -1
  177. package/dist/tools/index.d.ts +7 -1
  178. package/dist/tools/index.d.ts.map +1 -1
  179. package/dist/tools/index.js +7 -0
  180. package/dist/tools/index.js.map +1 -1
  181. package/dist/tools/registry.d.ts +13 -0
  182. package/dist/tools/registry.d.ts.map +1 -1
  183. package/dist/tools/registry.js +79 -2
  184. package/dist/tools/registry.js.map +1 -1
  185. package/docs/config-system-comparison.md +50 -50
  186. package/docs/cost-tracking-comparison.md +904 -0
  187. package/docs/memory-system.md +124 -31
  188. package/docs/operating-modes.md +96 -0
  189. package/docs/permissions.md +2 -2
  190. package/docs/proposals/0006-memory-system.md +4 -4
  191. package/docs/proposals/0008-checkpointing.md +109 -2
  192. package/docs/proposals/0011-custom-commands.md +2 -1
  193. package/docs/proposals/0021-skills-system.md +2 -1
  194. package/docs/proposals/0023-permission-enhancements.md +2 -2
  195. package/docs/proposals/0025-cost-tracking.md +60 -2
  196. package/docs/proposals/0033-enterprise-deployment.md +1 -1
  197. package/docs/proposals/0041-configuration-system.md +17 -19
  198. package/docs/proposals/0042-prompt-optimization.md +17 -9
  199. package/docs/proposals/README.md +6 -6
  200. package/docs/providers.md +94 -9
  201. package/examples/test-checkpointing.ts +121 -0
  202. package/examples/test-cost-tracking.ts +77 -0
  203. package/examples/test-interrupt-cleanup.ts +94 -0
  204. package/package.json +3 -2
  205. package/scripts/migrate.ts +449 -0
  206. package/src/agent/agent.ts +161 -12
  207. package/src/agent/types.ts +11 -1
  208. package/src/checkpointing/checkpoint-manager.ts +327 -0
  209. package/src/checkpointing/index.ts +45 -0
  210. package/src/checkpointing/types.ts +104 -0
  211. package/src/cli/components/App.tsx +221 -13
  212. package/src/cli/components/CommandSuggestions.tsx +5 -0
  213. package/src/cli/components/Messages.tsx +24 -5
  214. package/src/cli/components/ModeIndicator.tsx +174 -0
  215. package/src/cli/components/ModelSelector.tsx +62 -43
  216. package/src/cli/components/PlanApproval.tsx +327 -0
  217. package/src/cli/components/ProviderManager.tsx +278 -323
  218. package/src/cli/components/theme.ts +3 -0
  219. package/src/cli/index.tsx +36 -17
  220. package/src/config/index.ts +5 -3
  221. package/src/config/levels.test.ts +22 -22
  222. package/src/config/levels.ts +22 -22
  223. package/src/config/loader.test.ts +14 -14
  224. package/src/config/manager.test.ts +19 -19
  225. package/src/config/merger.test.ts +23 -23
  226. package/src/config/merger.ts +1 -1
  227. package/src/config/providers-config.ts +23 -21
  228. package/src/config/test-utils.ts +6 -6
  229. package/src/config/types.ts +30 -20
  230. package/src/index.ts +15 -0
  231. package/src/memory/memory-manager.test.ts +242 -24
  232. package/src/memory/memory-manager.ts +270 -141
  233. package/src/memory/test-utils.ts +4 -4
  234. package/src/memory/types.ts +28 -17
  235. package/src/permissions/persistence.ts +4 -4
  236. package/src/planning/index.ts +53 -0
  237. package/src/planning/plan-file.ts +326 -0
  238. package/src/planning/state.ts +305 -0
  239. package/src/planning/tools/enter-plan-mode.ts +111 -0
  240. package/src/planning/tools/exit-plan-mode.ts +170 -0
  241. package/src/planning/types.ts +150 -0
  242. package/src/pricing/calculator.ts +71 -0
  243. package/src/pricing/index.ts +7 -0
  244. package/src/pricing/models.ts +334 -0
  245. package/src/pricing/types.ts +32 -0
  246. package/src/prompts/index.ts +13 -9
  247. package/src/providers/anthropic.ts +30 -10
  248. package/src/providers/gemini.ts +34 -14
  249. package/src/providers/index.ts +76 -33
  250. package/src/providers/openai.ts +26 -8
  251. package/src/providers/registry.ts +116 -111
  252. package/src/providers/store.ts +130 -28
  253. package/src/providers/types.ts +36 -1
  254. package/src/providers/vertex-ai.ts +70 -23
  255. package/src/session/manager.ts +9 -0
  256. package/src/session/types.ts +1 -1
  257. package/src/tools/index.ts +8 -0
  258. package/src/tools/registry.ts +95 -2
  259. package/.gencode/settings.local.json +0 -7
  260. package/CLAUDE.md +0 -86
@@ -2,8 +2,8 @@
2
2
  * Agent - Core agent implementation with tool loop and session support
3
3
  */
4
4
 
5
- import type { LLMProvider, Message, ToolResultContent } from '../providers/types.js';
6
- import { createProvider, inferProvider } from '../providers/index.js';
5
+ import type { LLMProvider, Message, ToolResultContent, Provider, AuthMethod } from '../providers/types.js';
6
+ import { createProvider, inferProvider, inferAuthMethod } from '../providers/index.js';
7
7
  import { ToolRegistry, createDefaultRegistry } from '../tools/index.js';
8
8
  import {
9
9
  PermissionManager,
@@ -17,6 +17,12 @@ import { MemoryManager, type LoadedMemory } from '../memory/index.js';
17
17
  import type { AgentConfig, AgentEvent } from './types.js';
18
18
  import { buildSystemPromptForModel, debugPromptLoading } from '../prompts/index.js';
19
19
  import type { Question, QuestionAnswer } from '../tools/types.js';
20
+ import {
21
+ getPlanModeManager,
22
+ type PlanModeManager,
23
+ type ModeType,
24
+ type AllowedPrompt,
25
+ } from '../planning/index.js';
20
26
 
21
27
  // Type for askUser callback
22
28
  export type AskUserCallback = (questions: Question[]) => Promise<QuestionAnswer[]>;
@@ -27,6 +33,7 @@ export class Agent {
27
33
  private permissions: PermissionManager;
28
34
  private sessionManager: SessionManager;
29
35
  private memoryManager: MemoryManager;
36
+ private planModeManager: PlanModeManager;
30
37
  private config: AgentConfig;
31
38
  private messages: Message[] = [];
32
39
  private sessionId: string | null = null;
@@ -40,7 +47,10 @@ export class Agent {
40
47
  ...config,
41
48
  };
42
49
 
43
- this.provider = createProvider({ provider: config.provider });
50
+ this.provider = createProvider({
51
+ provider: config.provider,
52
+ authMethod: config.authMethod,
53
+ });
44
54
  this.registry = createDefaultRegistry();
45
55
  this.permissions = new PermissionManager({
46
56
  config: config.permissions,
@@ -48,6 +58,7 @@ export class Agent {
48
58
  });
49
59
  this.sessionManager = new SessionManager();
50
60
  this.memoryManager = new MemoryManager();
61
+ this.planModeManager = getPlanModeManager();
51
62
  }
52
63
 
53
64
  /**
@@ -133,10 +144,99 @@ export class Agent {
133
144
  */
134
145
  async loadMemory(): Promise<LoadedMemory> {
135
146
  const cwd = this.config.cwd ?? process.cwd();
136
- this.loadedMemory = await this.memoryManager.load({ cwd });
147
+
148
+ // Determine memory merge strategy (priority: env var > config > default)
149
+ const envStrategy = process.env.GEN_MEMORY_STRATEGY as
150
+ | 'fallback'
151
+ | 'both'
152
+ | 'gen-only'
153
+ | 'claude-only'
154
+ | undefined;
155
+ const strategy =
156
+ envStrategy ?? this.config.memoryMergeStrategy ?? 'fallback';
157
+
158
+ this.loadedMemory = await this.memoryManager.load({ cwd, strategy });
159
+
160
+ // Log verbose summary if verbose mode is enabled
161
+ if (this.config.verbose) {
162
+ console.log(this.memoryManager.getVerboseSummary(strategy));
163
+ }
164
+
137
165
  return this.loadedMemory;
138
166
  }
139
167
 
168
+ // ============================================================================
169
+ // Plan Mode
170
+ // ============================================================================
171
+
172
+ /**
173
+ * Get plan mode manager for external access
174
+ */
175
+ getPlanModeManager(): PlanModeManager {
176
+ return this.planModeManager;
177
+ }
178
+
179
+ /**
180
+ * Check if plan mode is active
181
+ */
182
+ isPlanModeActive(): boolean {
183
+ return this.planModeManager.isActive();
184
+ }
185
+
186
+ /**
187
+ * Get current mode (build or plan)
188
+ */
189
+ getCurrentMode(): ModeType {
190
+ return this.planModeManager.getCurrentMode();
191
+ }
192
+
193
+ /**
194
+ * Enter plan mode programmatically
195
+ */
196
+ async enterPlanMode(taskDescription?: string): Promise<string> {
197
+ const { createPlanFile } = await import('../planning/index.js');
198
+ const cwd = this.config.cwd ?? process.cwd();
199
+ const planFile = await createPlanFile(cwd, taskDescription);
200
+ this.planModeManager.enter(planFile.path, taskDescription);
201
+ return planFile.path;
202
+ }
203
+
204
+ /**
205
+ * Exit plan mode
206
+ */
207
+ exitPlanMode(approved: boolean = false): void {
208
+ if (approved) {
209
+ // Add allowed prompts from plan mode to permissions
210
+ const allowedPrompts = this.planModeManager.getRequestedPermissions();
211
+ if (allowedPrompts.length > 0) {
212
+ this.permissions.addAllowedPrompts(allowedPrompts);
213
+ }
214
+ }
215
+ this.planModeManager.exit(approved);
216
+ }
217
+
218
+ /**
219
+ * Toggle plan mode
220
+ */
221
+ async togglePlanMode(): Promise<void> {
222
+ if (this.planModeManager.isActive()) {
223
+ this.planModeManager.exit(false);
224
+ } else {
225
+ await this.enterPlanMode();
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Get plan mode requested permissions
231
+ */
232
+ getPlanModePermissions(): AllowedPrompt[] {
233
+ return this.planModeManager.getRequestedPermissions();
234
+ }
235
+
236
+ // ============================================================================
237
+ // Session Management
238
+ // ============================================================================
239
+
140
240
  /**
141
241
  * Get current session ID
142
242
  */
@@ -153,15 +253,30 @@ export class Agent {
153
253
 
154
254
  /**
155
255
  * Set the model to use (auto-switches provider if needed)
256
+ * @param model Model ID to use
257
+ * @param provider Optional: explicit provider (otherwise inferred from model name)
258
+ * @param authMethod Optional: explicit auth method (otherwise inferred or use current)
156
259
  */
157
- setModel(model: string): void {
260
+ setModel(model: string, provider?: string, authMethod?: string): void {
158
261
  this.config.model = model;
159
262
 
160
- // Auto-switch provider based on model name
161
- const newProvider = inferProvider(model);
162
- if (newProvider !== this.config.provider) {
263
+ // Determine new provider and authMethod
264
+ const newProvider = (provider as Provider | undefined) ?? inferProvider(model);
265
+ const newAuthMethod = (authMethod as AuthMethod | undefined) ??
266
+ inferAuthMethod(model) ??
267
+ this.config.authMethod;
268
+
269
+ // Recreate provider if either provider or authMethod changed
270
+ const providerChanged = newProvider !== this.config.provider;
271
+ const authMethodChanged = newAuthMethod !== this.config.authMethod;
272
+
273
+ if (providerChanged || authMethodChanged) {
163
274
  this.config.provider = newProvider;
164
- this.provider = createProvider({ provider: newProvider });
275
+ this.config.authMethod = newAuthMethod;
276
+ this.provider = createProvider({
277
+ provider: newProvider,
278
+ authMethod: newAuthMethod,
279
+ });
165
280
  }
166
281
  }
167
282
 
@@ -172,6 +287,13 @@ export class Agent {
172
287
  return this.config.model;
173
288
  }
174
289
 
290
+ /**
291
+ * Get current provider
292
+ */
293
+ getProvider(): Provider {
294
+ return this.config.provider;
295
+ }
296
+
175
297
  /**
176
298
  * List available models from the provider API
177
299
  */
@@ -290,8 +412,8 @@ export class Agent {
290
412
  while (turns < maxTurns) {
291
413
  turns++;
292
414
 
293
- // Get tool definitions
294
- const toolDefs = this.registry.getDefinitions(this.config.tools);
415
+ // Get tool definitions (filtered by plan mode if active)
416
+ const toolDefs = this.registry.getFilteredDefinitions(this.config.tools);
295
417
 
296
418
  // Call LLM
297
419
  let response;
@@ -341,7 +463,7 @@ export class Agent {
341
463
  await this.sessionManager.addMessage({ role: 'assistant', content: response.content });
342
464
 
343
465
  if (response.stopReason !== 'tool_use' || toolCalls.length === 0) {
344
- yield { type: 'done', text: textContent };
466
+ yield { type: 'done', text: textContent, usage: response.usage, cost: response.cost };
345
467
  return;
346
468
  }
347
469
 
@@ -388,6 +510,33 @@ export class Agent {
388
510
  this.sessionManager.clearMessages();
389
511
  }
390
512
 
513
+ /**
514
+ * Clean up incomplete tool use messages (after interruption)
515
+ * Removes the last assistant message if it contains tool_use without corresponding tool_result
516
+ */
517
+ cleanupIncompleteMessages(): void {
518
+ if (this.messages.length === 0) return;
519
+
520
+ const lastMessage = this.messages[this.messages.length - 1];
521
+
522
+ // Check if last message is an assistant message with tool_use
523
+ if (lastMessage.role === 'assistant' && Array.isArray(lastMessage.content)) {
524
+ const hasToolUse = lastMessage.content.some((c) => c.type === 'tool_use');
525
+
526
+ if (hasToolUse) {
527
+ // Remove the incomplete assistant message
528
+ this.messages.pop();
529
+
530
+ // Also remove from session manager
531
+ // Note: SessionManager should have corresponding cleanup method
532
+ const messages = this.sessionManager.getMessages();
533
+ if (messages.length > 0 && messages[messages.length - 1].role === 'assistant') {
534
+ this.sessionManager.removeLastMessage();
535
+ }
536
+ }
537
+ }
538
+ }
539
+
391
540
  /**
392
541
  * Get conversation history
393
542
  */
@@ -3,15 +3,20 @@
3
3
  */
4
4
 
5
5
  import type { PermissionConfig } from '../permissions/types.js';
6
+ import type { CostEstimate } from '../pricing/types.js';
7
+ import type { Provider, AuthMethod } from '../providers/types.js';
6
8
 
7
9
  export interface AgentConfig {
8
- provider: 'openai' | 'anthropic' | 'gemini' | 'vertex-ai';
10
+ provider: Provider;
11
+ authMethod?: AuthMethod;
9
12
  model: string;
10
13
  systemPrompt?: string;
11
14
  tools?: string[];
12
15
  cwd?: string;
13
16
  maxTurns?: number;
14
17
  permissions?: Partial<PermissionConfig>;
18
+ memoryMergeStrategy?: 'fallback' | 'both' | 'gen-only' | 'claude-only';
19
+ verbose?: boolean;
15
20
  }
16
21
 
17
22
  // Agent Events
@@ -59,6 +64,11 @@ export interface AgentEventError {
59
64
  export interface AgentEventDone {
60
65
  type: 'done';
61
66
  text: string;
67
+ usage?: {
68
+ inputTokens: number;
69
+ outputTokens: number;
70
+ };
71
+ cost?: CostEstimate;
62
72
  }
63
73
 
64
74
  export interface AgentEventAskUser {
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Checkpoint Manager - Core checkpointing logic
3
+ *
4
+ * Tracks file changes and provides rewind capabilities.
5
+ */
6
+
7
+ import * as fs from 'fs/promises';
8
+ import * as path from 'path';
9
+ import type {
10
+ FileCheckpoint,
11
+ CheckpointSession,
12
+ RewindOptions,
13
+ RewindResult,
14
+ CheckpointSummary,
15
+ RecordChangeInput,
16
+ } from './types.js';
17
+
18
+ /**
19
+ * Generates a unique ID for checkpoints
20
+ */
21
+ function generateId(): string {
22
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
23
+ }
24
+
25
+ /**
26
+ * CheckpointManager manages file change tracking and rewind operations.
27
+ *
28
+ * Usage:
29
+ * const manager = new CheckpointManager('session-123');
30
+ * manager.recordChange({ path: '/path/to/file', changeType: 'modify', ... });
31
+ * await manager.rewind({ all: true });
32
+ */
33
+ export class CheckpointManager {
34
+ private session: CheckpointSession;
35
+
36
+ constructor(sessionId: string = 'default') {
37
+ this.session = {
38
+ sessionId,
39
+ checkpoints: [],
40
+ createdAt: new Date(),
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Get the session ID
46
+ */
47
+ getSessionId(): string {
48
+ return this.session.sessionId;
49
+ }
50
+
51
+ /**
52
+ * Record a file change as a checkpoint
53
+ */
54
+ recordChange(input: RecordChangeInput): FileCheckpoint {
55
+ const checkpoint: FileCheckpoint = {
56
+ id: generateId(),
57
+ path: input.path,
58
+ changeType: input.changeType,
59
+ timestamp: new Date(),
60
+ previousContent: input.previousContent,
61
+ newContent: input.newContent,
62
+ toolName: input.toolName,
63
+ };
64
+
65
+ this.session.checkpoints.push(checkpoint);
66
+ return checkpoint;
67
+ }
68
+
69
+ /**
70
+ * Get all checkpoints
71
+ */
72
+ getCheckpoints(): FileCheckpoint[] {
73
+ return [...this.session.checkpoints];
74
+ }
75
+
76
+ /**
77
+ * Get checkpoints for a specific file
78
+ */
79
+ getFileHistory(filePath: string): FileCheckpoint[] {
80
+ return this.session.checkpoints.filter((cp) => cp.path === filePath);
81
+ }
82
+
83
+ /**
84
+ * Get a summary of all changes
85
+ */
86
+ getSummary(): CheckpointSummary {
87
+ const summary: CheckpointSummary = {
88
+ created: 0,
89
+ modified: 0,
90
+ deleted: 0,
91
+ total: this.session.checkpoints.length,
92
+ };
93
+
94
+ for (const cp of this.session.checkpoints) {
95
+ switch (cp.changeType) {
96
+ case 'create':
97
+ summary.created++;
98
+ break;
99
+ case 'modify':
100
+ summary.modified++;
101
+ break;
102
+ case 'delete':
103
+ summary.deleted++;
104
+ break;
105
+ }
106
+ }
107
+
108
+ return summary;
109
+ }
110
+
111
+ /**
112
+ * Check if there are any checkpoints
113
+ */
114
+ hasCheckpoints(): boolean {
115
+ return this.session.checkpoints.length > 0;
116
+ }
117
+
118
+ /**
119
+ * Get the number of checkpoints
120
+ */
121
+ getCheckpointCount(): number {
122
+ return this.session.checkpoints.length;
123
+ }
124
+
125
+ /**
126
+ * Rewind changes based on options
127
+ */
128
+ async rewind(options: RewindOptions): Promise<RewindResult> {
129
+ const result: RewindResult = {
130
+ success: true,
131
+ revertedFiles: [],
132
+ errors: [],
133
+ };
134
+
135
+ // Determine which checkpoints to rewind
136
+ let checkpointsToRewind: FileCheckpoint[] = [];
137
+
138
+ if (options.checkpointId) {
139
+ // Rewind specific checkpoint
140
+ const cp = this.session.checkpoints.find((c) => c.id === options.checkpointId);
141
+ if (cp) {
142
+ checkpointsToRewind = [cp];
143
+ }
144
+ } else if (options.path) {
145
+ // Rewind all changes to a specific file (in reverse order)
146
+ checkpointsToRewind = this.session.checkpoints
147
+ .filter((c) => c.path === options.path)
148
+ .reverse();
149
+ } else if (options.count) {
150
+ // Rewind last N changes (in reverse order)
151
+ checkpointsToRewind = this.session.checkpoints.slice(-options.count).reverse();
152
+ } else if (options.all) {
153
+ // Rewind all changes (in reverse order)
154
+ checkpointsToRewind = [...this.session.checkpoints].reverse();
155
+ }
156
+
157
+ // Apply reverts
158
+ for (const checkpoint of checkpointsToRewind) {
159
+ try {
160
+ await this.revertCheckpoint(checkpoint);
161
+ result.revertedFiles.push({
162
+ path: checkpoint.path,
163
+ action: this.getRevertAction(checkpoint),
164
+ });
165
+
166
+ // Remove the checkpoint from session
167
+ const index = this.session.checkpoints.findIndex((c) => c.id === checkpoint.id);
168
+ if (index !== -1) {
169
+ this.session.checkpoints.splice(index, 1);
170
+ }
171
+ } catch (error) {
172
+ result.success = false;
173
+ result.errors.push({
174
+ path: checkpoint.path,
175
+ error: error instanceof Error ? error.message : String(error),
176
+ });
177
+ }
178
+ }
179
+
180
+ return result;
181
+ }
182
+
183
+ /**
184
+ * Revert a single checkpoint
185
+ */
186
+ private async revertCheckpoint(checkpoint: FileCheckpoint): Promise<void> {
187
+ switch (checkpoint.changeType) {
188
+ case 'create':
189
+ // File was created, delete it to revert
190
+ await fs.unlink(checkpoint.path);
191
+ break;
192
+
193
+ case 'modify':
194
+ // File was modified, restore previous content
195
+ if (checkpoint.previousContent !== null) {
196
+ await fs.writeFile(checkpoint.path, checkpoint.previousContent, 'utf-8');
197
+ }
198
+ break;
199
+
200
+ case 'delete':
201
+ // File was deleted, recreate it with previous content
202
+ if (checkpoint.previousContent !== null) {
203
+ await fs.mkdir(path.dirname(checkpoint.path), { recursive: true });
204
+ await fs.writeFile(checkpoint.path, checkpoint.previousContent, 'utf-8');
205
+ }
206
+ break;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Get the action that will be taken to revert a checkpoint
212
+ */
213
+ private getRevertAction(checkpoint: FileCheckpoint): 'restored' | 'deleted' | 'recreated' {
214
+ switch (checkpoint.changeType) {
215
+ case 'create':
216
+ return 'deleted';
217
+ case 'modify':
218
+ return 'restored';
219
+ case 'delete':
220
+ return 'recreated';
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Clear all checkpoints
226
+ */
227
+ clearCheckpoints(): void {
228
+ this.session.checkpoints = [];
229
+ }
230
+
231
+ /**
232
+ * Format checkpoints for display
233
+ */
234
+ formatCheckpointList(includeUsage: boolean = false): string {
235
+ if (this.session.checkpoints.length === 0) {
236
+ return 'No file changes in this session.';
237
+ }
238
+
239
+ const lines: string[] = [];
240
+
241
+ this.session.checkpoints.forEach((cp, index) => {
242
+ const timeAgo = this.formatTimeAgo(cp.timestamp);
243
+ const action = this.formatChangeType(cp.changeType);
244
+ const fileName = cp.path.split('/').pop() || cp.path;
245
+ lines.push(` [${index + 1}] ${timeAgo.padEnd(8)} ${fileName.padEnd(30)} (${action})`);
246
+ });
247
+
248
+ const summary = this.getSummary();
249
+ lines.push('');
250
+ lines.push(
251
+ `Total: ${summary.created} created, ${summary.modified} modified, ${summary.deleted} deleted`
252
+ );
253
+
254
+ if (includeUsage) {
255
+ lines.push('');
256
+ lines.push('Usage: /rewind [n] to revert change #n, /rewind all to revert all');
257
+ }
258
+
259
+ return lines.join('\n');
260
+ }
261
+
262
+ /**
263
+ * Format a change type for display
264
+ */
265
+ private formatChangeType(changeType: string): string {
266
+ switch (changeType) {
267
+ case 'create':
268
+ return 'created';
269
+ case 'modify':
270
+ return 'modified';
271
+ case 'delete':
272
+ return 'deleted';
273
+ default:
274
+ return changeType;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Format a timestamp as relative time
280
+ */
281
+ private formatTimeAgo(date: Date): string {
282
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
283
+
284
+ if (seconds < 60) {
285
+ return `${seconds}s ago`;
286
+ }
287
+
288
+ const minutes = Math.floor(seconds / 60);
289
+ if (minutes < 60) {
290
+ return `${minutes}m ago`;
291
+ }
292
+
293
+ const hours = Math.floor(minutes / 60);
294
+ return `${hours}h ago`;
295
+ }
296
+ }
297
+
298
+ // ============================================================================
299
+ // Singleton Instance
300
+ // ============================================================================
301
+
302
+ let globalCheckpointManager: CheckpointManager | null = null;
303
+
304
+ /**
305
+ * Get the global checkpoint manager instance
306
+ */
307
+ export function getCheckpointManager(): CheckpointManager {
308
+ if (!globalCheckpointManager) {
309
+ globalCheckpointManager = new CheckpointManager();
310
+ }
311
+ return globalCheckpointManager;
312
+ }
313
+
314
+ /**
315
+ * Initialize checkpoint manager with a session ID
316
+ */
317
+ export function initCheckpointManager(sessionId: string): CheckpointManager {
318
+ globalCheckpointManager = new CheckpointManager(sessionId);
319
+ return globalCheckpointManager;
320
+ }
321
+
322
+ /**
323
+ * Reset the global checkpoint manager (for testing)
324
+ */
325
+ export function resetCheckpointManager(): void {
326
+ globalCheckpointManager = null;
327
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Checkpointing Module
3
+ *
4
+ * Provides automatic tracking of file changes with undo/rewind capabilities.
5
+ *
6
+ * Usage:
7
+ * import { getCheckpointManager, initCheckpointManager } from './checkpointing';
8
+ *
9
+ * // Initialize at session start
10
+ * const manager = initCheckpointManager('session-123');
11
+ *
12
+ * // Record changes (done automatically by ToolRegistry)
13
+ * manager.recordChange({
14
+ * path: '/path/to/file.ts',
15
+ * changeType: 'modify',
16
+ * previousContent: 'old content',
17
+ * newContent: 'new content',
18
+ * toolName: 'Edit'
19
+ * });
20
+ *
21
+ * // List changes
22
+ * console.log(manager.formatCheckpointList());
23
+ *
24
+ * // Rewind changes
25
+ * await manager.rewind({ all: true });
26
+ */
27
+
28
+ // Type exports
29
+ export type {
30
+ ChangeType,
31
+ FileCheckpoint,
32
+ CheckpointSession,
33
+ RewindOptions,
34
+ RewindResult,
35
+ CheckpointSummary,
36
+ RecordChangeInput,
37
+ } from './types.js';
38
+
39
+ // Manager exports
40
+ export {
41
+ CheckpointManager,
42
+ getCheckpointManager,
43
+ initCheckpointManager,
44
+ resetCheckpointManager,
45
+ } from './checkpoint-manager.js';