gencode-ai 0.1.2 → 0.2.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 (180) hide show
  1. package/README.md +15 -17
  2. package/dist/agent/agent.d.ts +43 -0
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +107 -4
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/index.d.ts +1 -0
  7. package/dist/agent/index.d.ts.map +1 -1
  8. package/dist/agent/types.d.ts +20 -1
  9. package/dist/agent/types.d.ts.map +1 -1
  10. package/dist/checkpointing/checkpoint-manager.d.ts +87 -0
  11. package/dist/checkpointing/checkpoint-manager.d.ts.map +1 -0
  12. package/dist/checkpointing/checkpoint-manager.js +281 -0
  13. package/dist/checkpointing/checkpoint-manager.js.map +1 -0
  14. package/dist/checkpointing/index.d.ts +29 -0
  15. package/dist/checkpointing/index.d.ts.map +1 -0
  16. package/dist/checkpointing/index.js +29 -0
  17. package/dist/checkpointing/index.js.map +1 -0
  18. package/dist/checkpointing/types.d.ts +98 -0
  19. package/dist/checkpointing/types.d.ts.map +1 -0
  20. package/dist/checkpointing/types.js +7 -0
  21. package/dist/checkpointing/types.js.map +1 -0
  22. package/dist/cli/components/App.d.ts.map +1 -1
  23. package/dist/cli/components/App.js +193 -7
  24. package/dist/cli/components/App.js.map +1 -1
  25. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  26. package/dist/cli/components/CommandSuggestions.js +5 -0
  27. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  28. package/dist/cli/components/Messages.d.ts +7 -1
  29. package/dist/cli/components/Messages.d.ts.map +1 -1
  30. package/dist/cli/components/Messages.js +28 -2
  31. package/dist/cli/components/Messages.js.map +1 -1
  32. package/dist/cli/components/ModeIndicator.d.ts +42 -0
  33. package/dist/cli/components/ModeIndicator.d.ts.map +1 -0
  34. package/dist/cli/components/ModeIndicator.js +52 -0
  35. package/dist/cli/components/ModeIndicator.js.map +1 -0
  36. package/dist/cli/components/PlanApproval.d.ts +36 -0
  37. package/dist/cli/components/PlanApproval.d.ts.map +1 -0
  38. package/dist/cli/components/PlanApproval.js +154 -0
  39. package/dist/cli/components/PlanApproval.js.map +1 -0
  40. package/dist/cli/components/QuestionPrompt.d.ts +23 -0
  41. package/dist/cli/components/QuestionPrompt.d.ts.map +1 -0
  42. package/dist/cli/components/QuestionPrompt.js +231 -0
  43. package/dist/cli/components/QuestionPrompt.js.map +1 -0
  44. package/dist/cli/components/index.d.ts +1 -0
  45. package/dist/cli/components/index.d.ts.map +1 -1
  46. package/dist/cli/components/index.js +1 -0
  47. package/dist/cli/components/index.js.map +1 -1
  48. package/dist/cli/components/theme.d.ts +9 -0
  49. package/dist/cli/components/theme.d.ts.map +1 -1
  50. package/dist/cli/components/theme.js +14 -1
  51. package/dist/cli/components/theme.js.map +1 -1
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +2 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/permissions/types.d.ts.map +1 -1
  57. package/dist/permissions/types.js +2 -0
  58. package/dist/permissions/types.js.map +1 -1
  59. package/dist/planning/index.d.ts +13 -0
  60. package/dist/planning/index.d.ts.map +1 -0
  61. package/dist/planning/index.js +15 -0
  62. package/dist/planning/index.js.map +1 -0
  63. package/dist/planning/plan-file.d.ts +59 -0
  64. package/dist/planning/plan-file.d.ts.map +1 -0
  65. package/dist/planning/plan-file.js +278 -0
  66. package/dist/planning/plan-file.js.map +1 -0
  67. package/dist/planning/state.d.ts +127 -0
  68. package/dist/planning/state.d.ts.map +1 -0
  69. package/dist/planning/state.js +261 -0
  70. package/dist/planning/state.js.map +1 -0
  71. package/dist/planning/tools/enter-plan-mode.d.ts +25 -0
  72. package/dist/planning/tools/enter-plan-mode.d.ts.map +1 -0
  73. package/dist/planning/tools/enter-plan-mode.js +98 -0
  74. package/dist/planning/tools/enter-plan-mode.js.map +1 -0
  75. package/dist/planning/tools/exit-plan-mode.d.ts +24 -0
  76. package/dist/planning/tools/exit-plan-mode.d.ts.map +1 -0
  77. package/dist/planning/tools/exit-plan-mode.js +149 -0
  78. package/dist/planning/tools/exit-plan-mode.js.map +1 -0
  79. package/dist/planning/types.d.ts +100 -0
  80. package/dist/planning/types.d.ts.map +1 -0
  81. package/dist/planning/types.js +28 -0
  82. package/dist/planning/types.js.map +1 -0
  83. package/dist/pricing/calculator.d.ts +21 -0
  84. package/dist/pricing/calculator.d.ts.map +1 -0
  85. package/dist/pricing/calculator.js +59 -0
  86. package/dist/pricing/calculator.js.map +1 -0
  87. package/dist/pricing/index.d.ts +7 -0
  88. package/dist/pricing/index.d.ts.map +1 -0
  89. package/dist/pricing/index.js +7 -0
  90. package/dist/pricing/index.js.map +1 -0
  91. package/dist/pricing/models.d.ts +20 -0
  92. package/dist/pricing/models.d.ts.map +1 -0
  93. package/dist/pricing/models.js +322 -0
  94. package/dist/pricing/models.js.map +1 -0
  95. package/dist/pricing/types.d.ts +30 -0
  96. package/dist/pricing/types.d.ts.map +1 -0
  97. package/dist/pricing/types.js +5 -0
  98. package/dist/pricing/types.js.map +1 -0
  99. package/dist/providers/anthropic.d.ts.map +1 -1
  100. package/dist/providers/anthropic.js +17 -10
  101. package/dist/providers/anthropic.js.map +1 -1
  102. package/dist/providers/gemini.d.ts.map +1 -1
  103. package/dist/providers/gemini.js +21 -14
  104. package/dist/providers/gemini.js.map +1 -1
  105. package/dist/providers/openai.d.ts.map +1 -1
  106. package/dist/providers/openai.js +12 -8
  107. package/dist/providers/openai.js.map +1 -1
  108. package/dist/providers/types.d.ts +2 -0
  109. package/dist/providers/types.d.ts.map +1 -1
  110. package/dist/providers/vertex-ai.d.ts.map +1 -1
  111. package/dist/providers/vertex-ai.js +17 -10
  112. package/dist/providers/vertex-ai.js.map +1 -1
  113. package/dist/session/manager.d.ts +4 -0
  114. package/dist/session/manager.d.ts.map +1 -1
  115. package/dist/session/manager.js +8 -0
  116. package/dist/session/manager.js.map +1 -1
  117. package/dist/tools/builtin/ask-user.d.ts +64 -0
  118. package/dist/tools/builtin/ask-user.d.ts.map +1 -0
  119. package/dist/tools/builtin/ask-user.js +148 -0
  120. package/dist/tools/builtin/ask-user.js.map +1 -0
  121. package/dist/tools/index.d.ts +19 -1
  122. package/dist/tools/index.d.ts.map +1 -1
  123. package/dist/tools/index.js +11 -0
  124. package/dist/tools/index.js.map +1 -1
  125. package/dist/tools/registry.d.ts +13 -0
  126. package/dist/tools/registry.d.ts.map +1 -1
  127. package/dist/tools/registry.js +79 -2
  128. package/dist/tools/registry.js.map +1 -1
  129. package/dist/tools/types.d.ts +17 -0
  130. package/dist/tools/types.d.ts.map +1 -1
  131. package/dist/tools/types.js.map +1 -1
  132. package/docs/cost-tracking-comparison.md +904 -0
  133. package/docs/operating-modes.md +96 -0
  134. package/docs/proposals/0012-ask-user-question.md +66 -1
  135. package/docs/proposals/0025-cost-tracking.md +60 -2
  136. package/docs/proposals/README.md +2 -2
  137. package/examples/test-ask-user.ts +167 -0
  138. package/examples/test-checkpointing.ts +121 -0
  139. package/examples/test-cost-tracking.ts +77 -0
  140. package/examples/test-interrupt-cleanup.ts +94 -0
  141. package/package.json +1 -1
  142. package/src/agent/agent.ts +130 -4
  143. package/src/agent/index.ts +1 -0
  144. package/src/agent/types.ts +19 -1
  145. package/src/checkpointing/checkpoint-manager.ts +327 -0
  146. package/src/checkpointing/index.ts +45 -0
  147. package/src/checkpointing/types.ts +104 -0
  148. package/src/cli/components/App.tsx +259 -8
  149. package/src/cli/components/CommandSuggestions.tsx +5 -0
  150. package/src/cli/components/Messages.tsx +66 -4
  151. package/src/cli/components/ModeIndicator.tsx +174 -0
  152. package/src/cli/components/PlanApproval.tsx +327 -0
  153. package/src/cli/components/QuestionPrompt.tsx +462 -0
  154. package/src/cli/components/index.ts +1 -0
  155. package/src/cli/components/theme.ts +14 -1
  156. package/src/index.ts +15 -0
  157. package/src/permissions/types.ts +2 -0
  158. package/src/planning/index.ts +53 -0
  159. package/src/planning/plan-file.ts +326 -0
  160. package/src/planning/state.ts +305 -0
  161. package/src/planning/tools/enter-plan-mode.ts +111 -0
  162. package/src/planning/tools/exit-plan-mode.ts +170 -0
  163. package/src/planning/types.ts +150 -0
  164. package/src/pricing/calculator.ts +71 -0
  165. package/src/pricing/index.ts +7 -0
  166. package/src/pricing/models.ts +334 -0
  167. package/src/pricing/types.ts +32 -0
  168. package/src/prompts/system/base.txt +42 -0
  169. package/src/prompts/tools/ask-user.txt +110 -0
  170. package/src/providers/anthropic.ts +21 -10
  171. package/src/providers/gemini.ts +25 -14
  172. package/src/providers/openai.ts +17 -8
  173. package/src/providers/types.ts +3 -0
  174. package/src/providers/vertex-ai.ts +21 -10
  175. package/src/session/manager.ts +9 -0
  176. package/src/tools/builtin/ask-user.ts +185 -0
  177. package/src/tools/index.ts +23 -0
  178. package/src/tools/registry.ts +95 -2
  179. package/src/tools/types.ts +18 -0
  180. package/.gencode/settings.local.json +0 -7
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import OpenAI from 'openai';
7
+ import { calculateCost } from '../pricing/calculator.js';
7
8
  import type {
8
9
  LLMProvider,
9
10
  CompletionOptions,
@@ -44,7 +45,7 @@ export class OpenAIProvider implements LLMProvider {
44
45
  temperature: options.temperature,
45
46
  });
