@probelabs/probe 0.6.0-rc203 → 0.6.0-rc204

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-rc204-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc204-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc204-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc204-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc204-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.d.ts +2 -0
  7. package/build/agent/ProbeAgent.js +224 -34
  8. package/build/agent/index.js +1508 -80
  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 +8882 -6389
  22. package/cjs/agent/simpleTelemetry.cjs +10 -0
  23. package/cjs/index.cjs +8902 -6389
  24. package/package.json +1 -1
  25. package/src/agent/ProbeAgent.d.ts +2 -0
  26. package/src/agent/ProbeAgent.js +224 -34
  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
@@ -3068,6 +3068,222 @@ var init_utils = __esm({
3068
3068
  }
3069
3069
  });
3070
3070
 
3071
+ // src/utils/error-types.js
3072
+ function escapeXml(str) {
3073
+ return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
3074
+ }
3075
+ function categorizeError(error) {
3076
+ if (error instanceof ProbeError) {
3077
+ return error;
3078
+ }
3079
+ const message = error?.message || String(error);
3080
+ const lowerMessage = message.toLowerCase();
3081
+ const errorCode = error?.code != null ? String(error.code).toLowerCase() : "";
3082
+ if (lowerMessage.includes("path does not exist") || lowerMessage.includes("no such file or directory") || errorCode === "enoent") {
3083
+ return new PathError(message, {
3084
+ originalError: error,
3085
+ suggestion: "The specified path does not exist. Please verify the path or use a different directory."
3086
+ });
3087
+ }
3088
+ if (lowerMessage.includes("not a directory") || errorCode === "enotdir") {
3089
+ return new PathError(message, {
3090
+ originalError: error,
3091
+ suggestion: "The path is not a directory. Please provide a valid directory path."
3092
+ });
3093
+ }
3094
+ if (lowerMessage.includes("permission denied") || errorCode === "eacces") {
3095
+ return new PathError(message, {
3096
+ originalError: error,
3097
+ recoverable: false,
3098
+ suggestion: "Permission denied. This is a system-level restriction that cannot be resolved by changing the query."
3099
+ });
3100
+ }
3101
+ if (lowerMessage.includes("timed out") || lowerMessage.includes("timeout") || errorCode === "etimedout" || error?.killed === true) {
3102
+ return new TimeoutError(message, {
3103
+ originalError: error,
3104
+ suggestion: "The operation timed out. Try a more specific query, reduce the search scope, or increase the timeout."
3105
+ });
3106
+ }
3107
+ const rateLimitPatterns = ["rate_limit", "rate limit", "429", "too many requests", "overloaded"];
3108
+ if (rateLimitPatterns.some((p) => lowerMessage.includes(p))) {
3109
+ return new ApiError(message, {
3110
+ originalError: error,
3111
+ recoverable: true,
3112
+ suggestion: "API rate limit or overload encountered. The system will retry automatically with backoff."
3113
+ });
3114
+ }
3115
+ const serverErrorPatterns = ["500", "502", "503", "504", "internal server error", "bad gateway", "service unavailable"];
3116
+ if (serverErrorPatterns.some((p) => lowerMessage.includes(p))) {
3117
+ return new ApiError(message, {
3118
+ originalError: error,
3119
+ recoverable: true,
3120
+ suggestion: "API server error encountered. The system will retry automatically."
3121
+ });
3122
+ }
3123
+ if ((lowerMessage.includes("context") || lowerMessage.includes("token")) && (lowerMessage.includes("limit") || lowerMessage.includes("exceed"))) {
3124
+ return new ApiError(message, {
3125
+ originalError: error,
3126
+ recoverable: true,
3127
+ suggestion: "Context or token limit exceeded. The conversation may be automatically compacted."
3128
+ });
3129
+ }
3130
+ if (lowerMessage.includes("invalid parameter") || lowerMessage.includes("missing parameter") || lowerMessage.includes("required") || lowerMessage.includes("must be")) {
3131
+ return new ParameterError(message, {
3132
+ originalError: error
3133
+ });
3134
+ }
3135
+ if (lowerMessage.includes("invalid") && (lowerMessage.includes("api") || lowerMessage.includes("key") || lowerMessage.includes("auth"))) {
3136
+ return new ApiError(message, {
3137
+ originalError: error,
3138
+ recoverable: false,
3139
+ suggestion: "API authentication error. Please check the API key configuration."
3140
+ });
3141
+ }
3142
+ if (lowerMessage.includes("delegation failed") || lowerMessage.includes("delegate agent") || lowerMessage.includes("subagent")) {
3143
+ return new DelegationError(message, {
3144
+ originalError: error
3145
+ });
3146
+ }
3147
+ if (errorCode === "econnreset" || errorCode === "econnrefused" || errorCode === "enotfound" || lowerMessage.includes("network") || lowerMessage.includes("connection")) {
3148
+ return new ApiError(message, {
3149
+ originalError: error,
3150
+ recoverable: true,
3151
+ suggestion: "Network error encountered. The system will retry automatically."
3152
+ });
3153
+ }
3154
+ return new ProbeError(message, {
3155
+ category: ErrorCategory.INTERNAL_ERROR,
3156
+ recoverable: false,
3157
+ originalError: error,
3158
+ suggestion: "An unexpected error occurred. Please check the error message for details."
3159
+ });
3160
+ }
3161
+ function formatErrorForAI(error) {
3162
+ const structuredError = categorizeError(error);
3163
+ return structuredError.toXml();
3164
+ }
3165
+ var ErrorCategory, ProbeError, PathError, ParameterError, TimeoutError, ApiError, DelegationError;
3166
+ var init_error_types = __esm({
3167
+ "src/utils/error-types.js"() {
3168
+ "use strict";
3169
+ ErrorCategory = {
3170
+ PATH_ERROR: "path_error",
3171
+ // Path doesn't exist, not a directory, permission denied
3172
+ PARAMETER_ERROR: "parameter_error",
3173
+ // Invalid parameters provided
3174
+ TIMEOUT_ERROR: "timeout_error",
3175
+ // Operation timed out
3176
+ API_ERROR: "api_error",
3177
+ // AI provider errors (rate limit, token limit)
3178
+ DELEGATION_ERROR: "delegation_error",
3179
+ // Subagent failures
3180
+ INTERNAL_ERROR: "internal_error"
3181
+ // Unexpected system errors
3182
+ };
3183
+ ProbeError = class extends Error {
3184
+ /**
3185
+ * Create a ProbeError
3186
+ * @param {string} message - Error message
3187
+ * @param {Object} options - Options
3188
+ * @param {string} [options.category] - Error category from ErrorCategory
3189
+ * @param {boolean} [options.recoverable] - Whether the error is recoverable by AI action
3190
+ * @param {string} [options.suggestion] - Suggested action for recovery
3191
+ * @param {Object} [options.details] - Additional error details
3192
+ * @param {Error} [options.originalError] - Original error that was wrapped
3193
+ */
3194
+ constructor(message, options = {}) {
3195
+ super(message);
3196
+ this.name = "ProbeError";
3197
+ this.category = options.category || ErrorCategory.INTERNAL_ERROR;
3198
+ this.recoverable = options.recoverable ?? false;
3199
+ this.suggestion = options.suggestion || null;
3200
+ this.details = options.details || {};
3201
+ this.originalError = options.originalError || null;
3202
+ }
3203
+ /**
3204
+ * Format error as XML for AI consumption
3205
+ * @returns {string} - XML-formatted error
3206
+ */
3207
+ toXml() {
3208
+ const parts = [
3209
+ `<error type="${this.category}" recoverable="${this.recoverable}">`,
3210
+ `<message>${escapeXml(this.message)}</message>`
3211
+ ];
3212
+ if (this.suggestion) {
3213
+ parts.push(`<suggestion>${escapeXml(this.suggestion)}</suggestion>`);
3214
+ }
3215
+ if (Object.keys(this.details).length > 0) {
3216
+ parts.push(`<details>${escapeXml(JSON.stringify(this.details))}</details>`);
3217
+ }
3218
+ parts.push("</error>");
3219
+ return parts.join("\n");
3220
+ }
3221
+ /**
3222
+ * Format error for plain text (backward compatible)
3223
+ * @returns {string} - Plain text error
3224
+ */
3225
+ toString() {
3226
+ return `Error: ${this.message}`;
3227
+ }
3228
+ };
3229
+ PathError = class extends ProbeError {
3230
+ constructor(message, options = {}) {
3231
+ super(message, {
3232
+ ...options,
3233
+ category: ErrorCategory.PATH_ERROR,
3234
+ recoverable: options.recoverable ?? true,
3235
+ suggestion: options.suggestion || "Please verify the path exists or try searching in a different directory."
3236
+ });
3237
+ this.name = "PathError";
3238
+ }
3239
+ };
3240
+ ParameterError = class extends ProbeError {
3241
+ constructor(message, options = {}) {
3242
+ super(message, {
3243
+ ...options,
3244
+ category: ErrorCategory.PARAMETER_ERROR,
3245
+ recoverable: options.recoverable ?? true,
3246
+ suggestion: options.suggestion || "Please check and correct the parameter values."
3247
+ });
3248
+ this.name = "ParameterError";
3249
+ }
3250
+ };
3251
+ TimeoutError = class extends ProbeError {
3252
+ constructor(message, options = {}) {
3253
+ super(message, {
3254
+ ...options,
3255
+ category: ErrorCategory.TIMEOUT_ERROR,
3256
+ recoverable: options.recoverable ?? true,
3257
+ suggestion: options.suggestion || "The operation timed out. Try a more specific query or increase the timeout."
3258
+ });
3259
+ this.name = "TimeoutError";
3260
+ }
3261
+ };
3262
+ ApiError = class extends ProbeError {
3263
+ constructor(message, options = {}) {
3264
+ super(message, {
3265
+ ...options,
3266
+ category: ErrorCategory.API_ERROR,
3267
+ recoverable: options.recoverable ?? false,
3268
+ suggestion: options.suggestion || "This is an API provider error. The system will retry automatically if possible."
3269
+ });
3270
+ this.name = "ApiError";
3271
+ }
3272
+ };
3273
+ DelegationError = class extends ProbeError {
3274
+ constructor(message, options = {}) {
3275
+ super(message, {
3276
+ ...options,
3277
+ category: ErrorCategory.DELEGATION_ERROR,
3278
+ recoverable: options.recoverable ?? true,
3279
+ suggestion: options.suggestion || "The delegated task failed. Consider breaking down the task further or trying a different approach."
3280
+ });
3281
+ this.name = "DelegationError";
3282
+ }
3283
+ };
3284
+ }
3285
+ });
3286
+
3071
3287
  // src/utils/path-validation.js
3072
3288
  import path4 from "path";
3073
3289
  import { promises as fs5 } from "fs";
@@ -3077,11 +3293,27 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
3077
3293
  try {
3078
3294
  const stats = await fs5.stat(normalizedPath);
3079
3295
  if (!stats.isDirectory()) {
3080
- throw new Error(`Path is not a directory: ${normalizedPath}`);
3296
+ throw new PathError(`Path is not a directory: ${normalizedPath}`, {
3297
+ suggestion: "The specified path is a file, not a directory. Please provide a directory path for searching.",
3298
+ details: { path: normalizedPath, type: "file" }
3299
+ });
3081
3300
  }
3082
3301
  } catch (error) {
3302
+ if (error instanceof PathError) {
3303
+ throw error;
3304
+ }
3083
3305
  if (error.code === "ENOENT") {
3084
- throw new Error(`Path does not exist: ${normalizedPath}`);
3306
+ throw new PathError(`Path does not exist: ${normalizedPath}`, {
3307
+ suggestion: "The specified path does not exist. Please verify the path is correct or use a different directory.",
3308
+ details: { path: normalizedPath }
3309
+ });
3310
+ }
3311
+ if (error.code === "EACCES") {
3312
+ throw new PathError(`Permission denied: ${normalizedPath}`, {
3313
+ recoverable: false,
3314
+ suggestion: "Permission denied accessing this path. This is a system-level restriction.",
3315
+ details: { path: normalizedPath }
3316
+ });
3085
3317
  }
3086
3318
  throw error;
3087
3319
  }
@@ -3090,6 +3322,7 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
3090
3322
  var init_path_validation = __esm({
3091
3323
  "src/utils/path-validation.js"() {
3092
3324
  "use strict";
3325
+ init_error_types();
3093
3326
  }
3094
3327
  });
3095
3328
 
