@probelabs/probe 0.6.0-rc203 → 0.6.0-rc205

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/bin/binaries/probe-v0.6.0-rc205-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc205-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc205-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc205-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc205-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.d.ts +2 -0
  7. package/build/agent/ProbeAgent.js +233 -40
  8. package/build/agent/index.js +1566 -84
  9. package/build/agent/simpleTelemetry.js +12 -0
  10. package/build/agent/tasks/TaskManager.js +604 -0
  11. package/build/agent/tasks/index.js +15 -0
  12. package/build/agent/tasks/taskTool.js +476 -0
  13. package/build/agent/tools.js +11 -0
  14. package/build/delegate.js +7 -2
  15. package/build/index.js +14 -1
  16. package/build/search.js +19 -5
  17. package/build/tools/common.js +67 -0
  18. package/build/tools/vercel.js +28 -12
  19. package/build/utils/error-types.js +303 -0
  20. package/build/utils/path-validation.js +21 -3
  21. package/cjs/agent/ProbeAgent.cjs +8940 -6393
  22. package/cjs/agent/simpleTelemetry.cjs +10 -0
  23. package/cjs/index.cjs +8960 -6393
  24. package/package.json +2 -2
  25. package/src/agent/ProbeAgent.d.ts +2 -0
  26. package/src/agent/ProbeAgent.js +233 -40
  27. package/src/agent/index.js +14 -2
  28. package/src/agent/simpleTelemetry.js +12 -0
  29. package/src/agent/tasks/TaskManager.js +604 -0
  30. package/src/agent/tasks/index.js +15 -0
  31. package/src/agent/tasks/taskTool.js +476 -0
  32. package/src/agent/tools.js +11 -0
  33. package/src/delegate.js +7 -2
  34. package/src/index.js +14 -1
  35. package/src/search.js +19 -5
  36. package/src/tools/common.js +67 -0
  37. package/src/tools/vercel.js +28 -12
  38. package/src/utils/error-types.js +303 -0
  39. package/src/utils/path-validation.js +21 -3
  40. package/bin/binaries/probe-v0.6.0-rc203-aarch64-apple-darwin.tar.gz +0 -0
  41. package/bin/binaries/probe-v0.6.0-rc203-aarch64-unknown-linux-musl.tar.gz +0 -0
  42. package/bin/binaries/probe-v0.6.0-rc203-x86_64-apple-darwin.tar.gz +0 -0
  43. package/bin/binaries/probe-v0.6.0-rc203-x86_64-pc-windows-msvc.zip +0 -0
  44. package/bin/binaries/probe-v0.6.0-rc203-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -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.
