@compilr-dev/agents 0.0.1

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 (160) hide show
  1. package/README.md +1277 -0
  2. package/dist/agent.d.ts +1272 -0
  3. package/dist/agent.js +1912 -0
  4. package/dist/anchors/builtin.d.ts +24 -0
  5. package/dist/anchors/builtin.js +61 -0
  6. package/dist/anchors/index.d.ts +6 -0
  7. package/dist/anchors/index.js +5 -0
  8. package/dist/anchors/manager.d.ts +115 -0
  9. package/dist/anchors/manager.js +412 -0
  10. package/dist/anchors/types.d.ts +168 -0
  11. package/dist/anchors/types.js +10 -0
  12. package/dist/context/index.d.ts +12 -0
  13. package/dist/context/index.js +10 -0
  14. package/dist/context/manager.d.ts +224 -0
  15. package/dist/context/manager.js +770 -0
  16. package/dist/context/types.d.ts +377 -0
  17. package/dist/context/types.js +7 -0
  18. package/dist/costs/index.d.ts +8 -0
  19. package/dist/costs/index.js +7 -0
  20. package/dist/costs/tracker.d.ts +121 -0
  21. package/dist/costs/tracker.js +295 -0
  22. package/dist/costs/types.d.ts +157 -0
  23. package/dist/costs/types.js +8 -0
  24. package/dist/errors.d.ts +178 -0
  25. package/dist/errors.js +249 -0
  26. package/dist/guardrails/builtin.d.ts +27 -0
  27. package/dist/guardrails/builtin.js +223 -0
  28. package/dist/guardrails/index.d.ts +6 -0
  29. package/dist/guardrails/index.js +5 -0
  30. package/dist/guardrails/manager.d.ts +117 -0
  31. package/dist/guardrails/manager.js +288 -0
  32. package/dist/guardrails/types.d.ts +159 -0
  33. package/dist/guardrails/types.js +7 -0
  34. package/dist/hooks/index.d.ts +31 -0
  35. package/dist/hooks/index.js +29 -0
  36. package/dist/hooks/manager.d.ts +147 -0
  37. package/dist/hooks/manager.js +600 -0
  38. package/dist/hooks/types.d.ts +368 -0
  39. package/dist/hooks/types.js +12 -0
  40. package/dist/index.d.ts +45 -0
  41. package/dist/index.js +73 -0
  42. package/dist/mcp/client.d.ts +93 -0
  43. package/dist/mcp/client.js +287 -0
  44. package/dist/mcp/errors.d.ts +60 -0
  45. package/dist/mcp/errors.js +78 -0
  46. package/dist/mcp/index.d.ts +43 -0
  47. package/dist/mcp/index.js +45 -0
  48. package/dist/mcp/manager.d.ts +120 -0
  49. package/dist/mcp/manager.js +276 -0
  50. package/dist/mcp/tools.d.ts +54 -0
  51. package/dist/mcp/tools.js +99 -0
  52. package/dist/mcp/types.d.ts +150 -0
  53. package/dist/mcp/types.js +40 -0
  54. package/dist/memory/index.d.ts +8 -0
  55. package/dist/memory/index.js +7 -0
  56. package/dist/memory/loader.d.ts +114 -0
  57. package/dist/memory/loader.js +463 -0
  58. package/dist/memory/types.d.ts +182 -0
  59. package/dist/memory/types.js +8 -0
  60. package/dist/messages/index.d.ts +82 -0
  61. package/dist/messages/index.js +155 -0
  62. package/dist/permissions/index.d.ts +5 -0
  63. package/dist/permissions/index.js +4 -0
  64. package/dist/permissions/manager.d.ts +125 -0
  65. package/dist/permissions/manager.js +379 -0
  66. package/dist/permissions/types.d.ts +162 -0
  67. package/dist/permissions/types.js +7 -0
  68. package/dist/providers/claude.d.ts +90 -0
  69. package/dist/providers/claude.js +348 -0
  70. package/dist/providers/index.d.ts +8 -0
  71. package/dist/providers/index.js +11 -0
  72. package/dist/providers/mock.d.ts +133 -0
  73. package/dist/providers/mock.js +204 -0
  74. package/dist/providers/types.d.ts +168 -0
  75. package/dist/providers/types.js +4 -0
  76. package/dist/rate-limit/index.d.ts +45 -0
  77. package/dist/rate-limit/index.js +47 -0
  78. package/dist/rate-limit/limiter.d.ts +104 -0
  79. package/dist/rate-limit/limiter.js +326 -0
  80. package/dist/rate-limit/provider-wrapper.d.ts +112 -0
  81. package/dist/rate-limit/provider-wrapper.js +201 -0
  82. package/dist/rate-limit/retry.d.ts +108 -0
  83. package/dist/rate-limit/retry.js +287 -0
  84. package/dist/rate-limit/types.d.ts +181 -0
  85. package/dist/rate-limit/types.js +22 -0
  86. package/dist/rehearsal/file-analyzer.d.ts +22 -0
  87. package/dist/rehearsal/file-analyzer.js +351 -0
  88. package/dist/rehearsal/git-analyzer.d.ts +22 -0
  89. package/dist/rehearsal/git-analyzer.js +472 -0
  90. package/dist/rehearsal/index.d.ts +35 -0
  91. package/dist/rehearsal/index.js +36 -0
  92. package/dist/rehearsal/manager.d.ts +100 -0
  93. package/dist/rehearsal/manager.js +290 -0
  94. package/dist/rehearsal/types.d.ts +235 -0
  95. package/dist/rehearsal/types.js +8 -0
  96. package/dist/skills/index.d.ts +160 -0
  97. package/dist/skills/index.js +282 -0
  98. package/dist/state/agent-state.d.ts +41 -0
  99. package/dist/state/agent-state.js +88 -0
  100. package/dist/state/checkpointer.d.ts +110 -0
  101. package/dist/state/checkpointer.js +362 -0
  102. package/dist/state/errors.d.ts +66 -0
  103. package/dist/state/errors.js +88 -0
  104. package/dist/state/index.d.ts +35 -0
  105. package/dist/state/index.js +37 -0
  106. package/dist/state/serializer.d.ts +55 -0
  107. package/dist/state/serializer.js +172 -0
  108. package/dist/state/types.d.ts +312 -0
  109. package/dist/state/types.js +14 -0
  110. package/dist/tools/builtin/bash-output.d.ts +61 -0
  111. package/dist/tools/builtin/bash-output.js +90 -0
  112. package/dist/tools/builtin/bash.d.ts +150 -0
  113. package/dist/tools/builtin/bash.js +354 -0
  114. package/dist/tools/builtin/edit.d.ts +50 -0
  115. package/dist/tools/builtin/edit.js +215 -0
  116. package/dist/tools/builtin/glob.d.ts +62 -0
  117. package/dist/tools/builtin/glob.js +244 -0
  118. package/dist/tools/builtin/grep.d.ts +74 -0
  119. package/dist/tools/builtin/grep.js +363 -0
  120. package/dist/tools/builtin/index.d.ts +44 -0
  121. package/dist/tools/builtin/index.js +69 -0
  122. package/dist/tools/builtin/kill-shell.d.ts +44 -0
  123. package/dist/tools/builtin/kill-shell.js +80 -0
  124. package/dist/tools/builtin/read-file.d.ts +57 -0
  125. package/dist/tools/builtin/read-file.js +184 -0
  126. package/dist/tools/builtin/shell-manager.d.ts +176 -0
  127. package/dist/tools/builtin/shell-manager.js +337 -0
  128. package/dist/tools/builtin/task.d.ts +202 -0
  129. package/dist/tools/builtin/task.js +350 -0
  130. package/dist/tools/builtin/todo.d.ts +207 -0
  131. package/dist/tools/builtin/todo.js +453 -0
  132. package/dist/tools/builtin/utils.d.ts +27 -0
  133. package/dist/tools/builtin/utils.js +70 -0
  134. package/dist/tools/builtin/web-fetch.d.ts +96 -0
  135. package/dist/tools/builtin/web-fetch.js +290 -0
  136. package/dist/tools/builtin/write-file.d.ts +54 -0
  137. package/dist/tools/builtin/write-file.js +147 -0
  138. package/dist/tools/define.d.ts +60 -0
  139. package/dist/tools/define.js +65 -0
  140. package/dist/tools/index.d.ts +10 -0
  141. package/dist/tools/index.js +37 -0
  142. package/dist/tools/registry.d.ts +79 -0
  143. package/dist/tools/registry.js +151 -0
  144. package/dist/tools/types.d.ts +59 -0
  145. package/dist/tools/types.js +4 -0
  146. package/dist/tracing/hooks.d.ts +58 -0
  147. package/dist/tracing/hooks.js +377 -0
  148. package/dist/tracing/index.d.ts +51 -0
  149. package/dist/tracing/index.js +55 -0
  150. package/dist/tracing/logging.d.ts +78 -0
  151. package/dist/tracing/logging.js +310 -0
  152. package/dist/tracing/manager.d.ts +160 -0
  153. package/dist/tracing/manager.js +468 -0
  154. package/dist/tracing/otel.d.ts +102 -0
  155. package/dist/tracing/otel.js +246 -0
  156. package/dist/tracing/types.d.ts +346 -0
  157. package/dist/tracing/types.js +38 -0
  158. package/dist/utils/index.d.ts +23 -0
  159. package/dist/utils/index.js +44 -0
  160. package/package.json +79 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Todo Tools - Task management for agents