46
47
 
47
- return this.convertResponse(response);
48
+ return this.convertResponse(response, options.model);
48
49
  }
49
50
 
50
51
  async *stream(options: CompletionOptions): AsyncGenerator<StreamChunk, void, unknown> {
@@ -195,7 +196,10 @@ export class OpenAIProvider implements LLMProvider {
195
196
  }));
196
197
  }
197
198
 
198
- private convertResponse(response: OpenAI.Chat.Completions.ChatCompletion): CompletionResponse {
199
+ private convertResponse(
200
+ response: OpenAI.Chat.Completions.ChatCompletion,
201
+ model: string
202
+ ): CompletionResponse {
199
203
  const choice = response.choices[0];
200
204
  const content: MessageContent[] = [];
201
205
 
@@ -216,15 +220,20 @@ export class OpenAIProvider implements LLMProvider {
216
220
  }
217
221
  }
218
222
 
223
+ const usage = response.usage
224
+ ? {
225
+ inputTokens: response.usage.prompt_tokens,
226
+ outputTokens: response.usage.completion_tokens,
227
+ }
228
+ : undefined;
229
+
230
+ const cost = usage ? calculateCost(this.name, model, usage) : undefined;
231
+
219
232
  return {
220
233
  content,
221
234
  stopReason: this.convertStopReason(choice.finish_reason),
222
- usage: response.usage
223
- ? {
224
- inputTokens: response.usage.prompt_tokens,
225
- outputTokens: response.usage.completion_tokens,
226
- }
227
- : undefined,
235
+ usage,
236
+ cost,
228
237
  };
229
238
  }
