newTaskTool.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import delay from "delay"
  2. import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
  3. import { Task } from "../task/Task"
  4. import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
  5. import { formatResponse } from "../prompts/responses"
  6. import { t } from "../../i18n"
  7. export async function newTaskTool(
  8. cline: Task,
  9. block: ToolUse,
  10. askApproval: AskApproval,
  11. handleError: HandleError,
  12. pushToolResult: PushToolResult,
  13. removeClosingTag: RemoveClosingTag,
  14. ) {
  15. const mode: string | undefined = block.params.mode
  16. const message: string | undefined = block.params.message
  17. try {
  18. if (block.partial) {
  19. const partialMessage = JSON.stringify({
  20. tool: "newTask",
  21. mode: removeClosingTag("mode", mode),
  22. message: removeClosingTag("message", message),
  23. })
  24. await cline.ask("tool", partialMessage, block.partial).catch(() => {})
  25. return
  26. } else {
  27. if (!mode) {
  28. cline.consecutiveMistakeCount++
  29. cline.recordToolError("new_task")
  30. pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "mode"))
  31. return
  32. }
  33. if (!message) {
  34. cline.consecutiveMistakeCount++
  35. cline.recordToolError("new_task")
  36. pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "message"))
  37. return
  38. }
  39. cline.consecutiveMistakeCount = 0
  40. // Un-escape one level of backslashes before '@' for hierarchical subtasks
  41. // Un-escape one level: \\@ -> \@ (removes one backslash for hierarchical subtasks)
  42. const unescapedMessage = message.replace(/\\\\@/g, "\\@")
  43. // Verify the mode exists
  44. const targetMode = getModeBySlug(mode, (await cline.providerRef.deref()?.getState())?.customModes)
  45. if (!targetMode) {
  46. pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`))
  47. return
  48. }
  49. const toolMessage = JSON.stringify({
  50. tool: "newTask",
  51. mode: targetMode.name,
  52. content: message,
  53. })
  54. const didApprove = await askApproval("tool", toolMessage)
  55. if (!didApprove) {
  56. return
  57. }
  58. const provider = cline.providerRef.deref()
  59. if (!provider) {
  60. return
  61. }
  62. if (cline.enableCheckpoints) {
  63. cline.checkpointSave(true)
  64. }
  65. // Preserve the current mode so we can resume with it later.
  66. cline.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug
  67. // Switch mode first, then create new task instance.
  68. await provider.handleModeSwitch(mode)
  69. // Delay to allow mode change to take effect before next tool is executed.
  70. await delay(500)
  71. const newCline = await provider.initClineWithTask(unescapedMessage, undefined, cline)
  72. if (!newCline) {
  73. pushToolResult(t("tools:newTask.errors.policy_restriction"))
  74. return
  75. }
  76. cline.emit("taskSpawned", newCline.taskId)
  77. pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${unescapedMessage}`)
  78. // Set the isPaused flag to true so the parent
  79. // task can wait for the sub-task to finish.
  80. cline.isPaused = true
  81. cline.emit("taskPaused")
  82. return
  83. }
  84. } catch (error) {
  85. await handleError("creating new task", error)
  86. return
  87. }
  88. }