3
+ *
4
+ * Provides in-memory task tracking with TodoRead and TodoWrite tools.
5
+ * Useful for planning and tracking multi-step tasks.
6
+ */
7
+ import type { Tool } from '../types.js';
8
+ /**
9
+ * Task status
10
+ */
11
+ export type TodoStatus = 'pending' | 'in_progress' | 'completed';
12
+ /**
13
+ * A single todo item
14
+ */
15
+ export interface TodoItem {
16
+ /**
17
+ * Unique identifier for the todo
18
+ */
19
+ id: string;
20
+ /**
21
+ * Task content/description (imperative form)
22
+ */
23
+ content: string;
24
+ /**
25
+ * Current status of the task
26
+ */
27
+ status: TodoStatus;
28
+ /**
29
+ * Active form of the task (present continuous, e.g., "Implementing feature")
30
+ */
31
+ activeForm?: string;
32
+ /**
33
+ * Optional priority (higher = more important)
34
+ */
35
+ priority?: number;
36
+ /**
37
+ * Creation timestamp
38
+ */
39
+ createdAt: Date;
40
+ /**
41
+ * Last update timestamp
42
+ */
43
+ updatedAt: Date;
44
+ }
45
+ /**
46
+ * Input for TodoWrite tool
47
+ */
48
+ export interface TodoWriteInput {
49
+ /**
50
+ * The complete list of todos (replaces existing list)
51
+ */
52
+ todos: Array<{
53
+ content: string;
54
+ status: TodoStatus;
55
+ activeForm?: string;
56
+ priority?: number;
57
+ }>;
58
+ }
59
+ /**
60
+ * Input for TodoRead tool
61
+ */
62
+ export interface TodoReadInput {
63
+ /**
64
+ * Filter by status (optional)
65
+ */
66
+ status?: TodoStatus;
67
+ /**
68
+ * Include completed tasks (default: true)
69
+ */
70
+ includeCompleted?: boolean;
71
+ }
72
+ /**
73
+ * TodoStore manages the in-memory task list
74
+ */
75
+ export declare class TodoStore {
76
+ private readonly todos;
77
+ private nextId;
78
+ /**
79
+ * Get all todos
80
+ */
81
+ getAll(): TodoItem[];
82
+ /**
83
+ * Get todos filtered by status
84
+ */
85
+ getByStatus(status: TodoStatus): TodoItem[];
86
+ /**
87
+ * Replace all todos with a new list
88
+ */
89
+ setAll(todos: Array<{
90
+ content: string;
91
+ status: TodoStatus;
92
+ activeForm?: string;
93
+ priority?: number;
94
+ }>): void;
95
+ /**
96
+ * Add a single todo
97
+ */
98
+ add(todo: {
99
+ content: string;
100
+ status: TodoStatus;
101
+ activeForm?: string;
102
+ priority?: number;
103
+ }): TodoItem;
104
+ /**
105
+ * Update a todo by ID
106
+ */
107
+ update(id: string, updates: Partial<Omit<TodoItem, 'id' | 'createdAt'>>): TodoItem | null;
108
+ /**
109
+ * Remove a todo by ID
110
+ */
111
+ remove(id: string): boolean;
112
+ /**
113
+ * Clear all todos
114
+ */
115
+ clear(): void;
116
+ /**
117
+ * Get count by status
118
+ */
119
+ getCounts(): Record<TodoStatus, number>;
120
+ }
121
+ /**
122
+ * TodoWrite tool - Update the task list
123
+ */
124
+ export declare const todoWriteTool: Tool<TodoWriteInput>;
125
+ /**
126
+ * TodoRead tool - Get the current task list
127
+ */
128
+ export declare const todoReadTool: Tool<TodoReadInput>;
129
+ /**
130
+ * Factory to create todo tools with a custom store
131
+ */
132
+ export declare function createTodoTools(store?: TodoStore): {
133
+ todoWrite: Tool<TodoWriteInput>;
134
+ todoRead: Tool<TodoReadInput>;
135
+ store: TodoStore;
136
+ };
137
+ /**
138
+ * Reset the default store (useful for testing)
139
+ */
140
+ export declare function resetDefaultTodoStore(): void;
141
+ /**
142
+ * Get the default store (useful for testing or direct access)
143
+ */
144
+ export declare function getDefaultTodoStore(): TodoStore;
145
+ /**
146
+ * Create a new isolated TodoStore instance.
147
+ *
148
+ * Use this when you need state isolation between parallel operations,
149
+ * such as concurrent sub-agent executions. This prevents state leakage
150
+ * between parallel runs.
151
+ *
152
+ * Inspired by LangGraph issue #6446: Parallel subgraphs with shared
153
+ * state keys cause InvalidUpdateError.
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * // For parallel sub-agents, create isolated stores
158
+ * const store1 = createIsolatedTodoStore();
159
+ * const store2 = createIsolatedTodoStore();
160
+ *
161
+ * // Each sub-agent uses its own store
162
+ * await Promise.all([
163
+ * runWithStore(store1, task1),
164
+ * runWithStore(store2, task2),
165
+ * ]);
166
+ * ```
167
+ */
168
+ export declare function createIsolatedTodoStore(): TodoStore;
169
+ /**
170
+ * Context cleanup options for todo tool calls
171
+ */
172
+ export interface TodoContextCleanupOptions {
173
+ /**
174
+ * Keep only the last N todo_write calls (default: 1)
175
+ */
176
+ keepLastN?: number;
177
+ /**
178
+ * Also clean up todo_read calls (default: false)
179
+ */
180
+ cleanReads?: boolean;
181
+ }
182
+ /**
183
+ * Filter messages to remove redundant todo tool calls.
184
+ * This helps prevent context bloat from repeated todo_write calls.
185
+ *
186
+ * The function keeps only the last N todo_write calls (default: 1),
187
+ * removing earlier ones to reduce context size.
188
+ *
189
+ * @param messages - Array of messages to filter
190
+ * @param options - Cleanup options
191
+ * @returns Filtered messages with redundant todo calls removed
192
+ */
193
+ export declare function cleanupTodoContextMessages<T extends {
194
+ role: string;
195
+ content: unknown;
196
+ }>(messages: T[], options?: TodoContextCleanupOptions): T[];
197
+ /**
198
+ * Get statistics about todo tool usage in messages
199
+ */
200
+ export declare function getTodoContextStats(messages: Array<{
201
+ role: string;
202
+ content: unknown;
203
+ }>): {
204
+ todoWriteCalls: number;
205
+ todoReadCalls: number;
206
+ estimatedTokensSaved: number;
207
+ };
@@ -0,0 +1,453 @@
1
+ /**
2
+ * Todo Tools - Task management for agents
3
+ *
4
+ * Provides in-memory task tracking with TodoRead and TodoWrite tools.
5
+ * Useful for planning and tracking multi-step tasks.
6
+ */
7
+ import { defineTool, createSuccessResult, createErrorResult } from '../define.js';
8
+ /**
9
+ * TodoStore manages the in-memory task list
10
+ */
11
+ export class TodoStore {
12
+ todos = new Map();
13
+ nextId = 1;
14
+ /**
15
+ * Get all todos
16
+ */
17
+ getAll() {
18
+ return Array.from(this.todos.values()).sort((a, b) => {
19
+ // Sort by status priority: in_progress > pending > completed
20
+ const statusOrder = {
21
+ in_progress: 0,
22
+ pending: 1,
23
+ completed: 2,
24
+ };
25
+ if (statusOrder[a.status] !== statusOrder[b.status]) {
26
+ return statusOrder[a.status] - statusOrder[b.status];
27
+ }
28
+ // Then by priority (higher first)
29
+ if ((a.priority ?? 0) !== (b.priority ?? 0)) {
30
+ return (b.priority ?? 0) - (a.priority ?? 0);
31
+ }
32
+ // Then by creation order
33
+ return a.createdAt.getTime() - b.createdAt.getTime();
34
+ });
35
+ }
36
+ /**
37
+ * Get todos filtered by status
38
+ */
39
+ getByStatus(status) {
40
+ return this.getAll().filter((t) => t.status === status);
41
+ }
42
+ /**
43
+ * Replace all todos with a new list
44
+ */
45
+ setAll(todos) {
46
+ this.todos.clear();
47
+ const now = new Date();
48
+ for (const todo of todos) {
49
+ const id = `todo-${String(this.nextId++)}`;
50
+ this.todos.set(id, {
51
+ id,
52
+ content: todo.content,
53
+ status: todo.status,
54
+ activeForm: todo.activeForm,
55
+ priority: todo.priority,
56
+ createdAt: now,
57
+ updatedAt: now,
58
+ });
59
+ }
60
+ }
61
+ /**
62
+ * Add a single todo
63
+ */
64
+ add(todo) {
65
+ const id = `todo-${String(this.nextId++)}`;
66
+ const now = new Date();
67
+ const item = {
68
+ id,
69
+ content: todo.content,
70
+ status: todo.status,
71
+ activeForm: todo.activeForm,
72
+ priority: todo.priority,
73
+ createdAt: now,
74
+ updatedAt: now,
75
+ };
76
+ this.todos.set(id, item);
77
+ return item;
78
+ }
79
+ /**
80
+ * Update a todo by ID
81
+ */
82
+ update(id, updates) {
83
+ const existing = this.todos.get(id);
84
+ if (!existing)
85
+ return null;
86
+ const updated = {
87
+ ...existing,
88
+ ...updates,
89
+ updatedAt: new Date(),
90
+ };
91
+ this.todos.set(id, updated);
92
+ return updated;
93
+ }
94
+ /**
95
+ * Remove a todo by ID
96
+ */
97
+ remove(id) {
98
+ return this.todos.delete(id);
99
+ }
100
+ /**
101
+ * Clear all todos
102
+ */
103
+ clear() {
104
+ this.todos.clear();
105
+ }
106
+ /**
107
+ * Get count by status
108
+ */
109
+ getCounts() {
110
+ const counts = {
111
+ pending: 0,
112
+ in_progress: 0,
113
+ completed: 0,
114
+ };
115
+ for (const todo of this.todos.values()) {
116
+ counts[todo.status]++;
117
+ }
118
+ return counts;
119
+ }
120
+ }
121
+ /**
122
+ * Global default store (can be overridden with factory functions)
123
+ */
124
+ const defaultStore = new TodoStore();
125
+ /**
126
+ * TodoWrite tool - Update the task list
127
+ */
128
+ export const todoWriteTool = defineTool({
129
+ name: 'todo_write',
130
+ description: 'Update the task list. Provide the complete list of todos. ' +
131
+ 'Use this to track progress on multi-step tasks.',
132
+ inputSchema: {
133
+ type: 'object',
134
+ properties: {
135
+ todos: {
136
+ type: 'array',
137
+ description: 'The complete list of todos',
138
+ items: {
139
+ type: 'object',
140
+ properties: {
141
+ content: {
142
+ type: 'string',
143
+ description: 'Task description (imperative form, e.g., "Fix bug in login")',
144
+ },
145
+ status: {
146
+ type: 'string',
147
+ enum: ['pending', 'in_progress', 'completed'],
148
+ description: 'Task status',
149
+ },
150
+ activeForm: {
151
+ type: 'string',
152
+ description: 'Active form (present continuous, e.g., "Fixing bug in login")',
153
+ },
154
+ priority: {
155
+ type: 'number',
156
+ description: 'Priority (higher = more important)',
157
+ },
158
+ },
159
+ required: ['content', 'status'],
160
+ },
161
+ },
162
+ },
163
+ required: ['todos'],
164
+ },
165
+ execute: (input) => {
166
+ // Validate todos
167
+ for (const todo of input.todos) {
168
+ if (!todo.content || todo.content.trim() === '') {
169
+ return Promise.resolve(createErrorResult('Todo content cannot be empty'));
170
+ }
171
+ if (!['pending', 'in_progress', 'completed'].includes(todo.status)) {
172
+ return Promise.resolve(createErrorResult(`Invalid status: ${todo.status}`));
173
+ }
174
+ }
175
+ defaultStore.setAll(input.todos);
176
+ const counts = defaultStore.getCounts();
177
+ return Promise.resolve(createSuccessResult({
178
+ message: 'Todos updated successfully',
179
+ counts,
180
+ total: input.todos.length,
181
+ }));
182
+ },
183
+ });
184
+ /**
185
+ * TodoRead tool - Get the current task list
186
+ */
187
+ export const todoReadTool = defineTool({
188
+ name: 'todo_read',
189
+ description: 'Get the current task list. Optionally filter by status.',
190
+ inputSchema: {
191
+ type: 'object',
192
+ properties: {
193
+ status: {
194
+ type: 'string',
195
+ enum: ['pending', 'in_progress', 'completed'],
196
+ description: 'Filter by status (optional)',
197
+ },
198
+ includeCompleted: {
199
+ type: 'boolean',
200
+ description: 'Include completed tasks (default: true)',
201
+ },
202
+ },
203
+ },
204
+ execute: (input) => {
205
+ let todos = defaultStore.getAll();
206
+ // Filter by status if specified
207
+ if (input.status) {
208
+ todos = todos.filter((t) => t.status === input.status);
209
+ }
210
+ // Filter out completed if requested
211
+ if (input.includeCompleted === false) {
212
+ todos = todos.filter((t) => t.status !== 'completed');
213
+ }
214
+ const counts = defaultStore.getCounts();
215
+ return Promise.resolve(createSuccessResult({
216
+ todos: todos.map((t) => ({
217
+ id: t.id,
218
+ content: t.content,
219
+ status: t.status,
220
+ activeForm: t.activeForm,
221
+ priority: t.priority,
222
+ })),
223
+ counts,
224
+ total: todos.length,
225
+ }));
226
+ },
227
+ });
228
+ /**
229
+ * Factory to create todo tools with a custom store
230
+ */
231
+ export function createTodoTools(store) {
232
+ const todoStore = store ?? new TodoStore();
233
+ const todoWrite = defineTool({
234
+ name: 'todo_write',
235
+ description: 'Update the task list. Provide the complete list of todos. ' +
236
+ 'Use this to track progress on multi-step tasks.',
237
+ inputSchema: {
238
+ type: 'object',
239
+ properties: {
240
+ todos: {
241
+ type: 'array',
242
+ description: 'The complete list of todos',
243
+ items: {
244
+ type: 'object',
245
+ properties: {
246
+ content: { type: 'string' },
247
+ status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] },
248
+ activeForm: { type: 'string' },
249
+ priority: { type: 'number' },
250
+ },
251
+ required: ['content', 'status'],
252
+ },
253
+ },
254
+ },
255
+ required: ['todos'],
256
+ },
257
+ execute: (input) => {
258
+ for (const todo of input.todos) {
259
+ if (!todo.content || todo.content.trim() === '') {
260
+ return Promise.resolve(createErrorResult('Todo content cannot be empty'));
261
+ }
262
+ if (!['pending', 'in_progress', 'completed'].includes(todo.status)) {
263
+ return Promise.resolve(createErrorResult(`Invalid status: ${todo.status}`));
264
+ }
265
+ }
266
+ todoStore.setAll(input.todos);
267
+ const counts = todoStore.getCounts();
268
+ return Promise.resolve(createSuccessResult({
269
+ message: 'Todos updated successfully',
270
+ counts,
271
+ total: input.todos.length,
272
+ }));
273
+ },
274
+ });
275
+ const todoRead = defineTool({
276
+ name: 'todo_read',
277
+ description: 'Get the current task list. Optionally filter by status.',
278
+ inputSchema: {
279
+ type: 'object',
280
+ properties: {
281
+ status: {
282
+ type: 'string',
283
+ enum: ['pending', 'in_progress', 'completed'],
284
+ },
285
+ includeCompleted: {
286
+ type: 'boolean',
287
+ },
288
+ },
289
+ },
290
+ execute: (input) => {
291
+ let todos = todoStore.getAll();
292
+ if (input.status) {
293
+ todos = todos.filter((t) => t.status === input.status);
294
+ }
295
+ if (input.includeCompleted === false) {
296
+ todos = todos.filter((t) => t.status !== 'completed');
297
+ }
298
+ const counts = todoStore.getCounts();
299
+ return Promise.resolve(createSuccessResult({
300
+ todos: todos.map((t) => ({
301
+ id: t.id,
302
+ content: t.content,
303
+ status: t.status,
304
+ activeForm: t.activeForm,
305
+ priority: t.priority,
306
+ })),
307
+ counts,
308
+ total: todos.length,
309
+ }));
310
+ },
311
+ });
312
+ return { todoWrite, todoRead, store: todoStore };
313
+ }
314
+ /**
315
+ * Reset the default store (useful for testing)
316
+ */
317
+ export function resetDefaultTodoStore() {
318
+ defaultStore.clear();
319
+ }
320
+ /**
321
+ * Get the default store (useful for testing or direct access)
322
+ */
323
+ export function getDefaultTodoStore() {
324
+ return defaultStore;
325
+ }
326
+ /**
327
+ * Create a new isolated TodoStore instance.
328
+ *
329
+ * Use this when you need state isolation between parallel operations,
330
+ * such as concurrent sub-agent executions. This prevents state leakage
331
+ * between parallel runs.
332
+ *
333
+ * Inspired by LangGraph issue #6446: Parallel subgraphs with shared
334
+ * state keys cause InvalidUpdateError.
335
+ *
336
+ * @example
337
+ * ```typescript
338
+ * // For parallel sub-agents, create isolated stores
339
+ * const store1 = createIsolatedTodoStore();
340
+ * const store2 = createIsolatedTodoStore();
341
+ *
342
+ * // Each sub-agent uses its own store
343
+ * await Promise.all([
344
+ * runWithStore(store1, task1),
345
+ * runWithStore(store2, task2),
346
+ * ]);
347
+ * ```
348
+ */
349
+ export function createIsolatedTodoStore() {
350
+ return new TodoStore();
351
+ }
352
+ /**
353
+ * Filter messages to remove redundant todo tool calls.
354
+ * This helps prevent context bloat from repeated todo_write calls.
355
+ *
356
+ * The function keeps only the last N todo_write calls (default: 1),
357
+ * removing earlier ones to reduce context size.
358
+ *
359
+ * @param messages - Array of messages to filter
360
+ * @param options - Cleanup options
361
+ * @returns Filtered messages with redundant todo calls removed
362
+ */
363
+ export function cleanupTodoContextMessages(messages, options) {
364
+ const { keepLastN = 1, cleanReads = false } = options ?? {};
365
+ // Find indices of all todo_write tool uses and results
366
+ const todoWriteIndices = [];
367
+ const todoReadIndices = [];
368
+ for (let i = 0; i < messages.length; i++) {
369
+ const message = messages[i];
370
+ if (typeof message.content === 'string')
371
+ continue;
372
+ const content = message.content;
373
+ if (!Array.isArray(content))
374
+ continue;
375
+ for (const block of content) {
376
+ if (block.type === 'tool_use' && block.name === 'todo_write') {
377
+ todoWriteIndices.push(i);
378
+ break;
379
+ }
380
+ if (block.type === 'tool_result') {
381
+ // Check if this is a result for todo_write
382
+ // We need to track tool_use IDs to match them, but for simplicity
383
+ // we'll just mark messages that follow todo_write calls
384
+ }
385
+ if (cleanReads && block.type === 'tool_use' && block.name === 'todo_read') {
386
+ todoReadIndices.push(i);
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ // Determine which todo_write indices to remove (keep last N)
392
+ const indicesToRemove = new Set();
393
+ if (todoWriteIndices.length > keepLastN) {
394
+ const toRemove = todoWriteIndices.slice(0, -keepLastN);
395
+ for (const idx of toRemove) {
396
+ indicesToRemove.add(idx);
397
+ // Also remove the following message if it's a tool result
398
+ if (idx + 1 < messages.length) {
399
+ const nextMessage = messages[idx + 1];
400
+ if (nextMessage.role === 'user' && typeof nextMessage.content !== 'string') {
401
+ const content = nextMessage.content;
402
+ if (Array.isArray(content) && content.some((b) => b.type === 'tool_result')) {
403
+ indicesToRemove.add(idx + 1);
404
+ }
405
+ }
406
+ }
407
+ }
408
+ }
409
+ // Optionally remove old todo_read calls (keep last N)
410
+ if (cleanReads && todoReadIndices.length > keepLastN) {
411
+ const toRemove = todoReadIndices.slice(0, -keepLastN);
412
+ for (const idx of toRemove) {
413
+ indicesToRemove.add(idx);
414
+ if (idx + 1 < messages.length) {
415
+ const nextMessage = messages[idx + 1];
416
+ if (nextMessage.role === 'user' && typeof nextMessage.content !== 'string') {
417
+ const content = nextMessage.content;
418
+ if (Array.isArray(content) && content.some((b) => b.type === 'tool_result')) {
419
+ indicesToRemove.add(idx + 1);
420
+ }
421
+ }
422
+ }
423
+ }
424
+ }
425
+ // Filter out removed indices
426
+ return messages.filter((_, idx) => !indicesToRemove.has(idx));
427
+ }
428
+ /**
429
+ * Get statistics about todo tool usage in messages
430
+ */
431
+ export function getTodoContextStats(messages) {
432
+ let todoWriteCalls = 0;
433
+ let todoReadCalls = 0;
434
+ for (const message of messages) {
435
+ if (typeof message.content === 'string')
436
+ continue;
437
+ const content = message.content;
438
+ if (!Array.isArray(content))
439
+ continue;
440
+ for (const block of content) {
441
+ if (block.type === 'tool_use' && block.name === 'todo_write') {
442
+ todoWriteCalls++;
443
+ }
444
+ if (block.type === 'tool_use' && block.name === 'todo_read') {
445
+ todoReadCalls++;
446
+ }
447
+ }
448
+ }
449
+ // Rough estimate: each todo call + result is ~200-500 tokens
450
+ const avgTokensPerCall = 300;
451
+ const estimatedTokensSaved = Math.max(0, todoWriteCalls - 1) * avgTokensPerCall;
452
+ return { todoWriteCalls, todoReadCalls, estimatedTokensSaved };
453
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared utilities for builtin tools
3
+ */
4
+ /**
5
+ * Type guard for Node.js errors with code property
6
+ */
7
+ export declare function isNodeError(error: unknown): error is NodeJS.ErrnoException;
8
+ /**
9
+ * Get the file extension from a path, including the leading dot.
10
+ * Returns empty string if no extension found.
11
+ *
12
+ * @example
13
+ * getExtension('/path/to/file.ts') // returns '.ts'
14
+ * getExtension('/path/to/file') // returns ''
15
+ * getExtension('/path/to/.gitignore') // returns '' (dotfile, not extension)
16
+ */
17
+ export declare function getExtension(filePath: string): string;
18
+ /**
19
+ * Check if a file extension is in the allowed list.
20
+ * Handles both formats: ['.ts', '.js'] or ['ts', 'js']
21
+ */
22
+ export declare function isExtensionAllowed(filePath: string, allowedExtensions: string[]): boolean;
23
+ /**
24
+ * Write a file atomically by writing to a temp file first, then renaming.
25
+ * This prevents file corruption if the write is interrupted.
26
+ */
27
+ export declare function atomicWriteFile(filePath: string, content: string, encoding?: BufferEncoding): Promise<void>;