230
239
 
@@ -3,6 +3,8 @@
3
3
  * Abstracts differences between OpenAI, Anthropic, and Gemini APIs
4
4
  */
5
5
 
6
+ import type { CostEstimate } from '../pricing/types.js';
7
+
6
8
  // ============================================================================
7
9
  // Message Types
8
10
  // ============================================================================
@@ -92,6 +94,7 @@ export interface CompletionResponse {
92
94
  inputTokens: number;
93
95
  outputTokens: number;
94
96
  };
97
+ cost?: CostEstimate;
95
98
  }
96
99
 
97
100
  // ============================================================================
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { GoogleAuth } from 'google-auth-library';
12
+ import { calculateCost } from '../pricing/calculator.js';
12
13
  import type {
13
14
  LLMProvider,
14
15
  CompletionOptions,
@@ -210,7 +211,7 @@ export class VertexAIProvider implements LLMProvider {
210
211
  }
211
212
 
212
213
  const data = (await response.json()) as VertexAIResponse;
213
- return this.convertResponse(data);
214
+ return this.convertResponse(data, options.model);
214
215
  }
215
216
 
216
217
  async *stream(options: CompletionOptions): AsyncGenerator<StreamChunk, void, unknown> {
@@ -337,15 +338,20 @@ export class VertexAIProvider implements LLMProvider {
337
338
  // Build final response
338
339
  const content = this.buildFinalContent(contentBlocks, toolInputBuffers);
339
340
 
341
+ const usage = {
342
+ inputTokens,
343
+ outputTokens,
344
+ };
345
+
346
+ const cost = calculateCost(this.name, options.model, usage);
347
+
340
348
  yield {
341
349
  type: 'done',
342
350
  response: {
343
351
  content,
344
352
  stopReason,
345
- usage: {
346
- inputTokens,
347
- outputTokens,
348
- },
353
+ usage,
354
+ cost,
349
355
  },
350
356
  };
351
357
  }
@@ -449,14 +455,19 @@ export class VertexAIProvider implements LLMProvider {
449
455
  }));