@@ -30449,6 +31713,8 @@ var init_semantics = __esm({
30449
31713
  constructor(ctx, knownIds, knownEdgeIds) {
30450
31714
  super();
30451
31715
  this.edgeCount = 0;
31716
+ this.subgraphStack = [];
31717
+ this.reportedSubgraphIdCollision = /* @__PURE__ */ new Set();
30452
31718
  this.validateVisitor();
30453
31719
  this.ctx = ctx;
30454
31720
  this.knownIds = knownIds;
@@ -30641,8 +31907,13 @@ var init_semantics = __esm({
30641
31907
  }
30642
31908
  }
30643
31909
  subgraph(ctx) {
31910
+ const idTok = ctx.subgraphIdOrFirstWord && ctx.subgraphIdOrFirstWord[0];
31911
+ if (idTok)
31912
+ this.subgraphStack.push({ id: String(idTok.image) });
30644
31913
  if (ctx.subgraphStatement)
30645
31914
  ctx.subgraphStatement.forEach((s) => this.visit(s));
31915
+ if (idTok)
31916
+ this.subgraphStack.pop();
30646
31917
  }
30647
31918
  subgraphStatement(ctx) {
30648
31919
  for (const k of Object.keys(ctx)) {
@@ -30705,6 +31976,27 @@ var init_semantics = __esm({
30705
31976
  }
30706
31977
  if (ctx.nodeShape)
30707
31978
  ctx.nodeShape.forEach((n) => this.visit(n));
31979
+ const idTok = ctx.nodeId && ctx.nodeId[0];
31980
+ const idNumTok = ctx.nodeIdNum && ctx.nodeIdNum[0];
31981
+ const idToken = idTok || idNumTok;
31982
+ if (idToken && this.subgraphStack.length > 0) {
31983
+ const id = String(idToken.image);
31984
+ const hasCollision = this.subgraphStack.some((sg) => sg.id === id);
31985
+ if (hasCollision) {
31986
+ const key = `${id}:${idToken.startLine ?? 1}:${idToken.startColumn ?? 1}`;
31987
+ if (!this.reportedSubgraphIdCollision.has(key)) {
31988
+ this.reportedSubgraphIdCollision.add(key);
31989
+ this.ctx.errors.push({
31990
+ line: idToken.startLine ?? 1,
31991
+ column: idToken.startColumn ?? 1,
31992
+ severity: "error",
31993
+ code: "FL-SUBGRAPH-ID-COLLISION",
31994
+ message: `Node id '${id}' conflicts with an enclosing subgraph id and creates a cycle in Mermaid.`,
31995
+ hint: "Rename the subgraph id or the node id, or use a quoted subgraph title with no explicit id."
31996
+ });
31997
+ }
31998
+ }
31999
+ }
30708
32000
  if (hasAttr) {
30709
32001
  const attr = ctx.attrObject?.[0];
30710
32002
  const pairs = attr?.children?.attrPair || [];
@@ -34745,6 +36037,7 @@ function computeFixes(text, errors, level = "safe") {
34745
36037
  const patchedLines = /* @__PURE__ */ new Set();
34746
36038
  const seen = /* @__PURE__ */ new Set();
34747
36039
  const piQuoteClosedLines = /* @__PURE__ */ new Set();
36040
+ const subgraphCollisionRename = /* @__PURE__ */ new Map();
34748
36041
  function sanitizeAllQuotedSegmentsInShapes(lineText, lineNo) {
34749
36042
  const shapes = [
34750
36043
  { open: "[[", close: "]]" },
@@ -35464,6 +36757,28 @@ function computeFixes(text, errors, level = "safe") {
35464
36757
  }
35465
36758
  continue;
35466
36759
  }
36760
+ if (is("FL-SUBGRAPH-ID-COLLISION", e)) {
36761
+ if (level === "all") {
36762
+ const lineText = lineTextAt(text, e.line);
36763
+ if (lineText.trimStart().startsWith("subgraph"))
36764
+ continue;
36765
+ const caret0 = Math.max(0, e.column - 1);
36766
+ const slice = lineText.slice(caret0);
36767
+ const m = slice.match(/^([A-Za-z_][A-Za-z0-9_]*(?:-[A-Za-z0-9_]+)*)/);
36768
+ if (m) {
36769
+ const id = m[1];
36770
+ let newId = subgraphCollisionRename.get(id);
36771
+ if (!newId) {
36772
+ newId = id.endsWith("_node") ? `${id}2` : `${id}_node`;
36773
+ subgraphCollisionRename.set(id, newId);
36774
+ }
36775
+ if (newId !== id) {
36776
+ edits.push(replaceRange(text, { line: e.line, column: caret0 + 1 }, id.length, newId));
36777
+ }
36778
+ }
36779
+ }
36780
+ continue;
36781
+ }
35467
36782
  if (is("FL-LABEL-QUOTE-IN-UNQUOTED", e)) {
35468
36783
  if (level === "safe" || level === "all") {
35469
36784
  const lineText = lineTextAt(text, e.line);
@@ -45125,7 +46440,7 @@ var init_styles = __esm({
45125
46440
  });
45126
46441
 
45127
46442
  // node_modules/@probelabs/maid/out/renderer/utils.js
45128
- function escapeXml(text) {
46443
+ function escapeXml2(text) {
45129
46444
  return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&apos;");
45130
46445
  }
45131
46446
  function measureText(text, fontSize = 12) {
@@ -45180,7 +46495,7 @@ function blockOverlay(x, y, width, height, title, branchYs = [], titleYOffset =
45180
46495
  const parts = [];
45181
46496
  parts.push(`<g class="cluster-overlay" transform="translate(${x},${y})">`);
45182
46497
  parts.push(`<rect class="cluster-border" x="0" y="0" width="${width}" height="${height}" rx="${radius}"/>`);
45183
- const titleText = title ? escapeXml(title) : "";
46498
+ const titleText = title ? escapeXml2(title) : "";
45184
46499
  if (titleText) {
45185
46500
  const titleW = Math.max(24, measureText(titleText, 12) + 10);
45186
46501
  const yBg = -2 + titleYOffset;
@@ -45199,7 +46514,7 @@ function blockOverlay(x, y, width, height, title, branchYs = [], titleYOffset =
45199
46514
  const yRel = br.y - y;
45200
46515
  parts.push(`<line x1="0" y1="${yRel}" x2="${width}" y2="${yRel}" class="cluster-border" />`);
45201
46516
  if (br.title) {
45202
- const text = escapeXml(br.title);
46517
+ const text = escapeXml2(br.title);
45203
46518
  const bw = Math.max(24, measureText(text, 12) + 10);
45204
46519
  const xBg = 6;
45205
46520
  parts.push(`<rect class="cluster-title-bg" x="${xBg}" y="${yRel - 10}" width="${bw}" height="18" rx="3"/>`);
@@ -46368,7 +47683,7 @@ function renderPie(model, opts = {}) {
46368
47683
  </style>`;
46369
47684
  if (model.title) {
46370
47685
  svg += `
46371
- <text class="pie-title" x="${cx}" y="${pad + 8}" text-anchor="middle">${escapeXml(model.title)}</text>`;
47686
+ <text class="pie-title" x="${cx}" y="${pad + 8}" text-anchor="middle">${escapeXml2(model.title)}</text>`;
46372
47687
  }
46373
47688
  svg += `
46374
47689
  <g class="pie" aria-label="pie">`;
@@ -46392,7 +47707,7 @@ function renderPie(model, opts = {}) {
46392
47707
  const mid = (start + end) / 2;
46393
47708
  const cos = Math.cos(mid);
46394
47709
  const sin = Math.sin(mid);
46395
- const percentLabel = escapeXml(formatPercent(s.value, total));
47710
+ const percentLabel = escapeXml2(formatPercent(s.value, total));
46396
47711
  if (angle < minOutsideAngle) {
46397
47712
  const r1 = radius * 0.9;
46398
47713
  const r2 = radius * 1.06;
@@ -46434,7 +47749,7 @@ function renderPie(model, opts = {}) {
46434
47749
  slices.forEach((s, i) => {
46435
47750
  const y = legendY + i * LEG_VSPACE;
46436
47751
  const fill = s.color || palette(i);
46437
- const text = escapeXml(`${s.label}${model.showData ? ` ${formatNumber(Number(s.value))}` : ""}`);
47752
+ const text = escapeXml2(`${s.label}${model.showData ? ` ${formatNumber(Number(s.value))}` : ""}`);
46438
47753
  svg += `
46439
47754
  <rect x="${legendX}" y="${y - LEG_SW + 6}" width="${LEG_SW}" height="${LEG_SW}" fill="${fill}" stroke="${fill}" stroke-width="1" />`;
46440
47755
  svg += `
@@ -47010,11 +48325,11 @@ function renderSequence(model, opts = {}) {
47010
48325
  const accTitle = model.accTitle || model.title || void 0;
47011
48326
  const accDesc = model.accDescr || void 0;
47012
48327
  if (accTitle)
47013
- svgParts.push(` <title>${escapeXml(accTitle)}</title>`);
48328
+ svgParts.push(` <title>${escapeXml2(accTitle)}</title>`);
47014
48329
  if (accDesc)
47015
- svgParts.push(` <desc>${escapeXml(accDesc)}</desc>`);
48330
+ svgParts.push(` <desc>${escapeXml2(accDesc)}</desc>`);
47016
48331
  if (model.title) {
47017
- const t = escapeXml(model.title);
48332
+ const t = escapeXml2(model.title);
47018
48333
  const tW = Math.max(20, measureText(model.title, 16));
47019
48334
  const xMid = width / 2;
47020
48335
  svgParts.push(` <text class="node-label" x="${xMid}" y="0" text-anchor="middle" font-size="16">${t}</text>`);
@@ -47055,7 +48370,7 @@ function renderSequence(model, opts = {}) {
47055
48370
  function drawParticipant(out, p) {
47056
48371
  out.push(` <g class="actor" transform="translate(${p.x},${p.y})">`);
47057
48372
  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>`);
48373
+ 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
48374
  out.push(" </g>");
47060
48375
  }
47061
48376
  function drawParticipantBottom(out, p, layout) {
@@ -47063,7 +48378,7 @@ function drawParticipantBottom(out, p, layout) {
47063
48378
  const y = lifeline ? lifeline.y2 : layout.height - 28;
47064
48379
  out.push(` <g class="actor" transform="translate(${p.x},${y})">`);
47065
48380
  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>`);
48381
+ 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
48382
  out.push(" </g>");
47068
48383
  }
47069
48384
  function drawMessage(out, m) {
@@ -47101,12 +48416,12 @@ function drawMessageLabel(out, m, label, _counter) {
47101
48416
  const x = xMid - w / 2;
47102
48417
  const y = m.y - 14 - h / 2;
47103
48418
  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>`);
48419
+ out.push(` <text class="msg-label" x="${xMid}" y="${y + h / 2}" text-anchor="middle">${escapeXml2(label)}</text>`);
47105
48420
  }
47106
48421
  function drawNote(out, n) {
47107
48422
  out.push(` <g class="note" transform="translate(${n.x},${n.y})">`);
47108
48423
  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>`);
48424
+ out.push(` <text class="note-text" x="${n.width / 2}" y="${n.height / 2 + 4}" text-anchor="middle">${escapeXml2(n.text)}</text>`);
47110
48425
  out.push(" </g>");
47111
48426
  }
47112
48427
  function applySequenceTheme(svg, theme) {
@@ -47872,13 +49187,13 @@ function renderClass(model, opts = {}) {
47872
49187
  let cy = y + padY + lineH;
47873
49188
  parts.push(` <g transform="translate(${x},${y})">`);
47874
49189
  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>`);
49190
+ parts.push(` <text class="node-label class-title" x="${w / 2}" y="${padY + 12}" text-anchor="middle" dominant-baseline="middle">${escapeXml2(title)}</text>`);
47876
49191
  let yCursor = padY + 18;
47877
49192
  if (attrs.length) {
47878
49193
  parts.push(` <line class="class-divider" x1="0" y1="${yCursor}" x2="${w}" y2="${yCursor}"/>`);
47879
49194
  yCursor += 8;
47880
49195
  for (const a of attrs) {
47881
- parts.push(` <text class="node-label class-member" x="${padX}" y="${yCursor}" dominant-baseline="hanging">${escapeXml(a)}</text>`);
49196
+ parts.push(` <text class="node-label class-member" x="${padX}" y="${yCursor}" dominant-baseline="hanging">${escapeXml2(a)}</text>`);
47882
49197
  yCursor += lineH;
47883
49198
  }
47884
49199
  }
@@ -47886,7 +49201,7 @@ function renderClass(model, opts = {}) {
47886
49201
  parts.push(` <line class="class-divider" x1="0" y1="${yCursor}" x2="${w}" y2="${yCursor}"/>`);
47887
49202
  yCursor += 8;
47888
49203
  for (const m of methods) {
47889
- parts.push(` <text class="node-label class-member" x="${padX}" y="${yCursor}" dominant-baseline="hanging">${escapeXml(m)}</text>`);
49204
+ parts.push(` <text class="node-label class-member" x="${padX}" y="${yCursor}" dominant-baseline="hanging">${escapeXml2(m)}</text>`);
47890
49205
  yCursor += lineH;
47891
49206
  }
47892
49207
  }
@@ -47912,7 +49227,7 @@ function renderClass(model, opts = {}) {
47912
49227
  const x = pt.x + ux * away + nx * perp;
47913
49228
  const y = pt.y + uy * away + ny * perp;
47914
49229
  const anchor = side === "start" ? "end" : "start";
47915
- parts.push(`<text class="edge-label-text" x="${x}" y="${y}" text-anchor="${anchor}">${escapeXml(text)}</text>`);
49230
+ parts.push(`<text class="edge-label-text" x="${x}" y="${y}" text-anchor="${anchor}">${escapeXml2(text)}</text>`);
47916
49231
  };
47917
49232
  if (rel.leftCard && pts.length >= 2)
47918
49233
  placeEndpoint(pts[0], pts[1], rel.leftCard, "start");
@@ -47941,7 +49256,7 @@ function renderClass(model, opts = {}) {
47941
49256
  lines.push(cur);
47942
49257
  const dy = 14;
47943
49258
  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>`);
49259
+ 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
49260
  }
47946
49261
  const pStart = [pts[0], pts[1] ?? pts[0]];
47947
49262
  const pEnd = [pts[pts.length - 2] ?? pts[pts.length - 1], pts[pts.length - 1]];
@@ -48007,7 +49322,7 @@ function renderClass(model, opts = {}) {
48007
49322
  }
48008
49323
  parts.push(` <g class="note" transform="translate(${nx},${ny})">`);
48009
49324
  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>`);
49325
+ parts.push(` <text class="note-text" x="${noteW / 2}" y="${noteH / 2 + 4}" text-anchor="middle">${escapeXml2(note.text)}</text>`);
48011
49326
  parts.push(" </g>");
48012
49327
  const ax = anchor.x + anchor.width;
48013
49328
  const ay = anchor.y + 16;
@@ -64944,7 +66259,7 @@ var init_registry = __esm({
64944
66259
  });
64945
66260
 
64946
66261
  // src/agent/skills/formatting.js
64947
- function escapeXml2(value) {
66262
+ function escapeXml3(value) {
64948
66263
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
64949
66264
  }
64950
66265
  function formatAvailableSkillsXml(skills) {
@@ -64952,8 +66267,8 @@ function formatAvailableSkillsXml(skills) {
64952
66267
  const lines = ["<available_skills>"];
64953
66268
  for (const skill of skills) {
64954
66269
  lines.push(" <skill>");
64955
- lines.push(` <name>${escapeXml2(skill.name)}</name>`);
64956
- lines.push(` <description>${escapeXml2(skill.description || "")}</description>`);
66270
+ lines.push(` <name>${escapeXml3(skill.name)}</name>`);
66271
+ lines.push(` <description>${escapeXml3(skill.description || "")}</description>`);
64957
66272
  lines.push(" </skill>");
64958
66273
  }
64959
66274
  lines.push("</available_skills>");
@@ -67306,6 +68621,8 @@ var init_ProbeAgent = __esm({
67306
68621
  init_RetryManager();
67307
68622
  init_FallbackManager();
67308
68623
  init_contextCompactor();
68624
+ init_error_types();
68625
+ init_tasks();
67309
68626
  dotenv2.config();
67310
68627
  MAX_TOOL_ITERATIONS = (() => {
67311
68628
  const val = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
@@ -67346,6 +68663,7 @@ var init_ProbeAgent = __esm({
67346
68663
  * @param {string} [options.mcpConfigPath] - Path to MCP configuration file
67347
68664
  * @param {Object} [options.mcpConfig] - MCP configuration object (overrides mcpConfigPath)
67348
68665
  * @param {Array} [options.mcpServers] - Deprecated, use mcpConfig instead
68666
+ * @param {boolean} [options.enableTasks=false] - Enable task management system for tracking progress
67349
68667
  * @param {Object} [options.storageAdapter] - Custom storage adapter for history management
67350
68668
  * @param {Object} [options.hooks] - Hook callbacks for events (e.g., {'tool:start': callback})
67351
68669
  * @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 +68759,8 @@ var init_ProbeAgent = __esm({
67441
68759
  this.mcpServers = options.mcpServers || null;
67442
68760
  this.mcpBridge = null;
67443
68761
  this._mcpInitialized = false;
68762
+ this.enableTasks = !!options.enableTasks;
68763
+ this.taskManager = null;
67444
68764
  this.retryConfig = options.retry || {};
67445
68765
  this.retryManager = null;
67446
68766
  this.fallbackConfig = options.fallback || null;
@@ -68816,6 +70136,10 @@ Workspace: ${this.allowedFolders.join(", ")}`;
68816
70136
  }
68817
70137
  if (this.enableBash && isToolAllowed("bash")) {
68818
70138
  toolDefinitions += `${bashToolDefinition}
70139
+ `;
70140
+ }
70141
+ if (this.enableTasks && isToolAllowed("task")) {
70142
+ toolDefinitions += `${taskToolDefinition}
68819
70143
  `;
68820
70144
  }
68821
70145
  if (isToolAllowed("attempt_completion")) {
@@ -68826,6 +70150,77 @@ Workspace: ${this.allowedFolders.join(", ")}`;
68826
70150
  toolDefinitions += `${delegateToolDefinition}
68827
70151
  `;
68828
70152
  }
70153
+ let toolExamples = "";
70154
+ if (isToolAllowed("search")) {
70155
+ toolExamples += `
70156
+ <search>
70157
+ <query>error handling</query>
70158
+ <path>src/search</path>
70159
+ </search>
70160
+ `;
70161
+ }
70162
+ if (isToolAllowed("extract")) {
70163
+ toolExamples += `
70164
+ <extract>
70165
+ <targets>src/config.js:15-25</targets>
70166
+ </extract>
70167
+ `;
70168
+ }
70169
+ if (isToolAllowed("attempt_completion")) {
70170
+ toolExamples += `
70171
+ <attempt_completion>
70172
+ The configuration is loaded from src/config.js lines 15-25 which contains the database settings.
70173
+ </attempt_completion>
70174
+ `;
70175
+ }
70176
+ let availableToolsList = "";
70177
+ if (isToolAllowed("search")) {
70178
+ availableToolsList += `- search: Search code using keyword queries${this.searchDelegate ? " (returns extracted code blocks via a dedicated subagent)" : ""}.
70179
+ `;
70180
+ }
70181
+ if (isToolAllowed("query")) {
70182
+ availableToolsList += "- query: Search code using structural AST patterns.\n";
70183
+ }
70184
+ if (isToolAllowed("extract")) {
70185
+ availableToolsList += "- extract: Extract specific code blocks or lines from files.\n";
70186
+ }
70187
+ if (isToolAllowed("listFiles")) {
70188
+ availableToolsList += "- listFiles: List files and directories in a specified location.\n";
70189
+ }
70190
+ if (isToolAllowed("searchFiles")) {
70191
+ availableToolsList += "- searchFiles: Find files matching a glob pattern with recursive search capability.\n";
70192
+ }
70193
+ if (this.enableSkills && isToolAllowed("listSkills")) {
70194
+ availableToolsList += "- listSkills: List available agent skills discovered in the repository.\n";
70195
+ }
70196
+ if (this.enableSkills && isToolAllowed("useSkill")) {
70197
+ availableToolsList += "- useSkill: Load and activate a specific skill's instructions.\n";
70198
+ }
70199
+ if (isToolAllowed("readImage")) {
70200
+ availableToolsList += "- readImage: Read and load an image file for AI analysis.\n";
70201
+ }
70202
+ if (this.allowEdit && isToolAllowed("implement")) {
70203
+ availableToolsList += "- implement: Implement a feature or fix a bug using aider.\n";
70204
+ }
70205
+ if (this.allowEdit && isToolAllowed("edit")) {
70206
+ availableToolsList += "- edit: Edit files using exact string replacement.\n";
70207
+ }
70208
+ if (this.allowEdit && isToolAllowed("create")) {
70209
+ availableToolsList += "- create: Create new files with specified content.\n";
70210
+ }
70211
+ if (this.enableDelegate && isToolAllowed("delegate")) {
70212
+ availableToolsList += "- delegate: Delegate big distinct tasks to specialized probe subagents.\n";
70213
+ }
70214
+ if (this.enableBash && isToolAllowed("bash")) {
70215
+ availableToolsList += "- bash: Execute bash commands for system operations.\n";
70216
+ }
70217
+ if (this.enableTasks && isToolAllowed("task")) {
70218
+ availableToolsList += "- task: Manage tasks for tracking progress (create, update, complete, delete, list).\n";
70219
+ }
70220
+ if (isToolAllowed("attempt_completion")) {
70221
+ availableToolsList += "- attempt_completion: Finalize the task and provide the result to the user.\n";
70222
+ availableToolsList += "- attempt_complete: Quick completion using previous response (shorthand).\n";
70223
+ }
68829
70224
  let xmlToolGuidelines = `
68830
70225
  # Tool Use Formatting
68831
70226
 
@@ -68840,20 +70235,7 @@ Structure (note the closing tags):
68840
70235
  ...
68841
70236
  </tool_name>
68842
70237
 
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
-
70238
+ Examples:${toolExamples}
68857
70239
  # Special Case: Quick Completion
68858
70240
  If your previous response was already correct and complete, you may respond with just:
68859
70241
  <attempt_complete>
@@ -68882,16 +70264,7 @@ I need to find code related to error handling in the search module. The most app
68882
70264
  10. If your previous response was already correct and complete, you may use \`<attempt_complete>\` as a shorthand.
68883
70265
 
68884
70266
  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
- `;
70267
+ ${availableToolsList}`;
68895
70268
  const commonInstructions = `<instructions>
68896
70269
  Follow these instructions carefully:
68897
70270
  1. Analyze the user's request.
@@ -68945,6 +70318,11 @@ To use a skill, call the useSkill tool with its name.
68945
70318
  `;
68946
70319
  }
68947
70320
  }
70321
+ if (this.enableTasks) {
70322
+ systemMessage += `
70323
+ ${taskSystemPrompt}
70324
+ `;
70325
+ }
68948
70326
  if (this.mcpBridge && this.mcpBridge.getToolNames().length > 0) {
68949
70327
  const allMcpTools = this.mcpBridge.getToolNames();
68950
70328
  const allowedMcpTools = this._filterMcpTools(allMcpTools);
@@ -69030,6 +70408,30 @@ You are working with a repository located at: ${searchDirectory}
69030
70408
  }
69031
70409
  try {
69032
70410
  const oldHistoryLength = this.history.length;
70411
+ if (this.enableTasks) {
70412
+ try {
70413
+ this.taskManager = new TaskManager({ debug: this.debug });
70414
+ const isToolAllowed = (toolName) => this.allowedTools.isEnabled(toolName);
70415
+ if (isToolAllowed("task")) {
70416
+ this.toolImplementations.task = createTaskTool({
70417
+ taskManager: this.taskManager,
70418
+ tracer: this.tracer,
70419
+ debug: this.debug
70420
+ });
70421
+ }
70422
+ if (this.tracer && typeof this.tracer.recordTaskEvent === "function") {
70423
+ this.tracer.recordTaskEvent("session_started", {
70424
+ "task.enabled": true
70425
+ });
70426
+ }
70427
+ if (this.debug) {
70428
+ console.log("[DEBUG] Task management initialized for this request");
70429
+ }
70430
+ } catch (taskInitError) {
70431
+ console.error("[ProbeAgent] Failed to initialize task management:", taskInitError.message);
70432
+ this.taskManager = null;
70433
+ }
70434
+ }
69033
70435
  await this.hooks.emit(HOOK_TYPES.MESSAGE_USER, {
69034
70436
  sessionId: this.sessionId,
69035
70437
  message,
@@ -69037,6 +70439,12 @@ You are working with a repository located at: ${searchDirectory}
69037
70439
  });
69038
70440
  const systemMessage = await this.getSystemMessage();
69039
70441
  let userMessage = { role: "user", content: message.trim() };
70442
+ if (this.enableTasks) {
70443
+ userMessage.content = userMessage.content + "\n\n" + taskGuidancePrompt;
70444
+ if (this.debug) {
70445
+ console.log("[DEBUG] Task guidance injected into user message");
70446
+ }
70447
+ }
69040
70448
  if (options.schema && !options._schemaFormatted) {
69041
70449
  const schemaInstructions = generateSchemaInstructions(options.schema, { debug: this.debug });
69042
70450
  userMessage.content = message.trim() + schemaInstructions;
@@ -69257,9 +70665,12 @@ You are working with a repository located at: ${searchDirectory}
69257
70665
  return result;
69258
70666
  };
69259
70667
  if (this.tracer) {
70668
+ const inputPreview = message.length > 1e3 ? message.substring(0, 1e3) + "... [truncated]" : message;
69260
70669
  await this.tracer.withSpan("ai.request", executeAIRequest, {
69261
70670
  "ai.model": this.model,
69262
70671
  "ai.provider": this.clientApiProvider || "auto",
70672
+ "ai.input": inputPreview,
70673
+ "ai.input_length": message.length,
69263
70674
  "iteration": currentIteration,
69264
70675
  "max_tokens": maxResponseTokens,
69265
70676
  "temperature": 0.3,
@@ -69339,6 +70750,9 @@ You are working with a repository located at: ${searchDirectory}
69339
70750
  if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
69340
70751
  validTools.push("delegate");
69341
70752
  }
70753
+ if (this.enableTasks && this.allowedTools.isEnabled("task")) {
70754
+ validTools.push("task");
70755
+ }
69342
70756
  }
69343
70757
  const nativeTools = validTools;
69344
70758
  const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
@@ -69347,6 +70761,32 @@ You are working with a repository located at: ${searchDirectory}
69347
70761
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
69348
70762
  if (toolName === "attempt_completion") {
69349
70763
  completionAttempted = true;
70764
+ if (this.enableTasks && this.taskManager && this.taskManager.hasIncompleteTasks()) {
70765
+ const taskSummary = this.taskManager.getTaskSummary();
70766
+ const blockedMessage = createTaskCompletionBlockedMessage(taskSummary);
70767
+ const incompleteTasks = this.taskManager.getIncompleteTasks();
70768
+ if (this.tracer && typeof this.tracer.recordTaskEvent === "function") {
70769
+ this.tracer.recordTaskEvent("completion_blocked", {
70770
+ "task.incomplete_count": incompleteTasks.length,
70771
+ "task.incomplete_ids": incompleteTasks.map((t) => t.id).join(", "),
70772
+ "task.iteration": currentIteration
70773
+ });
70774
+ }
70775
+ if (this.debug) {
70776
+ console.log("[DEBUG] Task checkpoint: Blocking completion due to incomplete tasks");
70777
+ console.log("[DEBUG] Incomplete tasks:", taskSummary);
70778
+ }
70779
+ currentMessages.push({
70780
+ role: "assistant",
70781
+ content: assistantResponseContent
70782
+ });
70783
+ currentMessages.push({
70784
+ role: "user",
70785
+ content: blockedMessage
70786
+ });
70787
+ completionAttempted = false;
70788
+ continue;
70789
+ }
69350
70790
  if (params.result === "__PREVIOUS_RESPONSE__") {
69351
70791
  const lastAssistantMessage = [...currentMessages].reverse().find(
69352
70792
  (msg) => msg.role === "assistant" && msg.content && !(this.mcpBridge ? parseHybridXmlToolCall(msg.content, validTools, this.mcpBridge) : parseXmlToolCallWithThinking(msg.content, validTools))
@@ -69408,7 +70848,6 @@ ${toolResultContent}
69408
70848
  </tool_result>` });
69409
70849
  } catch (error) {
69410
70850
  console.error(`Error executing MCP tool ${toolName}:`, error);
69411
- const toolResultContent = `Error executing MCP tool ${toolName}: ${error.message}`;
69412
70851
  if (this.debug) {
69413
70852
  console.error(`[DEBUG] ========================================`);
69414
70853
  console.error(`[DEBUG] MCP tool '${toolName}' failed with error:`);
@@ -69416,8 +70855,9 @@ ${toolResultContent}
69416
70855
  console.error(`[DEBUG] ========================================
69417
70856
  `);
69418
70857
  }
70858
+ const errorXml = formatErrorForAI(error);
69419
70859
  currentMessages.push({ role: "user", content: `<tool_result>
69420
- ${toolResultContent}
70860
+ ${errorXml}
69421
70861
  </tool_result>` });
69422
70862
  }
69423
70863
  } else if (this.toolImplementations[toolName]) {
@@ -69475,6 +70915,8 @@ ${toolResultContent}
69475
70915
  model: this.model,
69476
70916
  // Inherit model
69477
70917
  searchDelegate: this.searchDelegate,
70918
+ enableTasks: this.enableTasks,
70919
+ // Inherit task management (subagent gets isolated TaskManager)
69478
70920
  debug: this.debug,
69479
70921
  tracer: this.tracer
69480
70922
  };
@@ -69554,10 +70996,11 @@ ${toolResultContent}
69554
70996
  } catch (error) {
69555
70997
  console.error(`[ERROR] Tool execution failed for ${toolName}:`, error);
69556
70998
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
70999
+ const errorXml = formatErrorForAI(error);
69557
71000
  currentMessages.push({
69558
71001
  role: "user",
69559
71002
  content: `<tool_result>
69560
- Error: ${error.message}
71003
+ ${errorXml}
69561
71004
  </tool_result>`
69562
71005
  });
69563
71006
  }
@@ -69570,7 +71013,10 @@ Error: ${error.message}
69570
71013
  currentMessages.push({
69571
71014
  role: "user",
69572
71015
  content: `<tool_result>
69573
- Error: Unknown tool '${toolName}'. Available tools: ${allAvailableTools.join(", ")}
71016
+ <error type="parameter_error" recoverable="true">
71017
+ <message>Unknown tool '${toolName}'</message>
71018
+ <suggestion>Available tools: ${allAvailableTools.join(", ")}. Please use one of these tools.</suggestion>
71019
+ </error>
69574
71020
  </tool_result>`
69575
71021
  });
69576
71022
  }
@@ -69587,7 +71033,20 @@ Error: Unknown tool '${toolName}'. Available tools: ${allAvailableTools.join(",
69587
71033
  break;
69588
71034
  }
69589
71035
  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.
71036
+ const unrecognizedTool = detectUnrecognizedToolCall(assistantResponseContent, validTools);
71037
+ let reminderContent;
71038
+ if (unrecognizedTool) {
71039
+ if (this.debug) {
71040
+ console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
71041
+ }
71042
+ const toolError = new ParameterError(`Tool '${unrecognizedTool}' is not available in this context.`, {
71043
+ suggestion: `Available tools: ${validTools.join(", ")}. Please use one of these tools instead.`
71044
+ });
71045
+ reminderContent = `<tool_result>
71046
+ ${formatErrorForAI(toolError)}
71047
+ </tool_result>`;
71048
+ } else {
71049
+ 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
71050
 
69592
71051
  Remember: Use proper XML format with BOTH opening and closing tags:
69593
71052
 
@@ -69595,16 +71054,26 @@ Remember: Use proper XML format with BOTH opening and closing tags:
69595
71054
  <parameter>value</parameter>
69596
71055
  </tool_name>
69597
71056
 
69598
- Or for quick completion if your previous response was already correct and complete:
69599
- <attempt_complete>
71057
+ Available tools: ${validTools.join(", ")}
69600
71058
 
69601
- 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.`;
71059
+ To complete with a direct answer:
71060
+ <attempt_completion>Your final answer here</attempt_completion>
71061
+
71062
+ Or if your previous response already contains a complete, direct answer (not a thinking block or JSON):
71063
+ <attempt_complete></attempt_complete>
71064
+
71065
+ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant message as the final answer. Only use this if that message was already a valid, complete response to the user's question.`;
71066
+ }
69602
71067
  currentMessages.push({
69603
71068
  role: "user",
69604
71069
  content: reminderContent
69605
71070
  });
69606
71071
  if (this.debug) {
69607
- console.log(`[DEBUG] No tool call detected in assistant response. Prompting for tool use.`);
71072
+ if (unrecognizedTool) {
71073
+ console.log(`[DEBUG] Unrecognized tool '${unrecognizedTool}' used. Providing error feedback.`);
71074
+ } else {
71075
+ console.log(`[DEBUG] No tool call detected in assistant response. Prompting for tool use.`);
71076
+ }
69608
71077
  }
69609
71078
  }
69610
71079
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
@@ -70296,7 +71765,7 @@ Convert your previous response content into actual JSON data that follows this s
70296
71765
  if (content.includes("Your response does not match the expected JSON schema") || content.includes("Please provide a valid JSON response") || content.includes("Schema validation error:")) {
70297
71766
  return true;
70298
71767
  }
70299
- if (content.includes("When using <attempt_complete>") && content.includes("this must be the ONLY content in your response")) {
71768
+ if (content.includes("<attempt_complete></attempt_complete>") && content.includes("reuses your PREVIOUS assistant message")) {
70300
71769
  return true;
70301
71770
  }
70302
71771
  return false;
@@ -71109,6 +72578,9 @@ function parseArgs() {
71109
72578
  // Disable skill discovery and activation
71110
72579
  skillDirs: null,
71111
72580
  // Comma-separated list of repo-relative skill directories
72581
+ // Task management
72582
+ enableTasks: false,
72583
+ // Enable task tracking for progress management
71112
72584
  // Bash tool configuration
71113
72585
  enableBash: false,
71114
72586
  bashAllow: null,
@@ -71179,6 +72651,8 @@ function parseArgs() {
71179
72651
  config.disableSkills = true;
71180
72652
  } else if (arg === "--skills-dir" && i + 1 < args.length) {
71181
72653
  config.skillDirs = args[++i].split(",").map((dir) => dir.trim()).filter(Boolean);
72654
+ } else if (arg === "--allow-tasks") {
72655
+ config.enableTasks = true;
71182
72656
  } else if (arg === "--enable-bash") {
71183
72657
  config.enableBash = true;
71184
72658
  } else if (arg === "--bash-allow" && i + 1 < args.length) {
@@ -71233,6 +72707,7 @@ Options:
71233
72707
  Convenience flag equivalent to --allowed-tools none
71234
72708
  --skills-dir <dirs> Comma-separated list of repo-relative skill directories to scan
71235
72709
  --no-skills Disable skill discovery and activation
72710
+ --allow-tasks Enable task management for tracking multi-step progress
71236
72711
  --verbose Enable verbose output
71237
72712
  --outline Use outline-xml format for code search results
71238
72713
  --mcp Run as MCP server
@@ -71354,6 +72829,11 @@ var ProbeAgentMcpServer = class {
71354
72829
  architecture_file: {
71355
72830
  type: "string",
71356
72831
  description: "Optional architecture context filename in repo root (defaults to AGENTS.md with CLAUDE.md fallback; ARCHITECTURE.md is always included when present)."
72832
+ },
72833
+ enable_tasks: {
72834
+ type: "boolean",
72835
+ description: "Optional: Enable task management for tracking multi-step progress. When enabled, the agent can create, track, and complete tasks.",
72836
+ default: false
71357
72837
  }
71358
72838
  },
71359
72839
  required: ["query"]
@@ -71456,7 +72936,8 @@ var ProbeAgentMcpServer = class {
71456
72936
  maxResponseTokens: args.max_response_tokens,
71457
72937
  disableMermaidValidation: !!args.no_mermaid_validation,
71458
72938
  allowedTools: args.allowed_tools,
71459
- disableTools: args.disable_tools
72939
+ disableTools: args.disable_tools,
72940
+ enableTasks: !!args.enable_tasks
71460
72941
  };
71461
72942
  this.agent = new ProbeAgent(agentConfig2);
71462
72943
  await this.agent.initialize();
@@ -71700,7 +73181,8 @@ async function main() {
71700
73181
  enableSkills: !config.disableSkills,
71701
73182
  skillDirs: config.skillDirs,
71702
73183
  enableBash: config.enableBash,
71703
- bashConfig
73184
+ bashConfig,
73185
+ enableTasks: config.enableTasks
71704
73186
  };
71705
73187
  const agent = new ProbeAgent(agentConfig2);
71706
73188
  await agent.initialize();