@@ -3210,18 +3443,25 @@ Search results: ${resultCount} matches, ${tokenCount} tokens`;
3210
3443
  return stdout;
3211
3444
  } catch (error) {
3212
3445
  if (error.code === "ETIMEDOUT" || error.killed) {
3213
- const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.
3214
- Binary: ${binaryPath}
3215
- Args: ${args.join(" ")}
3216
- Cwd: ${cwd}`;
3446
+ const timeoutMessage = `Search operation timed out after ${options.timeout} seconds`;
3217
3447
  console.error(timeoutMessage);
3218
- throw new Error(timeoutMessage);
3448
+ throw new TimeoutError(timeoutMessage, {
3449
+ suggestion: "The search operation timed out. Try a more specific query, reduce the search scope, or increase the timeout.",
3450
+ details: {
3451
+ timeout: options.timeout,
3452
+ query: options.query,
3453
+ path: options.path
3454
+ }
3455
+ });
3219
3456
  }
3220
- const errorMessage = `Error executing search command: ${error.message}
3221
- Binary: ${binaryPath}
3222
- Args: ${args.join(" ")}
3223
- Cwd: ${cwd}`;
3224
- throw new Error(errorMessage);
3457
+ const structuredError = categorizeError(error);
3458
+ structuredError.details = {
3459
+ ...structuredError.details,
3460
+ binary: binaryPath,
3461
+ args: args.join(" "),
3462
+ cwd
3463
+ };
3464
+ throw structuredError;
3225
3465
  }
3226
3466
  }
3227
3467
  var execFileAsync, SEARCH_FLAG_MAP;
@@ -3230,6 +3470,7 @@ var init_search = __esm({
3230
3470
  "use strict";
3231
3471
  init_utils();
3232
3472
  init_path_validation();
3473
+ init_error_types();
3233
3474
  execFileAsync = promisify2(execFile);
3234
3475
  SEARCH_FLAG_MAP = {
3235
3476
  filesOnly: "--files-only",
@@ -3522,7 +3763,8 @@ async function delegate({
3522
3763
  allowedTools = null,
3523
3764
  disableTools = false,
3524
3765
  searchDelegate = void 0,
3525
- schema = null
3766
+ schema = null,
3767
+ enableTasks = false
3526
3768
  }) {
3527
3769
  if (!task || typeof task !== "string") {
3528
3770
  throw new Error("Task parameter is required and must be a string");
@@ -3577,7 +3819,9 @@ async function delegate({
3577
3819
  architectureFileName,
3578
3820
  allowedTools,
3579
3821
  disableTools,
3580
- searchDelegate
3822
+ searchDelegate,
3823
+ enableTasks
3824
+ // Inherit from parent (subagent gets isolated TaskManager)
3581
3825
  });
3582
3826
  if (debug) {
3583
3827
  console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
@@ -8202,6 +8446,439 @@ This is a new project.</content>
8202
8446
  }
8203
8447
  });
8204
8448
 
8449
+ // src/agent/tasks/taskTool.js
8450
+ function createTaskCompletionBlockedMessage(taskSummary) {
8451
+ return `<task_completion_blocked>
8452
+ You cannot complete yet. The following tasks are still unresolved:
8453
+
8454
+ ${taskSummary}
8455
+
8456
+ Required action:
8457
+ 1. For each "pending" or "in_progress" task, either:
8458
+ - Complete the work and mark it: <task><action>complete</action><id>task-X</id></task>
8459
+ - Or cancel if no longer needed: <task><action>update</action><id>task-X</id><status>cancelled</status></task>
8460
+
8461
+ 2. After ALL tasks are resolved (completed or cancelled), call attempt_completion again.
8462
+
8463
+ Use <task><action>list</action></task> to review current status.
8464
+ </task_completion_blocked>`;
8465
+ }
8466
+ function createTaskTool(options = {}) {
8467
+ const { taskManager, tracer, debug = false } = options;
8468
+ if (!taskManager) {
8469
+ throw new Error("TaskManager instance is required");
8470
+ }
8471
+ const recordTaskEvent = (eventType, data = {}) => {
8472
+ if (tracer && typeof tracer.recordTaskEvent === "function") {
8473
+ tracer.recordTaskEvent(eventType, data);
8474
+ }
8475
+ };
8476
+ return {
8477
+ name: "task",
8478
+ description: "Manage tasks for tracking progress during code exploration",
8479
+ parameters: taskSchema,
8480
+ /**
8481
+ * Execute task action
8482
+ * @param {Object} params - Tool parameters
8483
+ * @returns {string} Result message
8484
+ */
8485
+ execute: async (params) => {
8486
+ try {
8487
+ const validation = taskSchema.safeParse(params);
8488
+ if (!validation.success) {
8489
+ recordTaskEvent("validation_error", {
8490
+ "task.error": validation.error.message
8491
+ });
8492
+ return `Error: Invalid task parameters - ${validation.error.message}`;
8493
+ }
8494
+ const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
8495
+ switch (action) {
8496
+ case "create": {
8497
+ if (tasks && Array.isArray(tasks)) {
8498
+ const created = taskManager.createTasks(tasks);
8499
+ const ids = created.map((t) => t.id).join(", ");
8500
+ recordTaskEvent("batch_created", {
8501
+ "task.action": "create",
8502
+ "task.count": created.length,
8503
+ "task.ids": ids,
8504
+ "task.total_count": taskManager.listTasks().length
8505
+ });
8506
+ return `Created ${created.length} tasks: ${ids}
8507
+
8508
+ ${taskManager.formatTasksForPrompt()}`;
8509
+ } else if (title) {
8510
+ const task = taskManager.createTask({ title, description, priority, dependencies, after });
8511
+ recordTaskEvent("created", {
8512
+ "task.action": "create",
8513
+ "task.id": task.id,
8514
+ "task.title": title,
8515
+ "task.priority": priority || "none",
8516
+ "task.has_dependencies": dependencies && dependencies.length > 0,
8517
+ "task.after": after || "none",
8518
+ "task.total_count": taskManager.listTasks().length
8519
+ });
8520
+ return `Created task ${task.id}: ${task.title}
8521
+
8522
+ ${taskManager.formatTasksForPrompt()}`;
8523
+ } else {
8524
+ return 'Error: Create action requires either "tasks" array or "title" parameter';
8525
+ }
8526
+ }
8527
+ case "update": {
8528
+ if (tasks && Array.isArray(tasks)) {
8529
+ const updated = taskManager.updateTasks(tasks);
8530
+ const ids = updated.map((t) => t.id).join(", ");
8531
+ recordTaskEvent("batch_updated", {
8532
+ "task.action": "update",
8533
+ "task.count": updated.length,
8534
+ "task.ids": ids
8535
+ });
8536
+ return `Updated ${updated.length} tasks: ${ids}
8537
+
8538
+ ${taskManager.formatTasksForPrompt()}`;
8539
+ } else if (id) {
8540
+ const updates = {};
8541
+ if (status) updates.status = status;
8542
+ if (title) updates.title = title;
8543
+ if (description) updates.description = description;
8544
+ if (priority) updates.priority = priority;
8545
+ if (dependencies) updates.dependencies = dependencies;
8546
+ const task = taskManager.updateTask(id, updates);
8547
+ recordTaskEvent("updated", {
8548
+ "task.action": "update",
8549
+ "task.id": id,
8550
+ "task.new_status": status || "unchanged",
8551
+ "task.fields_updated": Object.keys(updates).join(", ")
8552
+ });
8553
+ return `Updated task ${task.id}
8554
+
8555
+ ${taskManager.formatTasksForPrompt()}`;
8556
+ } else {
8557
+ return 'Error: Update action requires either "tasks" array or "id" parameter';
8558
+ }
8559
+ }
8560
+ case "complete": {
8561
+ if (tasks && Array.isArray(tasks)) {
8562
+ const ids = tasks.map((t, index) => {
8563
+ if (typeof t === "string") return t;
8564
+ if (t && typeof t.id === "string") return t.id;
8565
+ throw new Error(`Invalid task item at index ${index}: must be a string ID or object with 'id' property`);
8566
+ });
8567
+ const completed = taskManager.completeTasks(ids);
8568
+ recordTaskEvent("batch_completed", {
8569
+ "task.action": "complete",
8570
+ "task.count": completed.length,
8571
+ "task.ids": ids.join(", "),
8572
+ "task.incomplete_remaining": taskManager.getIncompleteTasks().length
8573
+ });
8574
+ return `Completed ${completed.length} tasks
8575
+
8576
+ ${taskManager.formatTasksForPrompt()}`;
8577
+ } else if (id) {
8578
+ const task = taskManager.completeTask(id);
8579
+ recordTaskEvent("completed", {
8580
+ "task.action": "complete",
8581
+ "task.id": id,
8582
+ "task.title": task.title,
8583
+ "task.incomplete_remaining": taskManager.getIncompleteTasks().length
8584
+ });
8585
+ return `Completed task ${task.id}: ${task.title}
8586
+
8587
+ ${taskManager.formatTasksForPrompt()}`;
8588
+ } else {
8589
+ return 'Error: Complete action requires either "tasks" array or "id" parameter';
8590
+ }
8591
+ }
8592
+ case "delete": {
8593
+ if (tasks && Array.isArray(tasks)) {
8594
+ const ids = tasks.map((t, index) => {
8595
+ if (typeof t === "string") return t;
8596
+ if (t && typeof t.id === "string") return t.id;
8597
+ throw new Error(`Invalid task item at index ${index}: must be a string ID or object with 'id' property`);
8598
+ });
8599
+ const deleted = taskManager.deleteTasks(ids);
8600
+ recordTaskEvent("batch_deleted", {
8601
+ "task.action": "delete",
8602
+ "task.count": deleted.length,
8603
+ "task.ids": deleted.join(", "),
8604
+ "task.total_count": taskManager.listTasks().length
8605
+ });
8606
+ return `Deleted ${deleted.length} tasks: ${deleted.join(", ")}
8607
+
8608
+ ${taskManager.formatTasksForPrompt()}`;
8609
+ } else if (id) {
8610
+ taskManager.deleteTask(id);
8611
+ recordTaskEvent("deleted", {
8612
+ "task.action": "delete",
8613
+ "task.id": id,
8614
+ "task.total_count": taskManager.listTasks().length
8615
+ });
8616
+ return `Deleted task ${id}
8617
+
8618
+ ${taskManager.formatTasksForPrompt()}`;
8619
+ } else {
8620
+ return 'Error: Delete action requires either "tasks" array or "id" parameter';
8621
+ }
8622
+ }
8623
+ case "list": {
8624
+ const allTasks = taskManager.listTasks();
8625
+ const incomplete = taskManager.getIncompleteTasks();
8626
+ recordTaskEvent("listed", {
8627
+ "task.action": "list",
8628
+ "task.total_count": allTasks.length,
8629
+ "task.incomplete_count": incomplete.length,
8630
+ "task.completed_count": allTasks.length - incomplete.length
8631
+ });
8632
+ return taskManager.formatTasksForPrompt();
8633
+ }
8634
+ default:
8635
+ recordTaskEvent("unknown_action", {
8636
+ "task.action": action
8637
+ });
8638
+ return `Error: Unknown action "${action}". Valid actions: create, update, complete, delete, list`;
8639
+ }
8640
+ } catch (error) {
8641
+ recordTaskEvent("error", {
8642
+ "task.error": error.message,
8643
+ "task.action": params?.action || "unknown"
8644
+ });
8645
+ if (debug) {
8646
+ console.error("[TaskTool] Error:", error);
8647
+ }
8648
+ return `Error: ${error.message}`;
8649
+ }
8650
+ }
8651
+ };
8652
+ }
8653
+ var taskItemSchema, taskSchema, taskToolDefinition, taskSystemPrompt, taskGuidancePrompt;
8654
+ var init_taskTool = __esm({
8655
+ "src/agent/tasks/taskTool.js"() {
8656
+ "use strict";
8657
+ init_zod();
8658
+ taskItemSchema = external_exports.object({
8659
+ id: external_exports.string().optional(),
8660
+ title: external_exports.string().optional(),
8661
+ description: external_exports.string().optional(),
8662
+ status: external_exports.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
8663
+ priority: external_exports.enum(["low", "medium", "high", "critical"]).optional(),
8664
+ dependencies: external_exports.array(external_exports.string()).optional(),
8665
+ after: external_exports.string().optional()
8666
+ });
8667
+ taskSchema = external_exports.object({
8668
+ action: external_exports.enum(["create", "update", "complete", "delete", "list"]),
8669
+ tasks: external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])).optional(),
8670
+ id: external_exports.string().optional(),
8671
+ title: external_exports.string().optional(),
8672
+ description: external_exports.string().optional(),
8673
+ status: external_exports.enum(["pending", "in_progress", "completed", "cancelled"]).optional(),
8674
+ priority: external_exports.enum(["low", "medium", "high", "critical"]).optional(),
8675
+ dependencies: external_exports.array(external_exports.string()).optional(),
8676
+ after: external_exports.string().optional()
8677
+ });
8678
+ taskToolDefinition = `## task
8679
+ Manage tasks for tracking progress during code exploration and problem-solving. Create tasks to break down complex problems, track dependencies, and ensure all work is completed.
8680
+
8681
+ Parameters:
8682
+ - action: (required) The action to perform: create, update, complete, delete, list
8683
+ - tasks: (optional) JSON array for batch operations - alternative to single-task params
8684
+ - id: (optional) Task ID for single operations (e.g., "task-1")
8685
+ - title: (optional) Task title for create/update
8686
+ - description: (optional) Task description for create/update
8687
+ - status: (optional) Task status for update: pending, in_progress, completed, cancelled
8688
+ - priority: (optional) Task priority: low, medium, high, critical
8689
+ - dependencies: (optional) JSON array of task IDs that must be completed first
8690
+ - after: (optional) Task ID to insert the new task after (for ordering). By default, new tasks are appended to the end
8691
+
8692
+ Usage Examples:
8693
+
8694
+ Creating a single task:
8695
+ <task>
8696
+ <action>create</action>
8697
+ <title>Analyze authentication module</title>
8698
+ <description>Search and understand how authentication works</description>
8699
+ <priority>high</priority>
8700
+ </task>
8701
+
8702
+ Creating multiple tasks with dependencies:
8703
+ <task>
8704
+ <action>create</action>
8705
+ <tasks>[
8706
+ {"title": "Search for user model", "priority": "high"},
8707
+ {"title": "Analyze authentication flow", "dependencies": ["task-1"]},
8708
+ {"title": "Review session management", "dependencies": ["task-2"]}
8709
+ ]</tasks>
8710
+ </task>
8711
+
8712
+ Inserting a task after a specific task (instead of appending to end):
8713
+ <task>
8714
+ <action>create</action>
8715
+ <title>Investigate error handling</title>
8716
+ <after>task-2</after>
8717
+ </task>
8718
+
8719
+ Updating a task status:
8720
+ <task>
8721
+ <action>update</action>
8722
+ <id>task-1</id>
8723
+ <status>in_progress</status>
8724
+ </task>
8725
+
8726
+ Batch updating multiple tasks:
8727
+ <task>
8728
+ <action>update</action>
8729
+ <tasks>[
8730
+ {"id": "task-1", "status": "completed"},
8731
+ {"id": "task-2", "status": "in_progress"}
8732
+ ]</tasks>
8733
+ </task>
8734
+
8735
+ Completing a task:
8736
+ <task>
8737
+ <action>complete</action>
8738
+ <id>task-1</id>
8739
+ </task>
8740
+
8741
+ Cancelling a task:
8742
+ <task>
8743
+ <action>update</action>
8744
+ <id>task-1</id>
8745
+ <status>cancelled</status>
8746
+ </task>
8747
+
8748
+ Deleting a task:
8749
+ <task>
8750
+ <action>delete</action>
8751
+ <id>task-1</id>
8752
+ </task>
8753
+
8754
+ Listing all tasks:
8755
+ <task>
8756
+ <action>list</action>
8757
+ </task>
8758
+ `;
8759
+ taskSystemPrompt = `[Task Management System]
8760
+
8761
+ You have access to a task tracking tool to organize your work on complex requests.
8762
+
8763
+ ## When to Create Tasks
8764
+
8765
+ CREATE TASKS when the request has **multiple distinct deliverables or goals**:
8766
+ - "Fix bug A AND add feature B" \u2192 Two separate tasks
8767
+ - "Investigate auth, payments, AND notifications" \u2192 Three independent areas
8768
+ - "Implement X, then add tests, then update docs" \u2192 Sequential phases with different outputs
8769
+ - User explicitly asks for a plan or task breakdown
8770
+
8771
+ SKIP TASKS for single-goal requests, even if they require multiple searches:
8772
+ - "How does ranking work?" \u2192 Just investigate and answer (one goal)
8773
+ - "What does function X do?" \u2192 Just look it up (one goal)
8774
+ - "Explain the authentication flow" \u2192 Just trace and explain (one goal)
8775
+ - "Find where errors are logged" \u2192 Just search and report (one goal)
8776
+
8777
+ **Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
8778
+ A single investigation with many steps is still ONE task, not many.
8779
+
8780
+ MODIFY TASKS when (during execution):
8781
+ - You discover the problem is more complex than expected \u2192 Add new tasks
8782
+ - A single task covers too much scope \u2192 Split into smaller tasks
8783
+ - You find related work that needs attention \u2192 Add dependent tasks
8784
+ - A task becomes irrelevant based on findings \u2192 Cancel it
8785
+ - Task priorities change based on discoveries \u2192 Update priority
8786
+ - You learn new context \u2192 Update task description
8787
+
8788
+ ## Task Workflow
8789
+
8790
+ **STEP 1 - Plan (at start):**
8791
+ Analyze the request and create tasks for each logical step:
8792
+
8793
+ <task>
8794
+ <action>create</action>
8795
+ <tasks>[
8796
+ {"title": "Search for authentication module", "priority": "high"},
8797
+ {"title": "Analyze login flow implementation", "dependencies": ["task-1"]},
8798
+ {"title": "Find session management code", "dependencies": ["task-1"]},
8799
+ {"title": "Summarize authentication architecture", "dependencies": ["task-2", "task-3"]}
8800
+ ]</tasks>
8801
+ </task>
8802
+
8803
+ **STEP 2 - Execute (during work):**
8804
+ Update task status as you work:
8805
+
8806
+ <task>
8807
+ <action>update</action>
8808
+ <id>task-1</id>
8809
+ <status>in_progress</status>
8810
+ </task>
8811
+
8812
+ ... do the work (search, extract, etc.) ...
8813
+
8814
+ <task>
8815
+ <action>complete</action>
8816
+ <id>task-1</id>
8817
+ </task>
8818
+
8819
+ **STEP 2b - Adapt (when you discover new work):**
8820
+ As you work, you may discover that:
8821
+ - A task is more complex than expected \u2192 Split it into subtasks
8822
+ - New areas need investigation \u2192 Add new tasks
8823
+ - Some tasks are no longer needed \u2192 Cancel them
8824
+ - Task order should change \u2192 Update dependencies
8825
+
8826
+ *Adding a new task when you discover more work:*
8827
+ <task>
8828
+ <action>create</action>
8829
+ <title>Investigate caching layer</title>
8830
+ <description>Found references to Redis caching in auth module</description>
8831
+ </task>
8832
+
8833
+ *Inserting a task after a specific task (to maintain logical order):*
8834
+ <task>
8835
+ <action>create</action>
8836
+ <title>Check rate limiting</title>
8837
+ <after>task-2</after>
8838
+ </task>
8839
+
8840
+ *Cancelling and splitting a complex task:*
8841
+ <task>
8842
+ <action>update</action>
8843
+ <id>task-3</id>
8844
+ <status>cancelled</status>
8845
+ </task>
8846
+ <task>
8847
+ <action>create</action>
8848
+ <tasks>[
8849
+ {"title": "Review JWT token generation", "priority": "high"},
8850
+ {"title": "Review token refresh logic"}
8851
+ ]</tasks>
8852
+ </task>
8853
+
8854
+ **STEP 3 - Finish (before completion):**
8855
+ Before calling attempt_completion, ensure ALL tasks are either:
8856
+ - \`completed\` - you finished the work
8857
+ - \`cancelled\` - no longer needed
8858
+
8859
+ If you created tasks, you MUST resolve them all before completing.
8860
+
8861
+ ## Key Rules
8862
+
8863
+ 1. **Dependencies are enforced**: A task cannot start until its dependencies are completed
8864
+ 2. **Circular dependencies are rejected**: task-1 \u2192 task-2 \u2192 task-1 is invalid
8865
+ 3. **Completion is blocked**: attempt_completion will fail if tasks remain unresolved
8866
+ 4. **List to review**: Use <task><action>list</action></task> to see current task status
8867
+ 5. **Tasks are living documents**: Add, split, or cancel tasks as you learn more about the problem
8868
+ `;
8869
+ taskGuidancePrompt = `<task_guidance>
8870
+ Does this request have MULTIPLE DISTINCT GOALS?
8871
+ - "Do A AND B AND C" (multiple goals) \u2192 Create tasks for each goal
8872
+ - "Investigate/explain/find X" (single goal) \u2192 Skip tasks, just answer directly
8873
+
8874
+ Multiple internal steps (search, read, analyze) for ONE goal = NO tasks needed.
8875
+ Only create tasks when there are separate deliverables the user is asking for.
8876
+
8877
+ If creating tasks, use the task tool with action="create" first.
8878
+ </task_guidance>`;
8879
+ }
8880
+ });
8881
+
8205
8882
  // src/tools/common.js