450
456
  }
451
457
 
452
- private convertResponse(response: VertexAIResponse): CompletionResponse {
458
+ private convertResponse(response: VertexAIResponse, model: string): CompletionResponse {
459
+ const usage = {
460
+ inputTokens: response.usage.input_tokens,
461
+ outputTokens: response.usage.output_tokens,
462
+ };
463
+
464
+ const cost = calculateCost(this.name, model, usage);
465
+
453
466
  return {
454
467
  content: this.convertContent(response.content),
455
468
  stopReason: this.convertStopReason(response.stop_reason),
456
- usage: {
457
- inputTokens: response.usage.input_tokens,
458
- outputTokens: response.usage.output_tokens,
459
- },
469
+ usage,
470
+ cost,
460
471
  };
461
472
  }
462
473
 
@@ -326,6 +326,15 @@ export class SessionManager {
326
326
  }
327
327
  }
328
328
 
329
+ /**
330
+ * Remove the last message from current session
331
+ */
332
+ removeLastMessage(): void {
333
+ if (this.currentSession && this.currentSession.messages.length > 0) {
334
+ this.currentSession.messages.pop();
335
+ }
336
+ }
337
+
329
338
  /**
330
339
  * Export session to JSON
331
340
  */
@@ -0,0 +1,185 @@
1
+ /**
2
+ * AskUserQuestion Tool - Structured user questioning
3
+ *
4
+ * Allows the agent to pause execution and present structured questions
5
+ * to the user with predefined options. Supports single-select, multi-select,
6
+ * and custom "Other" input.
7
+ */
8
+
9
+ import { z } from 'zod';
10
+ import type { Tool, ToolResult, ToolContext } from '../types.js';
11
+ import { loadToolDescription } from '../../prompts/index.js';
12
+
13
+ // ============================================================================
14
+ // Zod Schemas
15
+ // ============================================================================
16
+
17
+ export const QuestionOptionSchema = z.object({
18
+ label: z
19
+ .string()
20
+ .min(1)
21
+ .max(50)
22
+ .describe('Display text for this option (1-5 words, concise)'),
23
+ description: z
24
+ .string()
25
+ .min(1)
26
+ .max(200)
27
+ .describe('Explanation of what this option means or implications'),
28
+ });
29
+
30
+ export const QuestionSchema = z.object({
31
+ question: z
32
+ .string()
33
+ .min(1)
34
+ .describe('The complete question to ask the user, ending with a question mark'),
35
+ header: z
36
+ .string()
37
+ .min(1)
38
+ .max(12)
39
+ .describe('Very short label displayed as a chip/tag (max 12 chars)'),
40
+ options: z
41
+ .array(QuestionOptionSchema)
42
+ .min(2)
43
+ .max(4)
44
+ .describe('2-4 options for the user to choose from'),
45
+ multiSelect: z
46
+ .boolean()
47
+ .describe('Set to true to allow multiple selections, false for single choice'),
48
+ });
49
+
50
+ export const AskUserQuestionInputSchema = z.object({
51
+ questions: z
52
+ .array(QuestionSchema)
53
+ .min(1)
54
+ .max(4)
55
+ .describe('1-4 questions to ask the user'),
56
+ });
57
+
58
+ // ============================================================================
59
+ // Types
60
+ // ============================================================================
61
+
62
+ export type QuestionOption = z.infer<typeof QuestionOptionSchema>;
63
+ export type Question = z.infer<typeof QuestionSchema>;
64
+ export type AskUserQuestionInput = z.infer<typeof AskUserQuestionInputSchema>;
65
+
66
+ export interface QuestionAnswer {
67
+ question: string;
68
+ header: string;
69
+ selectedOptions: string[];
70
+ customInput?: string;
71
+ }
72
+
73
+ export interface AskUserQuestionResult {
74
+ answers: QuestionAnswer[];
75
+ }
76
+
77
+ // ============================================================================
78
+ // Answer Formatting
79
+ // ============================================================================
80
+
81
+ /**
82
+ * Format answers for display to the agent
83
+ */
84
+ export function formatAnswersForAgent(answers: QuestionAnswer[]): string {
85
+ const lines: string[] = ['User answered the following questions:', ''];
86
+
87
+ answers.forEach((answer, index) => {
88
+ lines.push(`${index + 1}. ${answer.header} (${answer.question})`);
89
+
90
+ if (answer.selectedOptions.length > 0) {
91
+ lines.push(` Selected: ${answer.selectedOptions.join(', ')}`);
92
+ }
93
+
94
+ if (answer.customInput) {
95
+ lines.push(` Custom input: ${answer.customInput}`);
96
+ }
97
+
98
+ lines.push('');
99
+ });
100
+
101
+ lines.push('Proceeding with user selections.');
102
+
103
+ return lines.join('\n');
104
+ }
105
+
106
+ /**
107
+ * Format answers for CLI confirmation display
108
+ */
109
+ export function formatAnswersForDisplay(answers: QuestionAnswer[]): string {
110
+ return answers
111
+ .map((answer) => {
112
+ const selections = answer.customInput
113
+ ? [...answer.selectedOptions, answer.customInput].join(', ')
114
+ : answer.selectedOptions.join(', ');
115
+ return `✔ ${answer.header}: ${selections}`;
116
+ })
117
+ .join('\n');
118
+ }
119
+
120
+ // ============================================================================
121
+ // Tool Implementation
122
+ // ============================================================================
123
+
124
+ /**
125
+ * AskUserQuestion tool
126
+ *
127
+ * This tool is special - it doesn't execute immediately but signals
128
+ * the agent loop to pause and wait for user input via the CLI.
129
+ *
130
+ * The actual questioning is handled by the CLI layer (QuestionPrompt component).
131
+ * This tool just validates the input and returns a marker for the agent loop.
132
+ */
133
+ export const askUserQuestionTool: Tool<AskUserQuestionInput> = {
134
+ name: 'AskUserQuestion',
135
+ description: loadToolDescription('ask-user'),
136
+ parameters: AskUserQuestionInputSchema,
137
+
138
+ async execute(input: AskUserQuestionInput, context: ToolContext): Promise<ToolResult> {
139
+ // Validation is handled by Zod schema
140
+ // Additional validation for recommended options format
141
+ for (const question of input.questions) {
142
+ // Check if first option has (Recommended) - this is just a hint, not enforced
143
+ const firstOption = question.options[0];
144
+ if (firstOption && !firstOption.label.includes('(Recommended)')) {
145
+ // This is fine - recommended suffix is optional
146
+ }
147
+ }
148
+
149
+ // Check if context has askUser callback
150
+ if (context.askUser) {
151
+ try {
152
+ const answers = await context.askUser(input.questions);
153
+ return {
154
+ success: true,
155
+ output: formatAnswersForAgent(answers),
156
+ metadata: {
157
+ title: 'AskUserQuestion',
158
+ subtitle: `${answers.length} answer(s) received`,
159
+ },
160
+ };
161
+ } catch (error) {
162
+ return {
163
+ success: false,
164
+ output: '',
165
+ error: `Failed to get user response: ${error instanceof Error ? error.message : String(error)}`,
166
+ };
167
+ }
168
+ }
169
+
170
+ // If no askUser callback, return a special marker
171
+ // The agent loop should detect this and handle it specially
172
+ return {
173
+ success: true,
174
+ output: JSON.stringify({
175
+ type: 'ask_user_question',
176
+ questions: input.questions,
177
+ requiresUserInput: true,
178
+ }),
179
+ metadata: {
180
+ title: 'AskUserQuestion',
181
+ subtitle: `${input.questions.length} question(s) pending`,
182
+ },
183
+ };
184
+ },
185
+ };
@@ -15,6 +15,21 @@ export { grepTool } from './builtin/grep.js';
15
15
  export { webfetchTool } from './builtin/webfetch.js';
