@probelabs/probe 0.6.0-rc203 → 0.6.0-rc205

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 (44) hide show
  1. package/bin/binaries/probe-v0.6.0-rc205-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc205-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc205-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc205-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc205-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.d.ts +2 -0
  7. package/build/agent/ProbeAgent.js +233 -40
  8. package/build/agent/index.js +1566 -84
  9. package/build/agent/simpleTelemetry.js +12 -0
  10. package/build/agent/tasks/TaskManager.js +604 -0
  11. package/build/agent/tasks/index.js +15 -0
  12. package/build/agent/tasks/taskTool.js +476 -0
  13. package/build/agent/tools.js +11 -0
  14. package/build/delegate.js +7 -2
  15. package/build/index.js +14 -1
  16. package/build/search.js +19 -5
  17. package/build/tools/common.js +67 -0
  18. package/build/tools/vercel.js +28 -12
  19. package/build/utils/error-types.js +303 -0
  20. package/build/utils/path-validation.js +21 -3
  21. package/cjs/agent/ProbeAgent.cjs +8940 -6393
  22. package/cjs/agent/simpleTelemetry.cjs +10 -0
  23. package/cjs/index.cjs +8960 -6393
  24. package/package.json +2 -2
  25. package/src/agent/ProbeAgent.d.ts +2 -0
  26. package/src/agent/ProbeAgent.js +233 -40
  27. package/src/agent/index.js +14 -2
  28. package/src/agent/simpleTelemetry.js +12 -0
  29. package/src/agent/tasks/TaskManager.js +604 -0
  30. package/src/agent/tasks/index.js +15 -0
  31. package/src/agent/tasks/taskTool.js +476 -0
  32. package/src/agent/tools.js +11 -0
  33. package/src/delegate.js +7 -2
  34. package/src/index.js +14 -1
  35. package/src/search.js +19 -5
  36. package/src/tools/common.js +67 -0
  37. package/src/tools/vercel.js +28 -12
  38. package/src/utils/error-types.js +303 -0
  39. package/src/utils/path-validation.js +21 -3
  40. package/bin/binaries/probe-v0.6.0-rc203-aarch64-apple-darwin.tar.gz +0 -0
  41. package/bin/binaries/probe-v0.6.0-rc203-aarch64-unknown-linux-musl.tar.gz +0 -0
  42. package/bin/binaries/probe-v0.6.0-rc203-x86_64-apple-darwin.tar.gz +0 -0
  43. package/bin/binaries/probe-v0.6.0-rc203-x86_64-pc-windows-msvc.zip +0 -0
  44. package/bin/binaries/probe-v0.6.0-rc203-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -219,6 +219,18 @@ export class SimpleAppTracer {
219
219
  });
220
220
  }
221
221
 