8206
8883
  import { resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
8207
8884
  function buildToolTagPattern(tools2 = DEFAULT_VALID_TOOLS) {
@@ -8221,6 +8898,7 @@ function getValidParamsForTool(toolName) {
8221
8898
  listSkills: listSkillsSchema,
8222
8899
  useSkill: useSkillSchema,
8223
8900
  bash: bashSchema,
8901
+ task: taskSchema,
8224
8902
  attempt_completion: attemptCompletionSchema,
8225
8903
  edit: editSchema,
8226
8904
  create: createSchema
@@ -8327,6 +9005,62 @@ function createMessagePreview(message, charsPerSide = 200) {
8327
9005
  const end = message.substring(message.length - charsPerSide);
8328
9006
  return `${start}...${end}`;
8329
9007
  }
9008
+ function detectUnrecognizedToolCall(xmlString, validTools) {
9009
+ if (!xmlString || typeof xmlString !== "string") {
9010
+ return null;
9011
+ }
9012
+ const knownToolNames = [
9013
+ "search",
9014
+ "query",
9015
+ "extract",
9016
+ "listFiles",
9017
+ "searchFiles",
9018
+ "listSkills",
9019
+ "useSkill",
9020
+ "readImage",
9021
+ "implement",
9022
+ "edit",
9023
+ "create",
9024
+ "delegate",
9025
+ "bash",
9026
+ "task",
9027
+ "attempt_completion",
9028
+ "attempt_complete",
9029
+ "read_file",
9030
+ "write_file",
9031
+ "run_command",
9032
+ "grep",
9033
+ "find",
9034
+ "cat",
9035
+ "list_directory"
9036
+ ];
9037
+ for (const toolName of knownToolNames) {
9038
+ if (validTools.includes(toolName)) {
9039
+ continue;
9040
+ }
9041
+ const openTag = `<${toolName}>`;
9042
+ const closeTag = `</${toolName}>`;
9043
+ const openIndex = xmlString.indexOf(openTag);
9044
+ if (openIndex === -1) {
9045
+ continue;
9046
+ }
9047
+ let isNested = false;
9048
+ for (const validTool of validTools) {
9049
+ const validOpenTag = `<${validTool}>`;
9050
+ const validCloseTag = `</${validTool}>`;
9051
+ const validOpenIndex = xmlString.indexOf(validOpenTag);
9052
+ const validCloseIndex = xmlString.indexOf(validCloseTag);
9053
+ if (validOpenIndex !== -1 && validCloseIndex !== -1 && validOpenIndex < openIndex && openIndex < validCloseIndex) {
9054
+ isNested = true;
9055
+ break;
9056
+ }
9057
+ }
9058
+ if (!isNested) {
9059
+ return toolName;
9060
+ }
9061
+ }
9062
+ return null;
9063
+ }
8330
9064
  function parseTargets(targets) {
8331
9065
  if (!targets || typeof targets !== "string") {
8332
9066
  return [];
@@ -8369,6 +9103,7 @@ var init_common = __esm({
8369
9103
  "use strict";
8370
9104
  init_zod();
8371
9105
  init_edit();
9106
+ init_taskTool();
8372
9107
  searchSchema = external_exports.object({
8373
9108
  query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
8374
9109
  path: external_exports.string().optional().default(".").describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.')
@@ -8679,6 +9414,7 @@ User: Check system info
8679
9414
  "searchFiles",
8680
9415
  "implement",
8681
9416
  "bash",
9417
+ "task",
8682
9418
  "attempt_completion"
8683
9419
  ];
8684
9420
  }
@@ -8764,15 +9500,29 @@ function parseDelegatedTargets(rawResponse) {
8764
9500
  }
8765
9501
  function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, allowTests }) {
8766
9502
  return [
8767
- "You are a code-search subagent. Your ONLY job is to return ALL relevant code locations.",
8768
- "Use ONLY the search tool. Do NOT answer the question or explain anything.",
8769
- 'Return ONLY valid JSON with this shape: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}.',
8770
- "Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
8771
- `Search query: ${searchQuery}`,
9503
+ "You are a code-search subagent. Your job is to find ALL relevant code locations for the given query.",
9504
+ "",
9505
+ "The query may be complex - it could be a natural language question, a multi-part request, or a simple keyword.",
9506
+ "Break down complex queries into multiple searches to cover all aspects.",
9507
+ "",
9508
+ "Available tools:",
9509
+ "- search: Find code matching keywords or patterns. Run multiple searches for different aspects of complex queries.",
9510
+ "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
9511
+ "- listFiles: Understand directory structure to find where relevant code might live.",
9512
+ "",
9513
+ "Strategy for complex queries:",
9514
+ "1. Analyze the query - identify key concepts, entities, and relationships",
9515
+ '2. Run focused searches for each concept (e.g., "error handling" + "authentication" separately)',
9516
+ "3. Use extract to verify relevance of promising results",
9517
+ "4. Combine all relevant targets in your final response",
9518
+ "",
9519
+ `Query: ${searchQuery}`,
8772
9520
  `Search path(s): ${searchPath}`,
8773
9521
  `Options: exact=${exact ? "true" : "false"}, language=${language || "auto"}, allow_tests=${allowTests ? "true" : "false"}.`,
8774
- "Run additional searches only if needed to capture all relevant locations.",
8775
- "Deduplicate targets."
9522
+ "",
9523
+ 'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
9524
+ "Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
9525
+ "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets."
8776
9526
  ].join("\n");
8777
9527
  }
8778
9528
  var CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool;
@@ -8784,6 +9534,7 @@ var init_vercel = __esm({
8784
9534
  init_extract();
8785
9535
  init_delegate();
8786
9536
  init_common();
9537
+ init_error_types();
8787
9538
  CODE_SEARCH_SCHEMA = {
8788
9539
  type: "object",
8789
9540
  properties: {
@@ -8846,7 +9597,7 @@ var init_vercel = __esm({
8846
9597
  return await runRawSearch();
8847
9598
  } catch (error) {
8848
9599
  console.error("Error executing search command:", error);
8849
- return `Error executing search command: ${error.message}`;
9600
+ return formatErrorForAI(error);
8850
9601
  }
8851
9602
  }
8852
9603
  try {
@@ -8873,7 +9624,7 @@ var init_vercel = __esm({
8873
9624
  bashConfig: null,
8874
9625
  architectureFileName: options.architectureFileName || null,
8875
9626
  promptType: "code-searcher",
8876
- allowedTools: ["search", "attempt_completion"],
9627
+ allowedTools: ["search", "extract", "listFiles", "attempt_completion"],
8877
9628
  searchDelegate: false,
8878
9629
  schema: CODE_SEARCH_SCHEMA
8879
9630
  });
@@ -8905,7 +9656,7 @@ var init_vercel = __esm({
8905
9656
  return await runRawSearch();
8906
9657
  } catch (fallbackError) {
8907
9658
  console.error("Error executing search command:", fallbackError);
8908
- return `Error executing search command: ${fallbackError.message}`;
9659
+ return formatErrorForAI(fallbackError);
8909
9660
  }
8910
9661
  }
8911
9662
  }
@@ -8942,7 +9693,7 @@ var init_vercel = __esm({
8942
9693
  return results;
8943
9694
  } catch (error) {
8944
9695
  console.error("Error executing query command:", error);
8945
- return `Error executing query command: ${error.message}`;
9696
+ return formatErrorForAI(error);
8946
9697
  }
8947
9698
  }
8948
9699
  });
@@ -9018,7 +9769,7 @@ var init_vercel = __esm({
9018
9769
  return results;
9019
9770
  } catch (error) {
9020
9771
  console.error("Error executing extract command:", error);
9021
- return `Error executing extract command: ${error.message}`;
9772
+ return formatErrorForAI(error);
9022
9773
  }
9023
9774
  }
9024
9775
  });
@@ -10904,6 +11655,16 @@ var init_simpleTelemetry = __esm({
10904
11655
  ...data
10905
11656
  });
10906
11657
  }
11658
+ /**
11659
+ * Record task management events
11660
+ */
11661
+ recordTaskEvent(eventType, data = {}) {
11662
+ if (!this.isEnabled()) return;
11663
+ this.addEvent(`task.${eventType}`, {
11664
+ "session.id": this.sessionId,
11665
+ ...data
11666
+ });
11667
+ }
10907
11668
  setAttributes(attributes) {
10908
11669
  if (this.telemetry && this.telemetry.enableConsole) {
10909
11670
  console.log("[Attributes]", attributes);
@@ -17932,6 +18693,507 @@ var init_hooks = __esm({
17932
18693
  }
17933
18694
  });
17934
18695
 
18696
+ // src/agent/tasks/TaskManager.js
18697
+ var TaskManager;
18698
+ var init_TaskManager = __esm({
18699
+ "src/agent/tasks/TaskManager.js"() {
18700
+ "use strict";
18701
+ TaskManager = class _TaskManager {
18702
+ /**
18703
+ * Create a new TaskManager instance
18704
+ * @param {Object} [options] - Configuration options
18705
+ * @param {boolean} [options.debug=false] - Enable debug logging
18706
+ */
18707
+ constructor(options = {}) {
18708
+ this.tasks = /* @__PURE__ */ new Map();
18709
+ this.taskCounter = 0;
18710
+ this.debug = options.debug || false;
18711
+ }
18712
+ /**
18713
+ * Generate the next task ID
18714
+ * @returns {string} New task ID
18715
+ * @private
18716
+ */
18717
+ _generateId() {
18718
+ this.taskCounter++;
18719
+ return `task-${this.taskCounter}`;
18720
+ }
18721
+ /**
18722
+ * Get current timestamp in ISO format
18723
+ * @returns {string} ISO timestamp
18724
+ * @private
18725
+ */
18726
+ _now() {
18727
+ return (/* @__PURE__ */ new Date()).toISOString();
18728
+ }
18729
+ /**
18730
+ * Create a single task
18731
+ * @param {Object} taskData - Task data
18732
+ * @param {string} taskData.title - Task title
18733
+ * @param {string} [taskData.description] - Task description
18734
+ * @param {'low'|'medium'|'high'|'critical'} [taskData.priority] - Task priority
18735
+ * @param {string[]} [taskData.dependencies] - Task IDs this task depends on
18736
+ * @param {string} [taskData.after] - Task ID to insert this task after (for ordering)
18737
+ * @returns {Task} Created task
18738
+ * @throws {Error} If dependencies are invalid or create a cycle
18739
+ */
18740
+ createTask(taskData) {
18741
+ const id = this._generateId();
18742
+ const now = this._now();
18743
+ const dependencies = taskData.dependencies || [];
18744
+ for (const depId of dependencies) {
18745
+ if (!this.tasks.has(depId)) {
18746
+ throw new Error(`Dependency "${depId}" does not exist. Available tasks: ${this._getAvailableTaskIds()}`);
18747
+ }
18748
+ }
18749
+ const afterTaskId = taskData.after;
18750
+ if (afterTaskId && !this.tasks.has(afterTaskId)) {
18751
+ throw new Error(`Task "${afterTaskId}" does not exist. Cannot insert after non-existent task. Available tasks: ${this._getAvailableTaskIds()}`);
18752
+ }
18753
+ if (dependencies.length > 0 && !this._validateNoCycle(id, dependencies)) {
18754
+ throw new Error(`Adding dependencies [${dependencies.join(", ")}] to "${id}" would create a circular dependency`);
18755
+ }
18756
+ const task = {
18757
+ id,
18758
+ title: taskData.title,
18759
+ description: taskData.description || null,
18760
+ status: "pending",
18761
+ priority: taskData.priority || null,
18762
+ dependencies,
18763
+ createdAt: now,
18764
+ updatedAt: now,
18765
+ completedAt: null
18766
+ };
18767
+ if (afterTaskId) {
18768
+ this._insertAfter(afterTaskId, id, task);
18769
+ } else {
18770
+ this.tasks.set(id, task);
18771
+ }
18772
+ if (this.debug) {
18773
+ console.log(`[TaskManager] Created task: ${id} - ${task.title}${afterTaskId ? ` (after ${afterTaskId})` : ""}`);
18774
+ }
18775
+ return task;
18776
+ }
18777
+ /**
18778
+ * Insert a task after a specific task in the Map order
18779
+ * @param {string} afterId - Task ID to insert after
18780
+ * @param {string} newId - New task ID
18781
+ * @param {Task} newTask - New task object
18782
+ * @private
18783
+ */
18784
+ _insertAfter(afterId, newId, newTask) {
18785
+ const newTasks = /* @__PURE__ */ new Map();
18786
+ for (const [id, task] of this.tasks) {
18787
+ newTasks.set(id, task);
18788
+ if (id === afterId) {
18789
+ newTasks.set(newId, newTask);
18790
+ }
18791
+ }
18792
+ this.tasks = newTasks;
18793
+ }
18794
+ /**
18795
+ * Create multiple tasks in batch
18796
+ * @param {Object[]} tasksData - Array of task data objects
18797
+ * @returns {Task[]} Created tasks
18798
+ */
18799
+ createTasks(tasksData) {
18800
+ const createdTasks = [];
18801
+ for (const taskData of tasksData) {
18802
+ const task = this.createTask(taskData);
18803
+ createdTasks.push(task);
18804
+ }
18805
+ return createdTasks;
18806
+ }
18807
+ /**
18808
+ * Get a task by ID
18809
+ * @param {string} id - Task ID
18810
+ * @returns {Task|null} Task or null if not found
18811
+ */
18812
+ getTask(id) {
18813
+ return this.tasks.get(id) || null;
18814
+ }
18815
+ /**
18816
+ * Valid status values for tasks
18817
+ * @type {string[]}
18818
+ * @private
18819
+ */
18820
+ static VALID_STATUSES = ["pending", "in_progress", "completed", "cancelled"];
18821
+ /**
18822
+ * Valid priority values for tasks
18823
+ * @type {string[]}
18824
+ * @private
18825
+ */
18826
+ static VALID_PRIORITIES = ["low", "medium", "high", "critical"];
18827
+ /**
18828
+ * Update a task
18829
+ * @param {string} id - Task ID
18830
+ * @param {Object} updates - Fields to update
18831
+ * @returns {Task} Updated task
18832
+ * @throws {Error} If task not found or update is invalid
18833
+ */
18834
+ updateTask(id, updates) {
18835
+ const task = this.tasks.get(id);
18836
+ if (!task) {
18837
+ throw new Error(`Task "${id}" not found. Available tasks: ${this._getAvailableTaskIds()}`);
18838
+ }
18839
+ if (updates.status !== void 0) {
18840
+ if (!_TaskManager.VALID_STATUSES.includes(updates.status)) {
18841
+ throw new Error(`Invalid status "${updates.status}". Valid statuses: ${_TaskManager.VALID_STATUSES.join(", ")}`);
18842
+ }
18843
+ if (updates.status === "completed" && !task.completedAt) {
18844
+ updates.completedAt = this._now();
18845
+ }
18846
+ }
18847
+ if (updates.priority !== void 0 && updates.priority !== null) {
18848
+ if (!_TaskManager.VALID_PRIORITIES.includes(updates.priority)) {
18849
+ throw new Error(`Invalid priority "${updates.priority}". Valid priorities: ${_TaskManager.VALID_PRIORITIES.join(", ")}`);
18850
+ }
18851
+ }
18852
+ if (updates.dependencies !== void 0) {
18853
+ if (!Array.isArray(updates.dependencies)) {
18854
+ throw new Error("Dependencies must be an array");
18855
+ }
18856
+ for (const depId of updates.dependencies) {
18857
+ if (!this.tasks.has(depId)) {
18858
+ throw new Error(`Dependency "${depId}" does not exist. Available tasks: ${this._getAvailableTaskIds()}`);
18859
+ }
18860
+ }
18861
+ if (!this._validateNoCycle(id, updates.dependencies)) {
18862
+ throw new Error(`Adding dependencies [${updates.dependencies.join(", ")}] to "${id}" would create a circular dependency`);
18863
+ }
18864
+ }
18865
+ const updatedTask = {
18866
+ ...task,
18867
+ ...updates,
18868
+ id,
18869
+ // Ensure ID cannot be changed
18870
+ createdAt: task.createdAt,
18871
+ // Ensure createdAt cannot be changed
18872
+ updatedAt: this._now()
18873
+ };
18874
+ this.tasks.set(id, updatedTask);
18875
+ if (this.debug) {
18876
+ console.log(`[TaskManager] Updated task: ${id}`, updates);
18877
+ }
18878
+ return updatedTask;
18879
+ }
18880
+ /**
18881
+ * Update multiple tasks in batch
18882
+ * @param {Object[]} updates - Array of {id, ...updates} objects
18883
+ * @returns {Task[]} Updated tasks
18884
+ */
18885
+ updateTasks(updates) {
18886
+ const updatedTasks = [];
18887
+ for (const update of updates) {
18888
+ const { id, ...taskUpdates } = update;
18889
+ const task = this.updateTask(id, taskUpdates);
18890
+ updatedTasks.push(task);
18891
+ }
18892
+ return updatedTasks;
18893
+ }
18894
+ /**
18895
+ * Delete a task
18896
+ * @param {string} id - Task ID
18897
+ * @returns {boolean} True if deleted
18898
+ * @throws {Error} If task has dependents
18899
+ */
18900
+ deleteTask(id) {
18901
+ const task = this.tasks.get(id);
18902
+ if (!task) {
18903
+ throw new Error(`Task "${id}" not found. Available tasks: ${this._getAvailableTaskIds()}`);
18904
+ }
18905
+ const dependents = this._getDependents(id);
18906
+ if (dependents.length > 0) {
18907
+ throw new Error(`Cannot delete "${id}" - other tasks depend on it: ${dependents.join(", ")}`);
18908
+ }
18909
+ this.tasks.delete(id);
18910
+ if (this.debug) {
18911
+ console.log(`[TaskManager] Deleted task: ${id}`);
18912
+ }
18913
+ return true;
18914
+ }
18915
+ /**
18916
+ * Delete multiple tasks in batch
18917
+ * @param {string[]} ids - Task IDs to delete
18918
+ * @returns {string[]} Deleted task IDs
18919
+ */
18920
+ deleteTasks(ids) {
18921
+ const deletedIds = [];
18922
+ for (const id of ids) {
18923
+ this.deleteTask(id);
18924
+ deletedIds.push(id);
18925
+ }
18926
+ return deletedIds;
18927
+ }
18928
+ /**
18929
+ * Mark a task as completed
18930
+ * @param {string} id - Task ID
18931
+ * @returns {Task} Updated task
18932
+ */
18933
+ completeTask(id) {
18934
+ return this.updateTask(id, { status: "completed" });
18935
+ }
18936
+ /**
18937
+ * Mark multiple tasks as completed
18938
+ * @param {string[]} ids - Task IDs
18939
+ * @returns {Task[]} Updated tasks
18940
+ */
18941
+ completeTasks(ids) {
18942
+ return ids.map((id) => this.completeTask(id));
18943
+ }
18944
+ /**
18945
+ * List all tasks
18946
+ * @param {Object} [filter] - Optional filter
18947
+ * @param {'pending'|'in_progress'|'completed'|'cancelled'} [filter.status] - Filter by status
18948
+ * @returns {Task[]} Array of tasks
18949
+ */
18950
+ listTasks(filter3 = {}) {
18951
+ let tasks = Array.from(this.tasks.values());
18952
+ if (filter3.status) {
18953
+ tasks = tasks.filter((t) => t.status === filter3.status);
18954
+ }
18955
+ return tasks;
18956
+ }
18957
+ /**
18958
+ * Check if there are any incomplete tasks (pending or in_progress)
18959
+ * @returns {boolean} True if there are incomplete tasks
18960
+ */
18961
+ hasIncompleteTasks() {
18962
+ for (const task of this.tasks.values()) {
18963
+ if (task.status === "pending" || task.status === "in_progress") {
18964
+ return true;
18965
+ }
18966
+ }
18967
+ return false;
18968
+ }
18969
+ /**
18970
+ * Get incomplete tasks (pending or in_progress)
18971
+ * @returns {Task[]} Array of incomplete tasks
18972
+ */
18973
+ getIncompleteTasks() {
18974
+ return Array.from(this.tasks.values()).filter(
18975
+ (t) => t.status === "pending" || t.status === "in_progress"
18976
+ );
18977
+ }
18978
+ /**
18979
+ * Check if a dependency is resolved (completed or cancelled)
18980
+ * @param {string} depId - Dependency task ID
18981
+ * @returns {boolean} True if dependency is resolved or doesn't exist
18982
+ * @private
18983
+ */
18984
+ _isDependencyResolved(depId) {
18985
+ const dep = this.tasks.get(depId);
18986
+ if (!dep) return true;
18987
+ return dep.status === "completed" || dep.status === "cancelled";
18988
+ }
18989
+ /**
18990
+ * Get tasks that are ready to start (all dependencies completed)
18991
+ * @returns {Task[]} Array of ready tasks
18992
+ */
18993
+ getReadyTasks() {
18994
+ return Array.from(this.tasks.values()).filter((task) => {
18995
+ if (task.status !== "pending") return false;
18996
+ return task.dependencies.every((depId) => this._isDependencyResolved(depId));
18997
+ });
18998
+ }
18999
+ /**
19000
+ * Get blocked tasks (have incomplete dependencies)
19001
+ * @returns {Task[]} Array of blocked tasks
19002
+ */
19003
+ getBlockedTasks() {
19004
+ return Array.from(this.tasks.values()).filter((task) => {
19005
+ if (task.status !== "pending") return false;
19006
+ return task.dependencies.some((depId) => !this._isDependencyResolved(depId));
19007
+ });
19008
+ }
19009
+ /**
19010
+ * Get human-readable task summary for checkpoint messages
19011
+ * @returns {string} Formatted task summary
19012
+ */
19013
+ getTaskSummary() {
19014
+ const tasks = this.listTasks();
19015
+ if (tasks.length === 0) {
19016
+ return "No tasks created.";
19017
+ }
19018
+ const lines = ["Tasks:"];
19019
+ for (const task of tasks) {
19020
+ let line = `- [${task.status}] ${task.id}: ${task.title}`;
19021
+ if (task.status === "pending" && task.dependencies.length > 0) {
19022
+ const blockers = task.dependencies.filter((depId) => !this._isDependencyResolved(depId));
19023
+ if (blockers.length > 0) {
19024
+ line += ` (blocked by: ${blockers.join(", ")})`;
19025
+ }
19026
+ }
19027
+ lines.push(line);
19028
+ }
19029
+ return lines.join("\n");
19030
+ }
19031
+ /**
19032
+ * Escape XML special characters to prevent injection
19033
+ * @param {string} str - String to escape
19034
+ * @returns {string} Escaped string
19035
+ * @private
19036
+ */
19037
+ _escapeXml(str) {
19038
+ if (typeof str !== "string") return String(str);
19039
+ return str.replace(/[<>&'"]/g, (c) => ({
19040
+ "<": "&lt;",
19041
+ ">": "&gt;",
19042
+ "&": "&amp;",
19043
+ "'": "&apos;",
19044
+ '"': "&quot;"
19045
+ })[c]);
19046
+ }
19047
+ /**
19048
+ * Format tasks for inclusion in AI prompts
19049
+ * @returns {string} XML-formatted task list
19050
+ */
19051
+ formatTasksForPrompt() {
19052
+ const tasks = this.listTasks();
19053
+ if (tasks.length === 0) {
19054
+ return "<task_status>No tasks created.</task_status>";
19055
+ }
19056
+ const taskLines = tasks.map((task) => {
19057
+ const blockers = task.dependencies.filter((depId) => !this._isDependencyResolved(depId));
19058
+ let line = ` <task id="${this._escapeXml(task.id)}" status="${this._escapeXml(task.status)}"`;
19059
+ if (task.priority) line += ` priority="${this._escapeXml(task.priority)}"`;
19060
+ if (blockers.length > 0) line += ` blocked_by="${this._escapeXml(blockers.join(","))}"`;
19061
+ line += `>${this._escapeXml(task.title)}</task>`;
19062
+ return line;
19063
+ });
19064
+ return `<task_status>
19065
+ ${taskLines.join("\n")}
19066
+ </task_status>`;
19067
+ }
19068
+ /**
19069
+ * Clear all tasks
19070
+ */
19071
+ clear() {
19072
+ this.tasks.clear();
19073
+ this.taskCounter = 0;
19074
+ if (this.debug) {
19075
+ console.log("[TaskManager] Cleared all tasks");
19076
+ }
19077
+ }
19078
+ /**
19079
+ * Export tasks for persistence
19080
+ * @returns {Object} Serializable task data
19081
+ */
19082
+ export() {
19083
+ return {
19084
+ tasks: Array.from(this.tasks.entries()),
19085
+ taskCounter: this.taskCounter
19086
+ };
19087
+ }
19088
+ /**
19089
+ * Import tasks from exported data
19090
+ * @param {Object} data - Exported task data
19091
+ * @throws {Error} If data is invalid or malformed
19092
+ */
19093
+ import(data) {
19094
+ if (!data || typeof data !== "object") {
19095
+ throw new Error("Invalid import data: must be an object");
19096
+ }
19097
+ if (Object.prototype.hasOwnProperty.call(data, "__proto__") || Object.prototype.hasOwnProperty.call(data, "constructor")) {
19098
+ throw new Error("Invalid import data: prototype pollution attempt detected");
19099
+ }
19100
+ if (!Array.isArray(data.tasks)) {
19101
+ throw new Error("Invalid import data: tasks must be an array");
19102
+ }
19103
+ if (typeof data.taskCounter !== "number" || !Number.isInteger(data.taskCounter) || data.taskCounter < 0) {
19104
+ throw new Error("Invalid import data: taskCounter must be a non-negative integer");
19105
+ }
19106
+ for (const entry of data.tasks) {
19107
+ if (!Array.isArray(entry) || entry.length !== 2) {
19108
+ throw new Error("Invalid import data: each task entry must be a [id, task] tuple");
19109
+ }
19110
+ const [id, task] = entry;
19111
+ if (typeof id !== "string") {
19112
+ throw new Error("Invalid import data: task id must be a string");
19113
+ }
19114
+ if (!task || typeof task !== "object") {
19115
+ throw new Error("Invalid import data: task must be an object");
19116
+ }
19117
+ if (Object.prototype.hasOwnProperty.call(task, "__proto__") || Object.prototype.hasOwnProperty.call(task, "constructor")) {
19118
+ throw new Error("Invalid import data: prototype pollution attempt detected in task");
19119
+ }
19120
+ }
19121
+ this.tasks = new Map(data.tasks);
19122
+ this.taskCounter = data.taskCounter;
19123
+ }
19124
+ /**
19125
+ * Get list of available task IDs for error messages
19126
+ * @returns {string} Comma-separated list of task IDs
19127
+ * @private
19128
+ */
19129
+ _getAvailableTaskIds() {
19130
+ const ids = Array.from(this.tasks.keys());
19131
+ return ids.length > 0 ? ids.join(", ") : "(none)";
19132
+ }
19133
+ /**
19134
+ * Get tasks that depend on a given task
19135
+ * @param {string} taskId - Task ID
19136
+ * @returns {string[]} Array of dependent task IDs
19137
+ * @private
19138
+ */
19139
+ _getDependents(taskId) {
19140
+ const dependents = [];
19141
+ for (const [id, task] of this.tasks) {
19142
+ if (task.dependencies.includes(taskId)) {
19143
+ dependents.push(id);
19144
+ }
19145
+ }
19146
+ return dependents;
19147
+ }
19148
+ /**
19149
+ * Validate that adding dependencies won't create a cycle
19150
+ * Uses DFS to detect cycles
19151
+ * @param {string} taskId - Task being updated
19152
+ * @param {string[]} newDependencies - New dependencies to add
19153
+ * @returns {boolean} True if no cycle would be created
19154
+ * @private
19155
+ */
19156
+ _validateNoCycle(taskId, newDependencies) {
19157
+ const graph = /* @__PURE__ */ new Map();
19158
+ for (const [id, task] of this.tasks) {
19159
+ graph.set(id, [...task.dependencies]);
19160
+ }
19161
+ graph.set(taskId, newDependencies);
19162
+ const visited = /* @__PURE__ */ new Set();
19163
+ const recursionStack = /* @__PURE__ */ new Set();
19164
+ const hasCycle = (nodeId) => {
19165
+ if (recursionStack.has(nodeId)) {
19166
+ return true;
19167
+ }
19168
+ if (visited.has(nodeId)) {
19169
+ return false;
19170
+ }
19171
+ visited.add(nodeId);
19172
+ recursionStack.add(nodeId);
19173
+ const deps = graph.get(nodeId) || [];
19174
+ for (const depId of deps) {
19175
+ if (hasCycle(depId)) {
19176
+ return true;
19177
+ }
19178
+ }
19179
+ recursionStack.delete(nodeId);
19180
+ return false;
19181
+ };
19182
+ return !hasCycle(taskId);
19183
+ }
19184
+ };
19185
+ }
19186
+ });
19187
+
19188
+ // src/agent/tasks/index.js
19189
+ var init_tasks = __esm({
19190
+ "src/agent/tasks/index.js"() {
19191
+ "use strict";
19192
+ init_TaskManager();
19193
+ init_taskTool();
19194
+ }
19195
+ });
19196
+
17935
19197
  // src/index.js
17936
19198
  import dotenv from "dotenv";
17937
19199
  var init_index = __esm({
@@ -17956,6 +19218,7 @@ var init_index = __esm({
17956
19218
  init_probeTool();
17957
19219
  init_storage();
17958
19220
  init_hooks();
19221
+ init_tasks();
17959
19222
  dotenv.config();
17960
19223
  }
17961
19224
  });
@@ -18115,6 +19378,7 @@ var init_tools2 = __esm({
18115
19378
  "use strict";
18116
19379
  init_index();
18117
19380
  init_xmlParsingUtils();
19381
+ init_tasks();
18118
19382
  implementToolDefinition = `
18119
19383
  ## implement
18120
19384
  Description: Implement a given task. Can modify files. Can be used ONLY if task explicitly stated that something requires modification or implementation.
@@ -45125,7 +46389,7 @@ var init_styles = __esm({
45125
46389
  });
45126
46390
 
45127
46391
  // node_modules/@probelabs/maid/out/renderer/utils.js
45128
- function escapeXml(text) {
46392
+ function escapeXml2(text) {
45129
46393
  return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&apos;");
45130
46394
  }
45131
46395
  function measureText(text, fontSize = 12) {
@@ -45180,7 +46444,7 @@ function blockOverlay(x, y, width, height, title, branchYs = [], titleYOffset =
45180
46444
  const parts = [];
45181
46445
  parts.push(`<g class="cluster-overlay" transform="translate(${x},${y})">`);
45182
46446
  parts.push(`<rect class="cluster-border" x="0" y="0" width="${width}" height="${height}" rx="${radius}"/>`);
45183
- const titleText = title ? escapeXml(title) : "";
46447
+ const titleText = title ? escapeXml2(title) : "";
45184
46448
  if (titleText) {
45185
46449
  const titleW = Math.max(24, measureText(titleText, 12) + 10);
45186
46450
  const yBg = -2 + titleYOffset;
@@ -45199,7 +46463,7 @@ function blockOverlay(x, y, width, height, title, branchYs = [], titleYOffset =
45199
46463
  const yRel = br.y - y;
45200
46464
  parts.push(`<line x1="0" y1="${yRel}" x2="${width}" y2="${yRel}" class="cluster-border" />`);
45201
46465
  if (br.title) {
45202
- const text = escapeXml(br.title);
46466
+ const text = escapeXml2(br.title);
45203
46467
  const bw = Math.max(24, measureText(text, 12) + 10);
45204
46468
  const xBg = 6;
45205
46469
  parts.push(`<rect class="cluster-title-bg" x="${xBg}" y="${yRel - 10}" width="${bw}" height="18" rx="3"/>`);
@@ -46368,7 +47632,7 @@ function renderPie(model, opts = {}) {
46368
47632
  </style>`;
46369
47633
  if (model.title) {
46370
47634
  svg += `
46371
- <text class="pie-title" x="${cx}" y="${pad + 8}" text-anchor="middle">${escapeXml(model.title)}</text>`;
47635
+ <text class="pie-title" x="${cx}" y="${pad + 8}" text-anchor="middle">${escapeXml2(model.title)}</text>`;
46372
47636
  }
46373
47637
  svg += `
46374
47638
  <g class="pie" aria-label="pie">`;
@@ -46392,7 +47656,7 @@ function renderPie(model, opts = {}) {
46392
47656
  const mid = (start + end) / 2;
46393
47657
  const cos = Math.cos(mid);
46394
47658
  const sin = Math.sin(mid);
46395
- const percentLabel = escapeXml(formatPercent(s.value, total));
47659
+ const percentLabel = escapeXml2(formatPercent(s.value, total));
46396
47660
  if (angle < minOutsideAngle) {
46397
47661
  const r1 = radius * 0.9;
46398
47662
  const r2 = radius * 1.06;
@@ -46434,7 +47698,7 @@ function renderPie(model, opts = {}) {
46434
47698
  slices.forEach((s, i) => {
46435
47699
  const y = legendY + i * LEG_VSPACE;
46436
47700
  const fill = s.color || palette(i);
46437
- const text = escapeXml(`${s.label}${model.showData ? ` ${formatNumber(Number(s.value))}` : ""}`);
47701
+ const text = escapeXml2(`${s.label}${model.showData ? ` ${formatNumber(Number(s.value))}` : ""}`);
46438
47702
  svg += `
46439
47703
  <rect x="${legendX}" y="${y - LEG_SW + 6}" width="${LEG_SW}" height="${LEG_SW}" fill="${fill}" stroke="${fill}" stroke-width="1" />`;
46440
47704
  svg += `
@@ -47010,11 +48274,11 @@ function renderSequence(model, opts = {}) {
47010
48274
  const accTitle = model.accTitle || model.title || void 0;
47011
48275
  const accDesc = model.accDescr || void 0;
47012
48276
  if (accTitle)
47013
- svgParts.push(` <title>${escapeXml(accTitle)}</title>`);
48277
+ svgParts.push(` <title>${escapeXml2(accTitle)}</title>`);
47014
48278
  if (accDesc)
47015
- svgParts.push(` <desc>${escapeXml(accDesc)}</desc>`);
48279
+ svgParts.push(` <desc>${escapeXml2(accDesc)}</desc>`);
47016
48280
  if (model.title) {
47017
- const t = escapeXml(model.title);
48281
+ const t = escapeXml2(model.title);
47018
48282
  const tW = Math.max(20, measureText(model.title, 16));
47019
48283
  const xMid = width / 2;
47020
48284
  svgParts.push(` <text class="node-label" x="${xMid}" y="0" text-anchor="middle" font-size="16">${t}</text>`);
@@ -47055,7 +48319,7 @@ function renderSequence(model, opts = {}) {
47055
48319
  function drawParticipant(out, p) {
47056
48320
  out.push(` <g class="actor" transform="translate(${p.x},${p.y})">`);
47057
48321
  out.push(` <rect class="node-shape" width="${p.width}" height="${p.height}" rx="0"/>`);
47058
- out.push(` <text class="node-label" x="${p.width / 2}" y="${p.height / 2}" text-anchor="middle" dominant-baseline="middle">${escapeXml(p.display)}</text>`);
48322
+ out.push(` <text class="node-label" x="${p.width / 2}" y="${p.height / 2}" text-anchor="middle" dominant-baseline="middle">${escapeXml2(p.display)}</text>`);
47059
48323
  out.push(" </g>");
47060
48324
  }
47061
48325
  function drawParticipantBottom(out, p, layout) {
@@ -47063,7 +48327,7 @@ function drawParticipantBottom(out, p, layout) {
47063
48327
  const y = lifeline ? lifeline.y2 : layout.height - 28;
47064
48328
  out.push(` <g class="actor" transform="translate(${p.x},${y})">`);
47065
48329
  out.push(` <rect class="node-shape" width="${p.width}" height="${p.height}" rx="0"/>`);
47066
- out.push(` <text class="node-label" x="${p.width / 2}" y="${p.height / 2}" text-anchor="middle" dominant-baseline="middle">${escapeXml(p.display)}</text>`);
48330
+ out.push(` <text class="node-label" x="${p.width / 2}" y="${p.height / 2}" text-anchor="middle" dominant-baseline="middle">${escapeXml2(p.display)}</text>`);
47067
48331
  out.push(" </g>");
47068
48332
  }
47069
48333
  function drawMessage(out, m) {
@@ -47101,12 +48365,12 @@ function drawMessageLabel(out, m, label, _counter) {
47101
48365
  const x = xMid - w / 2;
47102
48366
  const y = m.y - 14 - h / 2;
47103
48367
  out.push(` <rect class="msg-label-bg" x="${x}" y="${y}" width="${w}" height="${h}" rx="3"/>`);
47104
- out.push(` <text class="msg-label" x="${xMid}" y="${y + h / 2}" text-anchor="middle">${escapeXml(label)}</text>`);
48368
+ out.push(` <text class="msg-label" x="${xMid}" y="${y + h / 2}" text-anchor="middle">${escapeXml2(label)}</text>`);
47105
48369
  }
47106
48370
  function drawNote(out, n) {
47107
48371
  out.push(` <g class="note" transform="translate(${n.x},${n.y})">`);
47108
48372
  out.push(` <rect width="${n.width}" height="${n.height}" rx="0"/>`);
47109
- out.push(` <text class="note-text" x="${n.width / 2}" y="${n.height / 2 + 4}" text-anchor="middle">${escapeXml(n.text)}</text>`);
48373
+ out.push(` <text class="note-text" x="${n.width / 2}" y="${n.height / 2 + 4}" text-anchor="middle">${escapeXml2(n.text)}</text>`);
47110
48374
  out.push(" </g>");
47111
48375
  }
47112
48376
  function applySequenceTheme(svg, theme) {
@@ -47872,13 +49136,13 @@ function renderClass(model, opts = {}) {
47872
49136
  let cy = y + padY + lineH;
47873
49137
  parts.push(` <g transform="translate(${x},${y})">`);
47874
49138
  parts.push(` <rect class="node-shape" width="${w}" height="${h}" rx="0"/>`);
47875
- parts.push(` <text class="node-label class-title" x="${w / 2}" y="${padY + 12}" text-anchor="middle" dominant-baseline="middle">${escapeXml(title)}</text>`);
49139
+ parts.push(` <text class="node-label class-title" x="${w / 2}" y="${padY + 12}" text-anchor="middle" dominant-baseline="middle">${escapeXml2(title)}</text>`);
47876
49140
  let yCursor = padY + 18;
47877
49141
  if (attrs.length) {
47878
49142
  parts.push(` <line class="class-divider" x1="0" y1="${yCursor}" x2="${w}" y2="${yCursor}"/>`);
47879
49143
  yCursor += 8;
47880
49144
  for (const a of attrs) {
47881
- parts.push(` <text class="node-label class-member" x="${padX}" y="${yCursor}" dominant-baseline="hanging">${escapeXml(a)}</text>`);
49145
+ parts.push(` <text class="node-label class-member" x="${padX}" y="${yCursor}" dominant-baseline="hanging">${escapeXml2(a)}</text>`);
47882
49146
  yCursor += lineH;
47883
49147
  }
47884
49148
  }
@@ -47886,7 +49150,7 @@ function renderClass(model, opts = {}) {
47886
49150
  parts.push(` <line class="class-divider" x1="0" y1="${yCursor}" x2="${w}" y2="${yCursor}"/>`);
47887
49151
  yCursor += 8;
47888
49152
  for (const m of methods) {
47889
- parts.push(` <text class="node-label class-member" x="${padX}" y="${yCursor}" dominant-baseline="hanging">${escapeXml(m)}</text>`);
49153
+ parts.push(` <text class="node-label class-member" x="${padX}" y="${yCursor}" dominant-baseline="hanging">${escapeXml2(m)}</text>`);
47890
49154
  yCursor += lineH;
47891
49155
  }
47892
49156
  }
@@ -47912,7 +49176,7 @@ function renderClass(model, opts = {}) {
47912
49176
  const x = pt.x + ux * away + nx * perp;
47913
49177
  const y = pt.y + uy * away + ny * perp;
47914
49178
  const anchor = side === "start" ? "end" : "start";
47915
- parts.push(`<text class="edge-label-text" x="${x}" y="${y}" text-anchor="${anchor}">${escapeXml(text)}</text>`);
49179
+ parts.push(`<text class="edge-label-text" x="${x}" y="${y}" text-anchor="${anchor}">${escapeXml2(text)}</text>`);
47916
49180
  };
47917
49181
  if (rel.leftCard && pts.length >= 2)
47918
49182
  placeEndpoint(pts[0], pts[1], rel.leftCard, "start");
@@ -47941,7 +49205,7 @@ function renderClass(model, opts = {}) {
47941
49205
  lines.push(cur);
47942
49206
  const dy = 14;
47943
49207
  let y0 = mid.y - (lines.length - 1) * dy / 2 - 10;
47944
- parts.push(`<text class="edge-label-text" x="${mid.x}" y="${y0}" text-anchor="middle">` + lines.map((ln, i2) => `<tspan x="${mid.x}" dy="${i2 === 0 ? 0 : dy}">${escapeXml(ln)}</tspan>`).join("") + `</text>`);
49208
+ parts.push(`<text class="edge-label-text" x="${mid.x}" y="${y0}" text-anchor="middle">` + lines.map((ln, i2) => `<tspan x="${mid.x}" dy="${i2 === 0 ? 0 : dy}">${escapeXml2(ln)}</tspan>`).join("") + `</text>`);
47945
49209
  }
47946
49210
  const pStart = [pts[0], pts[1] ?? pts[0]];
47947
49211
  const pEnd = [pts[pts.length - 2] ?? pts[pts.length - 1], pts[pts.length - 1]];
@@ -48007,7 +49271,7 @@ function renderClass(model, opts = {}) {
48007
49271
  }
48008
49272
  parts.push(` <g class="note" transform="translate(${nx},${ny})">`);
48009
49273
  parts.push(` <rect width="${noteW}" height="${noteH}" rx="0"/>`);
48010
- parts.push(` <text class="note-text" x="${noteW / 2}" y="${noteH / 2 + 4}" text-anchor="middle">${escapeXml(note.text)}</text>`);
49274
+ parts.push(` <text class="note-text" x="${noteW / 2}" y="${noteH / 2 + 4}" text-anchor="middle">${escapeXml2(note.text)}</text>`);
48011
49275
  parts.push(" </g>");
48012
49276
  const ax = anchor.x + anchor.width;
48013
49277
  const ay = anchor.y + 16;
@@ -64944,7 +66208,7 @@ var init_registry = __esm({
64944
66208
  });
64945
66209
 
64946
66210
  // src/agent/skills/formatting.js
64947
- function escapeXml2(value) {
66211
+ function escapeXml3(value) {
64948
66212
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
64949
66213
  }
64950
66214
  function formatAvailableSkillsXml(skills) {
@@ -64952,8 +66216,8 @@ function formatAvailableSkillsXml(skills) {
64952
66216
  const lines = ["<available_skills>"];
64953
66217
  for (const skill of skills) {
64954
66218
  lines.push(" <skill>");
64955
- lines.push(` <name>${escapeXml2(skill.name)}</name>`);
64956
- lines.push(` <description>${escapeXml2(skill.description || "")}</description>`);
66219
+ lines.push(` <name>${escapeXml3(skill.name)}</name>`);
66220
+ lines.push(` <description>${escapeXml3(skill.description || "")}</description>`);
64957
66221
  lines.push(" </skill>");
64958
66222
  }
64959
66223
  lines.push("</available_skills>");
@@ -67306,6 +68570,8 @@ var init_ProbeAgent = __esm({
67306
68570
  init_RetryManager();
67307
68571
  init_FallbackManager();
67308
68572
  init_contextCompactor();
68573
+ init_error_types();
68574
+ init_tasks();
67309
68575
  dotenv2.config();
67310
68576
  MAX_TOOL_ITERATIONS = (() => {
67311
68577
  const val = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
@@ -67346,6 +68612,7 @@ var init_ProbeAgent = __esm({
67346
68612
  * @param {string} [options.mcpConfigPath] - Path to MCP configuration file
67347
68613
  * @param {Object} [options.mcpConfig] - MCP configuration object (overrides mcpConfigPath)
67348
68614
  * @param {Array} [options.mcpServers] - Deprecated, use mcpConfig instead
68615
+ * @param {boolean} [options.enableTasks=false] - Enable task management system for tracking progress
67349
68616
  * @param {Object} [options.storageAdapter] - Custom storage adapter for history management
67350
68617
  * @param {Object} [options.hooks] - Hook callbacks for events (e.g., {'tool:start': callback})
67351
68618
  * @param {Array<string>|null} [options.allowedTools] - List of allowed tool names. Use ['*'] for all tools (default), [] or null for no tools (raw AI mode), or specific tool names like ['search', 'query', 'extract']. Supports exclusion with '!' prefix (e.g., ['*', '!bash'])
@@ -67441,6 +68708,8 @@ var init_ProbeAgent = __esm({
67441
68708
  this.mcpServers = options.mcpServers || null;
67442
68709
  this.mcpBridge = null;
67443
68710
  this._mcpInitialized = false;
68711
+ this.enableTasks = !!options.enableTasks;
68712
+ this.taskManager = null;
67444
68713
  this.retryConfig = options.retry || {};
67445
68714
  this.retryManager = null;
67446
68715
  this.fallbackConfig = options.fallback || null;
@@ -68816,6 +70085,10 @@ Workspace: ${this.allowedFolders.join(", ")}`;
68816
70085
  }
68817
70086
  if (this.enableBash && isToolAllowed("bash")) {
68818
70087
  toolDefinitions += `${bashToolDefinition}
70088
+ `;
70089
+ }
70090
+ if (this.enableTasks && isToolAllowed("task")) {
70091
+ toolDefinitions += `${taskToolDefinition}
68819
70092
  `;
68820
70093
  }
68821
70094
  if (isToolAllowed("attempt_completion")) {
@@ -68826,6 +70099,77 @@ Workspace: ${this.allowedFolders.join(", ")}`;
68826
70099
  toolDefinitions += `${delegateToolDefinition}
68827
70100
  `;
68828
70101
  }
70102
+ let toolExamples = "";
70103
+ if (isToolAllowed("search")) {
70104
+ toolExamples += `
70105
+ <search>
70106
+ <query>error handling</query>
70107
+ <path>src/search</path>
70108
+ </search>
70109
+ `;
70110
+ }
70111
+ if (isToolAllowed("extract")) {
70112
+ toolExamples += `
70113
+ <extract>
70114
+ <targets>src/config.js:15-25</targets>
70115
+ </extract>
70116
+ `;
70117
+ }
70118
+ if (isToolAllowed("attempt_completion")) {
70119
+ toolExamples += `
70120
+ <attempt_completion>
70121
+ The configuration is loaded from src/config.js lines 15-25 which contains the database settings.
70122
+ </attempt_completion>
70123
+ `;
70124
+ }
70125
+ let availableToolsList = "";
70126
+ if (isToolAllowed("search")) {
70127
+ availableToolsList += `- search: Search code using keyword queries${this.searchDelegate ? " (returns extracted code blocks via a dedicated subagent)" : ""}.
70128
+ `;
70129
+ }
70130
+ if (isToolAllowed("query")) {
70131
+ availableToolsList += "- query: Search code using structural AST patterns.\n";
70132
+ }
70133
+ if (isToolAllowed("extract")) {
70134
+ availableToolsList += "- extract: Extract specific code blocks or lines from files.\n";
70135
+ }
70136
+ if (isToolAllowed("listFiles")) {
70137
+ availableToolsList += "- listFiles: List files and directories in a specified location.\n";
70138
+ }
70139
+ if (isToolAllowed("searchFiles")) {
70140
+ availableToolsList += "- searchFiles: Find files matching a glob pattern with recursive search capability.\n";
70141
+ }
70142
+ if (this.enableSkills && isToolAllowed("listSkills")) {
70143
+ availableToolsList += "- listSkills: List available agent skills discovered in the repository.\n";
70144
+ }
70145
+ if (this.enableSkills && isToolAllowed("useSkill")) {
70146
+ availableToolsList += "- useSkill: Load and activate a specific skill's instructions.\n";
70147
+ }
70148
+ if (isToolAllowed("readImage")) {
70149
+ availableToolsList += "- readImage: Read and load an image file for AI analysis.\n";
70150
+ }
70151
+ if (this.allowEdit && isToolAllowed("implement")) {
70152
+ availableToolsList += "- implement: Implement a feature or fix a bug using aider.\n";
70153
+ }
70154
+ if (this.allowEdit && isToolAllowed("edit")) {
70155
+ availableToolsList += "- edit: Edit files using exact string replacement.\n";
70156
+ }
70157
+ if (this.allowEdit && isToolAllowed("create")) {
70158
+ availableToolsList += "- create: Create new files with specified content.\n";
70159
+ }
70160
+ if (this.enableDelegate && isToolAllowed("delegate")) {
70161
+ availableToolsList += "- delegate: Delegate big distinct tasks to specialized probe subagents.\n";
70162
+ }
70163
+ if (this.enableBash && isToolAllowed("bash")) {
70164
+ availableToolsList += "- bash: Execute bash commands for system operations.\n";
70165
+ }
70166
+ if (this.enableTasks && isToolAllowed("task")) {
70167
+ availableToolsList += "- task: Manage tasks for tracking progress (create, update, complete, delete, list).\n";
70168
+ }
70169
+ if (isToolAllowed("attempt_completion")) {
70170
+ availableToolsList += "- attempt_completion: Finalize the task and provide the result to the user.\n";
70171
+ availableToolsList += "- attempt_complete: Quick completion using previous response (shorthand).\n";
70172
+ }
68829
70173
  let xmlToolGuidelines = `
68830
70174
  # Tool Use Formatting
68831
70175
 
@@ -68840,20 +70184,7 @@ Structure (note the closing tags):
68840
70184
  ...
68841
70185
  </tool_name>
68842
70186
 
68843
- Examples:
68844
- <search>
68845
- <query>error handling</query>
68846
- <path>src/search</path>
68847
- </search>
68848
-
68849
- <extract>
68850
- <targets>src/config.js:15-25</targets>
68851
- </extract>
68852
-
68853
- <attempt_completion>
68854
- The configuration is loaded from src/config.js lines 15-25 which contains the database settings.
68855
- </attempt_completion>
68856
-
70187
+ Examples:${toolExamples}
68857
70188
  # Special Case: Quick Completion
68858
70189
  If your previous response was already correct and complete, you may respond with just:
68859
70190
  <attempt_complete>
@@ -68882,16 +70213,7 @@ I need to find code related to error handling in the search module. The most app
68882
70213
  10. If your previous response was already correct and complete, you may use \`<attempt_complete>\` as a shorthand.
68883
70214
 
68884
70215
  Available Tools:
68885
- - search: Search code using keyword queries${this.searchDelegate ? " (returns extracted code blocks via a dedicated subagent)" : ""}.
68886
- - query: Search code using structural AST patterns.
68887
- - extract: Extract specific code blocks or lines from files.
68888
- - listFiles: List files and directories in a specified location.
68889
- - searchFiles: Find files matching a glob pattern with recursive search capability.
68890
- ${this.enableSkills ? "- listSkills: List available agent skills discovered in the repository.\n- useSkill: Load and activate a specific skill's instructions.\n" : ""}- readImage: Read and load an image file for AI analysis.
68891
- ${this.allowEdit ? "- implement: Implement a feature or fix a bug using aider.\n- edit: Edit files using exact string replacement.\n- create: Create new files with specified content.\n" : ""}${this.enableDelegate ? "- delegate: Delegate big distinct tasks to specialized probe subagents.\n" : ""}${this.enableBash ? "- bash: Execute bash commands for system operations.\n" : ""}
68892
- - attempt_completion: Finalize the task and provide the result to the user.
68893
- - attempt_complete: Quick completion using previous response (shorthand).
68894
- `;
70216
+ ${availableToolsList}`;
68895
70217
  const commonInstructions = `<instructions>
68896
70218
  Follow these instructions carefully:
68897
70219
  1. Analyze the user's request.
@@ -68945,6 +70267,11 @@ To use a skill, call the useSkill tool with its name.
68945
70267
  `;
68946
70268
  }
68947
70269
  }
70270
+ if (this.enableTasks) {
70271
+ systemMessage += `
70272
+ ${taskSystemPrompt}
70273
+ `;
70274
+ }
68948
70275
  if (this.mcpBridge && this.mcpBridge.getToolNames().length > 0) {
68949
70276
  const allMcpTools = this.mcpBridge.getToolNames();
68950
70277
  const allowedMcpTools = this._filterMcpTools(allMcpTools);
@@ -69030,6 +70357,30 @@ You are working with a repository located at: ${searchDirectory}
69030
70357
  }
69031
70358
  try {
69032
70359
  const oldHistoryLength = this.history.length;
70360
+ if (this.enableTasks) {
70361
+ try {
70362
+ this.taskManager = new TaskManager({ debug: this.debug });
70363
+ const isToolAllowed = (toolName) => this.allowedTools.isEnabled(toolName);
70364
+ if (isToolAllowed("task")) {
70365
+ this.toolImplementations.task = createTaskTool({
70366
+ taskManager: this.taskManager,
70367
+ tracer: this.tracer,
70368
+ debug: this.debug
70369
+ });
70370
+ }
70371
+ if (this.tracer && typeof this.tracer.recordTaskEvent === "function") {
70372
+ this.tracer.recordTaskEvent("session_started", {
70373
+ "task.enabled": true
70374
+ });
70375
+ }
70376
+ if (this.debug) {
70377
+ console.log("[DEBUG] Task management initialized for this request");
70378
+ }
70379
+ } catch (taskInitError) {
70380
+ console.error("[ProbeAgent] Failed to initialize task management:", taskInitError.message);
70381
+ this.taskManager = null;
70382
+ }
70383
+ }
69033
70384
  await this.hooks.emit(HOOK_TYPES.MESSAGE_USER, {
69034
70385
  sessionId: this.sessionId,
69035
70386
  message,
@@ -69037,6 +70388,12 @@ You are working with a repository located at: ${searchDirectory}
69037
70388
  });
69038
70389
  const systemMessage = await this.getSystemMessage();
69039
70390
  let userMessage = { role: "user", content: message.trim() };
70391
+ if (this.enableTasks) {
70392
+ userMessage.content = userMessage.content + "\n\n" + taskGuidancePrompt;
70393
+ if (this.debug) {
70394
+ console.log("[DEBUG] Task guidance injected into user message");
70395
+ }
70396
+ }
69040
70397
  if (options.schema && !options._schemaFormatted) {
69041
70398
  const schemaInstructions = generateSchemaInstructions(options.schema, { debug: this.debug });
69042
70399
  userMessage.content = message.trim() + schemaInstructions;
@@ -69257,9 +70614,12 @@ You are working with a repository located at: ${searchDirectory}
69257
70614
  return result;
69258
70615
  };
69259
70616
  if (this.tracer) {
70617
+ const inputPreview = message.length > 1e3 ? message.substring(0, 1e3) + "... [truncated]" : message;
69260
70618
  await this.tracer.withSpan("ai.request", executeAIRequest, {
69261
70619
  "ai.model": this.model,
69262
70620
  "ai.provider": this.clientApiProvider || "auto",
70621
+ "ai.input": inputPreview,
70622
+ "ai.input_length": message.length,
69263
70623
  "iteration": currentIteration,
69264
70624
  "max_tokens": maxResponseTokens,
69265
70625
  "temperature": 0.3,
@@ -69339,6 +70699,9 @@ You are working with a repository located at: ${searchDirectory}
69339
70699
  if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
69340
70700
  validTools.push("delegate");
69341
70701
  }
70702
+ if (this.enableTasks && this.allowedTools.isEnabled("task")) {
70703
+ validTools.push("task");
70704
+ }
69342
70705
  }
69343
70706
  const nativeTools = validTools;
69344
70707
  const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
@@ -69347,6 +70710,32 @@ You are working with a repository located at: ${searchDirectory}
69347
70710
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
69348
70711
  if (toolName === "attempt_completion") {
69349
70712
  completionAttempted = true;
70713
+ if (this.enableTasks && this.taskManager && this.taskManager.hasIncompleteTasks()) {
70714
+ const taskSummary = this.taskManager.getTaskSummary();
70715
+ const blockedMessage = createTaskCompletionBlockedMessage(taskSummary);
70716
+ const incompleteTasks = this.taskManager.getIncompleteTasks();
70717
+ if (this.tracer && typeof this.tracer.recordTaskEvent === "function") {
70718
+ this.tracer.recordTaskEvent("completion_blocked", {
70719
+ "task.incomplete_count": incompleteTasks.length,
70720
+ "task.incomplete_ids": incompleteTasks.map((t) => t.id).join(", "),
70721
+ "task.iteration": currentIteration
70722
+ });
70723
+ }
70724
+ if (this.debug) {
70725
+ console.log("[DEBUG] Task checkpoint: Blocking completion due to incomplete tasks");
70726
+ console.log("[DEBUG] Incomplete tasks:", taskSummary);
70727
+ }
70728
+ currentMessages.push({
70729
+ role: "assistant",
70730
+ content: assistantResponseContent
70731
+ });
70732
+ currentMessages.push({
70733
+ role: "user",
70734
+ content: blockedMessage
70735
+ });
70736
+ completionAttempted = false;
70737
+ continue;
70738
+ }
69350
70739
  if (params.result === "__PREVIOUS_RESPONSE__") {
69351
70740
  const lastAssistantMessage = [...currentMessages].reverse().find(
69352
70741
  (msg) => msg.role === "assistant" && msg.content && !(this.mcpBridge ? parseHybridXmlToolCall(msg.content, validTools, this.mcpBridge) : parseXmlToolCallWithThinking(msg.content, validTools))
@@ -69408,7 +70797,6 @@ ${toolResultContent}
69408
70797
  </tool_result>` });
69409
70798
  } catch (error) {
69410
70799
  console.error(`Error executing MCP tool ${toolName}:`, error);
69411
- const toolResultContent = `Error executing MCP tool ${toolName}: ${error.message}`;
69412
70800
  if (this.debug) {
69413
70801
  console.error(`[DEBUG] ========================================`);
69414
70802
  console.error(`[DEBUG] MCP tool '${toolName}' failed with error:`);
@@ -69416,8 +70804,9 @@ ${toolResultContent}
69416
70804
  console.error(`[DEBUG] ========================================
69417
70805
  `);
69418
70806
  }
70807
+ const errorXml = formatErrorForAI(error);
69419
70808
  currentMessages.push({ role: "user", content: `<tool_result>
69420
- ${toolResultContent}
70809
+ ${errorXml}
69421
70810
  </tool_result>` });
69422
70811
  }
69423
70812
  } else if (this.toolImplementations[toolName]) {
@@ -69475,6 +70864,8 @@ ${toolResultContent}
69475
70864
  model: this.model,
69476
70865
  // Inherit model
69477
70866
  searchDelegate: this.searchDelegate,
70867
+ enableTasks: this.enableTasks,
70868
+ // Inherit task management (subagent gets isolated TaskManager)
69478
70869
  debug: this.debug,
69479
70870
  tracer: this.tracer
69480
70871
  };
@@ -69554,10 +70945,11 @@ ${toolResultContent}
69554
70945
  } catch (error) {
69555
70946
  console.error(`[ERROR] Tool execution failed for ${toolName}:`, error);
69556
70947
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
70948
+ const errorXml = formatErrorForAI(error);
69557
70949
  currentMessages.push({
69558
70950
  role: "user",
69559
70951
  content: `<tool_result>
69560
- Error: ${error.message}
70952
+ ${errorXml}
69561
70953
  </tool_result>`
69562
70954
  });
69563
70955
  }
@@ -69570,7 +70962,10 @@ Error: ${error.message}
69570
70962
  currentMessages.push({
69571
70963
  role: "user",
69572
70964
  content: `<tool_result>
69573
- Error: Unknown tool '${toolName}'. Available tools: ${allAvailableTools.join(", ")}
70965
+ <error type="parameter_error" recoverable="true">
70966
+ <message>Unknown tool '${toolName}'</message>
70967
+ <suggestion>Available tools: ${allAvailableTools.join(", ")}. Please use one of these tools.</suggestion>
70968
+ </error>
69574
70969
  </tool_result>`
69575
70970
  });
69576
70971
  }
@@ -69587,7 +70982,20 @@ Error: Unknown tool '${toolName}'. Available tools: ${allAvailableTools.join(",
69587
70982
  break;
69588
70983
  }
69589
70984
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
69590
- const reminderContent = `Please use one of the available tools to help answer the question, or use attempt_completion if you have enough information to provide a final answer.
70985
+ const unrecognizedTool = detectUnrecognizedToolCall(assistantResponseContent, validTools);
70986
+ let reminderContent;
70987
+ if (unrecognizedTool) {
70988
+ if (this.debug) {
70989
+ console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
70990
+ }
70991
+ const toolError = new ParameterError(`Tool '${unrecognizedTool}' is not available in this context.`, {
70992
+ suggestion: `Available tools: ${validTools.join(", ")}. Please use one of these tools instead.`
70993
+ });
70994
+ reminderContent = `<tool_result>
70995
+ ${formatErrorForAI(toolError)}
70996
+ </tool_result>`;
70997
+ } else {
70998
+ reminderContent = `Please use one of the available tools to help answer the question, or use attempt_completion if you have enough information to provide a final answer.
69591
70999
 
69592
71000
  Remember: Use proper XML format with BOTH opening and closing tags:
69593
71001
 
@@ -69595,16 +71003,23 @@ Remember: Use proper XML format with BOTH opening and closing tags:
69595
71003
  <parameter>value</parameter>
69596
71004
  </tool_name>
69597
71005
 
71006
+ Available tools: ${validTools.join(", ")}
71007
+
69598
71008
  Or for quick completion if your previous response was already correct and complete:
69599
71009
  <attempt_complete>
69600
71010
 
69601
71011
  IMPORTANT: When using <attempt_complete>, this must be the ONLY content in your response. No additional text, explanations, or other content should be included. This tag signals to reuse your previous response as the final answer.`;
71012
+ }
69602
71013
  currentMessages.push({
69603
71014
  role: "user",
69604
71015
  content: reminderContent
69605
71016
  });
69606
71017
  if (this.debug) {
69607
- console.log(`[DEBUG] No tool call detected in assistant response. Prompting for tool use.`);
71018
+ if (unrecognizedTool) {
71019
+ console.log(`[DEBUG] Unrecognized tool '${unrecognizedTool}' used. Providing error feedback.`);
71020
+ } else {
71021
+ console.log(`[DEBUG] No tool call detected in assistant response. Prompting for tool use.`);
71022
+ }
69608
71023
  }
69609
71024
  }
69610
71025
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
@@ -71109,6 +72524,9 @@ function parseArgs() {
71109
72524
  // Disable skill discovery and activation
71110
72525
  skillDirs: null,
71111
72526
  // Comma-separated list of repo-relative skill directories
72527
+ // Task management
72528
+ enableTasks: false,
72529
+ // Enable task tracking for progress management
71112
72530
  // Bash tool configuration
71113
72531
  enableBash: false,
71114
72532
  bashAllow: null,
@@ -71179,6 +72597,8 @@ function parseArgs() {
71179
72597
  config.disableSkills = true;
71180
72598
  } else if (arg === "--skills-dir" && i + 1 < args.length) {
71181
72599
  config.skillDirs = args[++i].split(",").map((dir) => dir.trim()).filter(Boolean);
72600
+ } else if (arg === "--allow-tasks") {
72601
+ config.enableTasks = true;
71182
72602
  } else if (arg === "--enable-bash") {
71183
72603
  config.enableBash = true;
71184
72604
  } else if (arg === "--bash-allow" && i + 1 < args.length) {
@@ -71233,6 +72653,7 @@ Options:
71233
72653
  Convenience flag equivalent to --allowed-tools none
71234
72654
  --skills-dir <dirs> Comma-separated list of repo-relative skill directories to scan
71235
72655
  --no-skills Disable skill discovery and activation
72656
+ --allow-tasks Enable task management for tracking multi-step progress
71236
72657
  --verbose Enable verbose output
71237
72658
  --outline Use outline-xml format for code search results
71238
72659
  --mcp Run as MCP server
@@ -71354,6 +72775,11 @@ var ProbeAgentMcpServer = class {
71354
72775
  architecture_file: {
71355
72776
  type: "string",
71356
72777
  description: "Optional architecture context filename in repo root (defaults to AGENTS.md with CLAUDE.md fallback; ARCHITECTURE.md is always included when present)."
72778
+ },
72779
+ enable_tasks: {
72780
+ type: "boolean",
72781
+ description: "Optional: Enable task management for tracking multi-step progress. When enabled, the agent can create, track, and complete tasks.",
72782
+ default: false
71357
72783
  }
71358
72784
  },
71359
72785
  required: ["query"]
@@ -71456,7 +72882,8 @@ var ProbeAgentMcpServer = class {
71456
72882
  maxResponseTokens: args.max_response_tokens,
71457
72883
  disableMermaidValidation: !!args.no_mermaid_validation,
71458
72884
  allowedTools: args.allowed_tools,
71459
- disableTools: args.disable_tools
72885
+ disableTools: args.disable_tools,
72886
+ enableTasks: !!args.enable_tasks
71460
72887
  };
71461
72888
  this.agent = new ProbeAgent(agentConfig2);
71462
72889
  await this.agent.initialize();
@@ -71700,7 +73127,8 @@ async function main() {
71700
73127
  enableSkills: !config.disableSkills,
71701
73128
  skillDirs: config.skillDirs,
71702
73129
  enableBash: config.enableBash,
71703
- bashConfig
73130
+ bashConfig,
73131
+ enableTasks: config.enableTasks
71704
73132
  };
71705
73133
  const agent = new ProbeAgent(agentConfig2);
71706
73134
  await agent.initialize();