16
16
  export { websearchTool } from './builtin/websearch.js';
17
17
  export { todowriteTool, getTodos, clearTodos } from './builtin/todowrite.js';
18
+ export {
19
+ askUserQuestionTool,
20
+ formatAnswersForAgent,
21
+ formatAnswersForDisplay,
22
+ } from './builtin/ask-user.js';
23
+ export type {
24
+ Question as AskUserQuestion,
25
+ QuestionOption as AskUserQuestionOption,
26
+ QuestionAnswer as AskUserQuestionAnswer,
27
+ AskUserQuestionInput,
28
+ AskUserQuestionResult,
29
+ } from './builtin/ask-user.js';
30
+
31
+ // Plan mode tools
32
+ export { enterPlanModeTool, exitPlanModeTool } from '../planning/index.js';
18
33
 
19
34
  import { ToolRegistry } from './registry.js';
20
35
  import { readTool } from './builtin/read.js';
@@ -26,6 +41,8 @@ import { grepTool } from './builtin/grep.js';
26
41
  import { webfetchTool } from './builtin/webfetch.js';
27
42
  import { websearchTool } from './builtin/websearch.js';
28
43
  import { todowriteTool } from './builtin/todowrite.js';
44
+ import { askUserQuestionTool } from './builtin/ask-user.js';
45
+ import { enterPlanModeTool, exitPlanModeTool } from '../planning/index.js';
29
46
 