222
+ /**
223
+ * Record task management events
224
+ */
225
+ recordTaskEvent(eventType, data = {}) {
226
+ if (!this.isEnabled()) return;
227
+
228
+ this.addEvent(`task.${eventType}`, {
229
+ 'session.id': this.sessionId,
230
+ ...data
231
+ });
232
+ }
233
+
222
234
  setAttributes(attributes) {
223
235
  // For simplicity, just log attributes when no active span
224
236
  if (this.telemetry && this.telemetry.enableConsole) {
@@ -0,0 +1,604 @@
1
+ /**
2
+ * TaskManager - Manages tasks for tracking agent progress
3
+ * @module agent/tasks/TaskManager
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} Task
8
+ * @property {string} id - Unique task identifier (e.g., "task-1")
9
+ * @property {string} title - Short task description
10
+ * @property {string} [description] - Detailed task description
11
+ * @property {'pending'|'in_progress'|'completed'|'cancelled'} status - Current task status
12
+ * @property {'low'|'medium'|'high'|'critical'} [priority] - Task priority
13
+ * @property {string[]} dependencies - Array of task IDs that must complete first
14
+ * @property {string} createdAt - ISO timestamp of creation
15
+ * @property {string} updatedAt - ISO timestamp of last update
16
+ * @property {string} [completedAt] - ISO timestamp when completed
17
+ */
18
+
19
+ /**
20
+ * TaskManager class for managing tasks within a ProbeAgent session
21
+ */
22
+ export class TaskManager {
23
+ /**
24
+ * Create a new TaskManager instance
25
+ * @param {Object} [options] - Configuration options
26
+ * @param {boolean} [options.debug=false] - Enable debug logging
27
+ */
28
+ constructor(options = {}) {
29
+ /** @type {Map<string, Task>} */
30
+ this.tasks = new Map();
31
+ this.taskCounter = 0;
32
+ this.debug = options.debug || false;
33
+ }
34
+
35
+ /**
36
+ * Generate the next task ID
37
+ * @returns {string} New task ID
38
+ * @private
39
+ */
40
+ _generateId() {
41
+ this.taskCounter++;
42
+ return `task-${this.taskCounter}`;
43
+ }
44
+
45
+ /**
46
+ * Get current timestamp in ISO format
47
+ * @returns {string} ISO timestamp
48
+ * @private
49
+ */
50
+ _now() {
51
+ return new Date().toISOString();
52
+ }
53
+
54
+ /**
55
+ * Create a single task
56
+ * @param {Object} taskData - Task data
57
+ * @param {string} taskData.title - Task title
58
+ * @param {string} [taskData.description] - Task description
59
+ * @param {'low'|'medium'|'high'|'critical'} [taskData.priority] - Task priority
60
+ * @param {string[]} [taskData.dependencies] - Task IDs this task depends on
61
+ * @param {string} [taskData.after] - Task ID to insert this task after (for ordering)
62
+ * @returns {Task} Created task
63
+ * @throws {Error} If dependencies are invalid or create a cycle
64
+ */
65
+ createTask(taskData) {
66
+ const id = this._generateId();
67
+ const now = this._now();
68
+
69
+ // Validate dependencies exist
70
+ const dependencies = taskData.dependencies || [];
71
+ for (const depId of dependencies) {
72
+ if (!this.tasks.has(depId)) {
73
+ throw new Error(`Dependency "${depId}" does not exist. Available tasks: ${this._getAvailableTaskIds()}`);
74
+ }
75
+ }
76
+
77
+ // Validate 'after' task exists if specified
78
+ const afterTaskId = taskData.after;
79
+ if (afterTaskId && !this.tasks.has(afterTaskId)) {
80
+ throw new Error(`Task "${afterTaskId}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}`);
81
+ }
82
+
83
+ // Check for circular dependencies
84
+ if (dependencies.length > 0 && !this._validateNoCycle(id, dependencies)) {
85
+ throw new Error(`Adding dependencies [${dependencies.join(', ')}] to "${id}" would create a circular dependency`);
86
+ }
87
+
88
+ const task = {
89
+ id,
90
+ title: taskData.title,
91
+ description: taskData.description || null,
92
+ status: 'pending',
93
+ priority: taskData.priority || null,
94
+ dependencies,
95
+ createdAt: now,
96
+ updatedAt: now,
97
+ completedAt: null
98
+ };
99
+
100
+ // Insert task at the correct position
101
+ if (afterTaskId) {
102
+ this._insertAfter(afterTaskId, id, task);
103
+ } else {
104
+ this.tasks.set(id, task);
105
+ }
106
+
107
+ if (this.debug) {
108
+ console.log(`[TaskManager] Created task: ${id} - ${task.title}${afterTaskId ? ` (after ${afterTaskId})` : ''}`);
109
+ }
110
+
111
+ return task;
112
+ }
113
+
114
+ /**
115
+ * Insert a task after a specific task in the Map order
116
+ * @param {string} afterId - Task ID to insert after
117
+ * @param {string} newId - New task ID
118
+ * @param {Task} newTask - New task object
119
+ * @private
120
+ */
121
+ _insertAfter(afterId, newId, newTask) {
122
+ const newTasks = new Map();
123
+
124
+ for (const [id, task] of this.tasks) {
125
+ newTasks.set(id, task);
126
+ if (id === afterId) {
127
+ newTasks.set(newId, newTask);
128
+ }
129
+ }
130
+
131
+ this.tasks = newTasks;
132
+ }
133
+
134
+ /**
135
+ * Create multiple tasks in batch
136
+ * @param {Object[]} tasksData - Array of task data objects
137
+ * @returns {Task[]} Created tasks
138
+ */
139
+ createTasks(tasksData) {
140
+ const createdTasks = [];
141
+
142
+ for (const taskData of tasksData) {
143
+ const task = this.createTask(taskData);
144
+ createdTasks.push(task);
145
+ }
146
+
147
+ return createdTasks;
148
+ }
149
+
150
+ /**
151
+ * Get a task by ID
152
+ * @param {string} id - Task ID
153
+ * @returns {Task|null} Task or null if not found
154
+ */
155
+ getTask(id) {
156
+ return this.tasks.get(id) || null;
157
+ }
158
+
159
+ /**
160
+ * Valid status values for tasks
161
+ * @type {string[]}
162
+ * @private
163
+ */
164
+ static VALID_STATUSES = ['pending', 'in_progress', 'completed', 'cancelled'];
165
+
166
+ /**
167
+ * Valid priority values for tasks
168
+ * @type {string[]}
169
+ * @private
170
+ */
171
+ static VALID_PRIORITIES = ['low', 'medium', 'high', 'critical'];
172
+
173
+ /**
174
+ * Update a task
175
+ * @param {string} id - Task ID
176
+ * @param {Object} updates - Fields to update
177
+ * @returns {Task} Updated task
178
+ * @throws {Error} If task not found or update is invalid
179
+ */
180
+ updateTask(id, updates) {
181
+ const task = this.tasks.get(id);
182
+ if (!task) {
183
+ throw new Error(`Task "${id}" not found. Available tasks: ${this._getAvailableTaskIds()}`);
184
+ }
185
+
186
+ // Validate status if provided
187
+ if (updates.status !== undefined) {
188
+ if (!TaskManager.VALID_STATUSES.includes(updates.status)) {
189
+ throw new Error(`Invalid status "${updates.status}". Valid statuses: ${TaskManager.VALID_STATUSES.join(', ')}`);
190
+ }
191
+ if (updates.status === 'completed' && !task.completedAt) {
192
+ updates.completedAt = this._now();
193
+ }
194
+ }
195
+
196
+ // Validate priority if provided
197
+ if (updates.priority !== undefined && updates.priority !== null) {
198
+ if (!TaskManager.VALID_PRIORITIES.includes(updates.priority)) {
199
+ throw new Error(`Invalid priority "${updates.priority}". Valid priorities: ${TaskManager.VALID_PRIORITIES.join(', ')}`);
200
+ }
201
+ }
202
+
203
+ // Handle dependency updates
204
+ if (updates.dependencies !== undefined) {
205
+ // Validate dependencies is an array
206
+ if (!Array.isArray(updates.dependencies)) {
207
+ throw new Error('Dependencies must be an array');
208
+ }
209
+
210
+ // Validate new dependencies exist
211
+ for (const depId of updates.dependencies) {
212
+ if (!this.tasks.has(depId)) {
213
+ throw new Error(`Dependency "${depId}" does not exist. Available tasks: ${this._getAvailableTaskIds()}`);
214
+ }
215
+ }
216
+
217
+ // Check for circular dependencies
218
+ if (!this._validateNoCycle(id, updates.dependencies)) {
219
+ throw new Error(`Adding dependencies [${updates.dependencies.join(', ')}] to "${id}" would create a circular dependency`);
220
+ }
221
+ }
222
+
223
+ // Apply updates
224
+ const updatedTask = {
225
+ ...task,
226
+ ...updates,
227
+ id, // Ensure ID cannot be changed
228
+ createdAt: task.createdAt, // Ensure createdAt cannot be changed
229
+ updatedAt: this._now()
230
+ };
231
+
232
+ this.tasks.set(id, updatedTask);
233
+
234
+ if (this.debug) {
235
+ console.log(`[TaskManager] Updated task: ${id}`, updates);
236
+ }
237
+
238
+ return updatedTask;
239
+ }
240
+
241
+ /**
242
+ * Update multiple tasks in batch
243
+ * @param {Object[]} updates - Array of {id, ...updates} objects
244
+ * @returns {Task[]} Updated tasks
245
+ */
246
+ updateTasks(updates) {
247
+ const updatedTasks = [];
248
+
249
+ for (const update of updates) {
250
+ const { id, ...taskUpdates } = update;
251
+ const task = this.updateTask(id, taskUpdates);
252
+ updatedTasks.push(task);
253
+ }
254
+
255
+ return updatedTasks;
256
+ }
257
+
258
+ /**
259
+ * Delete a task
260
+ * @param {string} id - Task ID
261
+ * @returns {boolean} True if deleted
262
+ * @throws {Error} If task has dependents
263
+ */
264
+ deleteTask(id) {
265
+ const task = this.tasks.get(id);
266
+ if (!task) {
267
+ throw new Error(`Task "${id}" not found. Available tasks: ${this._getAvailableTaskIds()}`);
268
+ }
269
+
270
+ // Check if other tasks depend on this one
271
+ const dependents = this._getDependents(id);
272
+ if (dependents.length > 0) {
273
+ throw new Error(`Cannot delete "${id}" - other tasks depend on it: ${dependents.join(', ')}`);
274
+ }
275
+
276
+ this.tasks.delete(id);
277
+
278
+ if (this.debug) {
279
+ console.log(`[TaskManager] Deleted task: ${id}`);
280
+ }
281
+
282
+ return true;
283
+ }
284
+
285
+ /**
286
+ * Delete multiple tasks in batch
287
+ * @param {string[]} ids - Task IDs to delete
288
+ * @returns {string[]} Deleted task IDs
289
+ */
290
+ deleteTasks(ids) {
291
+ const deletedIds = [];
292
+
293
+ for (const id of ids) {
294
+ this.deleteTask(id);
295
+ deletedIds.push(id);
296
+ }
297
+
298
+ return deletedIds;
299
+ }
300
+
301
+ /**
302
+ * Mark a task as completed
303
+ * @param {string} id - Task ID
304
+ * @returns {Task} Updated task
305
+ */
306
+ completeTask(id) {
307
+ return this.updateTask(id, { status: 'completed' });
308
+ }
309
+
310
+ /**
311
+ * Mark multiple tasks as completed
312
+ * @param {string[]} ids - Task IDs
313
+ * @returns {Task[]} Updated tasks
314
+ */
315
+ completeTasks(ids) {
316
+ return ids.map(id => this.completeTask(id));
317
+ }
318
+
319
+ /**
320
+ * List all tasks
321
+ * @param {Object} [filter] - Optional filter
322
+ * @param {'pending'|'in_progress'|'completed'|'cancelled'} [filter.status] - Filter by status
323
+ * @returns {Task[]} Array of tasks
324
+ */
325
+ listTasks(filter = {}) {
326
+ let tasks = Array.from(this.tasks.values());
327
+
328
+ if (filter.status) {
329
+ tasks = tasks.filter(t => t.status === filter.status);
330
+ }
331
+
332
+ return tasks;
333
+ }
334
+
335
+ /**
336
+ * Check if there are any incomplete tasks (pending or in_progress)
337
+ * @returns {boolean} True if there are incomplete tasks
338
+ */
339
+ hasIncompleteTasks() {
340
+ for (const task of this.tasks.values()) {
341
+ if (task.status === 'pending' || task.status === 'in_progress') {
342
+ return true;
343
+ }
344
+ }
345
+ return false;
346
+ }
347
+
348
+ /**
349
+ * Get incomplete tasks (pending or in_progress)
350
+ * @returns {Task[]} Array of incomplete tasks
351
+ */
352
+ getIncompleteTasks() {
353
+ return Array.from(this.tasks.values()).filter(
354
+ t => t.status === 'pending' || t.status === 'in_progress'
355
+ );
356
+ }
357
+
358
+ /**
359
+ * Check if a dependency is resolved (completed or cancelled)
360
+ * @param {string} depId - Dependency task ID
361
+ * @returns {boolean} True if dependency is resolved or doesn't exist
362
+ * @private
363
+ */
364
+ _isDependencyResolved(depId) {
365
+ const dep = this.tasks.get(depId);
366
+ // If dependency doesn't exist, treat as resolved (shouldn't happen with validation)
367
+ if (!dep) return true;
368
+ return dep.status === 'completed' || dep.status === 'cancelled';
369
+ }
370
+
371
+ /**
372
+ * Get tasks that are ready to start (all dependencies completed)
373
+ * @returns {Task[]} Array of ready tasks
374
+ */
375
+ getReadyTasks() {
376
+ return Array.from(this.tasks.values()).filter(task => {
377
+ if (task.status !== 'pending') return false;
378
+ return task.dependencies.every(depId => this._isDependencyResolved(depId));
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Get blocked tasks (have incomplete dependencies)
384
+ * @returns {Task[]} Array of blocked tasks
385
+ */
386
+ getBlockedTasks() {
387
+ return Array.from(this.tasks.values()).filter(task => {
388
+ if (task.status !== 'pending') return false;
389
+ return task.dependencies.some(depId => !this._isDependencyResolved(depId));
390
+ });
391
+ }
392
+
393
+ /**
394
+ * Get human-readable task summary for checkpoint messages
395
+ * @returns {string} Formatted task summary
396
+ */
397
+ getTaskSummary() {
398
+ const tasks = this.listTasks();
399
+ if (tasks.length === 0) {
400
+ return 'No tasks created.';
401
+ }
402
+
403
+ const lines = ['Tasks:'];
404
+ for (const task of tasks) {
405
+ let line = `- [${task.status}] ${task.id}: ${task.title}`;
406
+
407
+ // Add blocking info for pending tasks
408
+ if (task.status === 'pending' && task.dependencies.length > 0) {
409
+ const blockers = task.dependencies.filter(depId => !this._isDependencyResolved(depId));
410
+ if (blockers.length > 0) {
411
+ line += ` (blocked by: ${blockers.join(', ')})`;
412
+ }
413
+ }
414
+
415
+ lines.push(line);
416
+ }
417
+
418
+ return lines.join('\n');
419
+ }
420
+
421
+ /**
422
+ * Escape XML special characters to prevent injection
423
+ * @param {string} str - String to escape
424
+ * @returns {string} Escaped string
425
+ * @private
426
+ */
427
+ _escapeXml(str) {
428
+ if (typeof str !== 'string') return String(str);
429
+ return str.replace(/[<>&'"]/g, c => ({
430
+ '<': '&lt;',
431
+ '>': '&gt;',
432
+ '&': '&amp;',
433
+ "'": '&apos;',
434
+ '"': '&quot;'
435
+ }[c]));
436
+ }
437
+
438
+ /**
439
+ * Format tasks for inclusion in AI prompts
440
+ * @returns {string} XML-formatted task list
441
+ */
442
+ formatTasksForPrompt() {
443
+ const tasks = this.listTasks();
444
+ if (tasks.length === 0) {
445
+ return '<task_status>No tasks created.</task_status>';
446
+ }
447
+
448
+ const taskLines = tasks.map(task => {
449
+ const blockers = task.dependencies.filter(depId => !this._isDependencyResolved(depId));
450
+
451
+ let line = ` <task id="${this._escapeXml(task.id)}" status="${this._escapeXml(task.status)}"`;
452
+ if (task.priority) line += ` priority="${this._escapeXml(task.priority)}"`;
453
+ if (blockers.length > 0) line += ` blocked_by="${this._escapeXml(blockers.join(','))}"`;
454
+ line += `>${this._escapeXml(task.title)}</task>`;
455
+
456
+ return line;
457
+ });
458
+
459
+ return `<task_status>\n${taskLines.join('\n')}\n</task_status>`;
460
+ }
461
+
462
+ /**
463
+ * Clear all tasks
464
+ */
465
+ clear() {
466
+ this.tasks.clear();
467
+ this.taskCounter = 0;
468
+
469
+ if (this.debug) {
470
+ console.log('[TaskManager] Cleared all tasks');
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Export tasks for persistence
476
+ * @returns {Object} Serializable task data
477
+ */
478
+ export() {
479
+ return {
480
+ tasks: Array.from(this.tasks.entries()),
481
+ taskCounter: this.taskCounter
482
+ };
483
+ }
484
+
485
+ /**
486
+ * Import tasks from exported data
487
+ * @param {Object} data - Exported task data
488
+ * @throws {Error} If data is invalid or malformed
489
+ */
490
+ import(data) {
491
+ // Validate data structure to prevent prototype pollution
492
+ if (!data || typeof data !== 'object') {
493
+ throw new Error('Invalid import data: must be an object');
494
+ }
495
+ if (Object.prototype.hasOwnProperty.call(data, '__proto__') ||
496
+ Object.prototype.hasOwnProperty.call(data, 'constructor')) {
497
+ throw new Error('Invalid import data: prototype pollution attempt detected');
498
+ }
499
+ if (!Array.isArray(data.tasks)) {
500
+ throw new Error('Invalid import data: tasks must be an array');
501
+ }
502
+ if (typeof data.taskCounter !== 'number' || !Number.isInteger(data.taskCounter) || data.taskCounter < 0) {
503
+ throw new Error('Invalid import data: taskCounter must be a non-negative integer');
504
+ }
505
+
506
+ // Validate each task entry
507
+ for (const entry of data.tasks) {
508
+ if (!Array.isArray(entry) || entry.length !== 2) {
509
+ throw new Error('Invalid import data: each task entry must be a [id, task] tuple');
510
+ }
511
+ const [id, task] = entry;
512
+ if (typeof id !== 'string') {
513
+ throw new Error('Invalid import data: task id must be a string');
514
+ }
515
+ if (!task || typeof task !== 'object') {
516
+ throw new Error('Invalid import data: task must be an object');
517
+ }
518
+ if (Object.prototype.hasOwnProperty.call(task, '__proto__') ||
519
+ Object.prototype.hasOwnProperty.call(task, 'constructor')) {
520
+ throw new Error('Invalid import data: prototype pollution attempt detected in task');
521
+ }
522
+ }
523
+
524
+ this.tasks = new Map(data.tasks);
525
+ this.taskCounter = data.taskCounter;
526
+ }
527
+
528
+ /**
529
+ * Get list of available task IDs for error messages
530
+ * @returns {string} Comma-separated list of task IDs
531
+ * @private
532
+ */
533
+ _getAvailableTaskIds() {
534
+ const ids = Array.from(this.tasks.keys());
535
+ return ids.length > 0 ? ids.join(', ') : '(none)';
536
+ }
537
+
538
+ /**
539
+ * Get tasks that depend on a given task
540
+ * @param {string} taskId - Task ID
541
+ * @returns {string[]} Array of dependent task IDs
542
+ * @private
543
+ */
544
+ _getDependents(taskId) {
545
+ const dependents = [];
546
+ for (const [id, task] of this.tasks) {
547
+ if (task.dependencies.includes(taskId)) {
548
+ dependents.push(id);
549
+ }
550
+ }
551
+ return dependents;
552
+ }
553
+
554
+ /**
555
+ * Validate that adding dependencies won't create a cycle
556
+ * Uses DFS to detect cycles
557
+ * @param {string} taskId - Task being updated
558
+ * @param {string[]} newDependencies - New dependencies to add
559
+ * @returns {boolean} True if no cycle would be created
560
+ * @private
561
+ */
562
+ _validateNoCycle(taskId, newDependencies) {
563
+ // Build a temporary dependency graph including the new dependencies
564
+ const graph = new Map();
565
+
566
+ for (const [id, task] of this.tasks) {
567
+ graph.set(id, [...task.dependencies]);
568
+ }
569
+
570
+ // Add or update the target task's dependencies
571
+ graph.set(taskId, newDependencies);
572
+
573
+ // DFS to detect cycles
574
+ const visited = new Set();
575
+ const recursionStack = new Set();
576
+
577
+ const hasCycle = (nodeId) => {
578
+ if (recursionStack.has(nodeId)) {
579
+ return true; // Found a cycle
580
+ }
581
+ if (visited.has(nodeId)) {
582
+ return false; // Already fully explored
583
+ }
584
+
585
+ visited.add(nodeId);
586
+ recursionStack.add(nodeId);
587
+
588
+ const deps = graph.get(nodeId) || [];
589
+ for (const depId of deps) {
590
+ if (hasCycle(depId)) {
591
+ return true;
592
+ }
593
+ }
594
+
595
+ recursionStack.delete(nodeId);
596
+ return false;
597
+ };
598
+
599
+ // Check from the task being modified
600
+ return !hasCycle(taskId);
601
+ }
602
+ }
603
+
604
+ export default TaskManager;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Task Management Module
3
+ * @module agent/tasks
4
+ */
5
+
6
+ export { TaskManager, default as TaskManagerDefault } from './TaskManager.js';
7
+ export {
8
+ taskSchema,
9
+ taskToolDefinition,
10
+ taskSystemPrompt,
11
+ taskGuidancePrompt,
12
+ createTaskCompletionBlockedMessage,
13
+ createTaskTool,
14
+ default as createTaskToolDefault
15
+ } from './taskTool.js';