30
47
  /**
31
48
  * Create a registry with all built-in tools
@@ -42,6 +59,9 @@ export function createDefaultRegistry(): ToolRegistry {
42
59
  webfetchTool,
43
60
  websearchTool,
44
61
  todowriteTool,
62
+ askUserQuestionTool,
63
+ enterPlanModeTool,
64
+ exitPlanModeTool,
45
65
  ]);
46
66
  return registry;
47
67
  }
@@ -59,4 +79,7 @@ export const builtinTools = [
59
79
  webfetchTool,
60
80
  websearchTool,
61
81
  todowriteTool,
82
+ askUserQuestionTool,
83
+ enterPlanModeTool,
84
+ exitPlanModeTool,
62
85
  ];
@@ -2,9 +2,16 @@
2
2
  * Tool Registry - Manages available tools
3
3
  */
4
4
 
5
+ import * as fs from 'fs/promises';
5
6
  import type { Tool, ToolContext, ToolResult } from './types.js';
6
- import { zodToJsonSchema, getErrorMessage } from './types.js';
7
+ import { zodToJsonSchema, getErrorMessage, resolvePath } from './types.js';
7
8
  import type { ToolDefinition } from '../providers/types.js';
9
+ import { getPlanModeManager } from '../planning/index.js';
10
+ import { getCheckpointManager } from '../checkpointing/index.js';
11
+ import type { ChangeType } from '../checkpointing/index.js';
12
+
13
+ // Tools that modify files and should be tracked for checkpointing
14
+ const CHECKPOINT_TOOLS = ['Write', 'Edit'];
8
15
 
9
16
  export class ToolRegistry {
10
17
  private tools: Map<string, Tool> = new Map();
@@ -50,6 +57,20 @@ export class ToolRegistry {
50
57
  .filter((t): t is ToolDefinition => t !== null);
51
58
  }
52
59
 
60
+ /**
61
+ * Get tool definitions filtered by plan mode
62
+ * In plan mode, only read-only tools are returned
63
+ */
64
+ getFilteredDefinitions(toolNames?: string[]): ToolDefinition[] {
65
+ const planManager = getPlanModeManager();
66
+ const names = toolNames ?? this.list();
67
+
68
+ // Filter tools based on plan mode state
69
+ const filteredNames = planManager.filterTools(names);
70
+
71
+ return this.getDefinitions(filteredNames);
72
+ }
73
+
53
74
  /**
54
75
  * Execute a tool by name
55
76
  */
@@ -75,9 +96,81 @@ export class ToolRegistry {
75
96
  };
76
97
  }
77
98
 
78
- return await tool.execute(parsed.data, context);
99
+ // Capture pre-execution state for checkpointing
100
+ let preState: { filePath: string; content: string | null; existed: boolean } | null = null;
101
+ if (CHECKPOINT_TOOLS.includes(name) && parsed.data && typeof parsed.data === 'object') {
102
+ const filePath = (parsed.data as { file_path?: string }).file_path;
103
+ if (filePath) {
104
+ preState = await this.captureFileState(filePath, context.cwd);
105
+ }
106
+ }
107
+
108
+ // Execute the tool
109
+ const result = await tool.execute(parsed.data, context);
110
+
111
+ // Record checkpoint on successful file modification
112
+ if (result.success && preState) {
113
+ await this.recordCheckpoint(name, preState, context.cwd);
114
+ }
115
+
116
+ return result;
79
117
  } catch (error) {
80
118
  return { success: false, output: '', error: `Tool execution failed: ${getErrorMessage(error)}` };
81
119
  }
82
120
  }
121
+
122
+ /**
123
+ * Capture file state before modification
124
+ */
125
+ private async captureFileState(
126
+ filePath: string,
127
+ cwd: string
128
+ ): Promise<{ filePath: string; content: string | null; existed: boolean }> {
129
+ const resolvedPath = resolvePath(filePath, cwd);
130
+ try {
131
+ const content = await fs.readFile(resolvedPath, 'utf-8');
132
+ return { filePath: resolvedPath, content, existed: true };
133
+ } catch {
134
+ // File doesn't exist
135
+ return { filePath: resolvedPath, content: null, existed: false };
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Record a checkpoint after file modification
141
+ */
142
+ private async recordCheckpoint(
143
+ toolName: string,
144
+ preState: { filePath: string; content: string | null; existed: boolean },
145
+ cwd: string
146
+ ): Promise<void> {
147
+ const checkpointManager = getCheckpointManager();
148
+
149
+ // Read current file content
150
+ let newContent: string | null = null;
151
+ try {
152
+ newContent = await fs.readFile(preState.filePath, 'utf-8');
153
+ } catch {
154
+ // File was deleted or doesn't exist
155
+ }
156
+
157
+ // Determine change type
158
+ let changeType: ChangeType;
159
+ if (!preState.existed && newContent !== null) {
160
+ changeType = 'create';
161
+ } else if (preState.existed && newContent === null) {
162
+ changeType = 'delete';
163
+ } else {
164
+ changeType = 'modify';
165
+ }
166
+
167
+ // Record the change
168
+ checkpointManager.recordChange({
169
+ path: preState.filePath,
170
+ changeType,
171
+ previousContent: preState.content,
172
+ newContent,
173
+ toolName,
174
+ });
175
+ }
83
176
  }
@@ -9,9 +9,27 @@ import { z } from 'zod';
9
9
  // Tool Definition Types
10
10
  // ============================================================================
11
11
 
12
+ // Forward declaration for Question type (used by AskUserQuestion tool)
13
+ export interface Question {
14
+ question: string;
15
+ header: string;
16
+ options: Array<{ label: string; description: string }>;
17
+ multiSelect: boolean;
18
+ }
19
+
20
+ // Forward declaration for QuestionAnswer type (used by AskUserQuestion tool)
21
+ export interface QuestionAnswer {
22
+ question: string;
23
+ header: string;
24
+ selectedOptions: string[];
25
+ customInput?: string;
26
+ }
27
+
12
28
  export interface ToolContext {
13
29
  cwd: string;
14
30
  abortSignal?: AbortSignal;
31
+ /** Callback for AskUserQuestion tool to interact with user */
32
+ askUser?: (questions: Question[]) => Promise<QuestionAnswer[]>;
15
33
  }
16
34
 
17
35
  export interface ToolResultMetadata {
@@ -1,7 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(cd /Users/myan/Workspace/ideas/gencode:*)"
5
- ]
6
- }
7
- }