2023-07-25 15:36:27 +02:00
import { chat _metadata , callPopup , saveSettingsDebounced } from "../../../script.js" ;
2023-07-20 19:32:15 +02:00
import { getContext , extension _settings , saveMetadataDebounced } from "../../extensions.js" ;
import {
substituteParams ,
eventSource ,
event _types ,
generateQuietPrompt ,
} from "../../../script.js" ;
import { registerSlashCommand } from "../../slash-commands.js" ;
const MODULE _NAME = "Objective"
let globalObjective = ""
let globalTasks = [ ]
let currentChatId = ""
let currentTask = null
let checkCounter = 0
2023-07-25 15:36:27 +02:00
const defaultPrompts = {
2023-07-20 19:32:15 +02:00
"createTask" : ` Pause your roleplay and generate a list of tasks to complete an objective. Your next response must be formatted as a numbered list of plain text entries. Do not include anything but the numbered list. The list must be prioritized in the order that tasks must be completed.
2023-07-25 15:36:27 +02:00
The objective that you must make a numbered task list for is : [ { { objective } } ] .
The tasks created should take into account the character traits of { { char } } . These tasks may or may not involve { { user } } directly . Be sure to include the objective as the final task .
Given an example objective of 'Make me a four course dinner' , here is an example output :
1. Determine what the courses will be
2. Find recipes for each course
3. Go shopping for supplies with { { user } }
4. Cook the food
5. Get { { user } } to set the table
6. Serve the food
7. Enjoy eating the meal with { { user } }
2023-07-20 19:32:15 +02:00
` ,
"checkTaskCompleted" : ` Pause your roleplay. Determine if this task is completed: [{{task}}].
2023-07-25 15:36:27 +02:00
To do this , examine the most recent messages . Your response must only contain either true or false , nothing other words .
Example output :
true
` ,
'currentTask' : ` Your current task is [{{task}}]. Balance existing roleplay with completing this task. `
2023-07-20 19:32:15 +02:00
}
2023-07-25 15:36:27 +02:00
let objectivePrompts = defaultPrompts
2023-07-20 19:32:15 +02:00
//###############################//
//# Task Management #//
//###############################//
// Accepts optional index. Defaults to adding to end of list.
function addTask ( description , index = null ) {
index = index != null ? index : index = globalTasks . length
globalTasks . splice ( index , 0 , new ObjectiveTask (
{ description : description }
) )
saveState ( )
}
// Return the task and index or throw an error
function getTaskById ( taskId ) {
if ( taskId == null ) {
throw ` Null task id `
}
const index = globalTasks . findIndex ( ( task ) => task . id === taskId ) ;
if ( index !== - 1 ) {
return { task : globalTasks [ index ] , index : index } ;
} else {
throw ` Cannot find task with ${ taskId } `
}
}
function deleteTask ( taskId ) {
const { task , index } = getTaskById ( taskId )
globalTasks . splice ( index , 1 )
setCurrentTask ( )
updateUiTaskList ( )
}
// Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much.
async function generateTasks ( ) {
2023-07-25 15:36:27 +02:00
const prompt = substituteParams ( objectivePrompts . createTask . replace ( /{{objective}}/gi , globalObjective ) ) ;
2023-07-20 19:32:15 +02:00
console . log ( ` Generating tasks for objective with prompt ` )
toastr . info ( 'Generating tasks for objective' , 'Please wait...' ) ;
const taskResponse = await generateQuietPrompt ( prompt )
// Clear all existing global tasks when generating
globalTasks = [ ]
const numberedListPattern = /^\d+\./
// Create tasks from generated task list
for ( const task of taskResponse . split ( '\n' ) . map ( x => x . trim ( ) ) ) {
if ( task . match ( numberedListPattern ) != null ) {
addTask ( task . replace ( numberedListPattern , "" ) . trim ( ) )
}
}
updateUiTaskList ( )
console . info ( ` Response for Objective: ' ${ globalObjective } ' was \n ' ${ taskResponse } ', \n which created tasks \n ${ JSON . stringify ( globalTasks . map ( v => { return v . toSaveState ( ) } ), null, 2)} ` )
toastr . success ( ` Generated ${ globalTasks . length } tasks ` , 'Done!' ) ;
}
// Call Quiet Generate to check if a task is completed
async function checkTaskCompleted ( ) {
// Make sure there are tasks
if ( jQuery . isEmptyObject ( currentTask ) ) {
return
}
checkCounter = $ ( '#objective-check-frequency' ) . val ( )
2023-07-25 15:36:27 +02:00
const prompt = substituteParams ( objectivePrompts . checkTaskCompleted . replace ( /{{task}}/gi , currentTask . description ) ) ;
2023-07-20 19:32:15 +02:00
const taskResponse = ( await generateQuietPrompt ( prompt ) ) . toLowerCase ( )
// Check response if task complete
if ( taskResponse . includes ( "true" ) ) {
console . info ( ` Character determined task ' ${ JSON . stringify ( currentTask . toSaveState ( ) ) } is completed. ` )
currentTask . completeTask ( )
} else if ( ! ( taskResponse . includes ( "false" ) ) ) {
console . warn ( ` checkTaskCompleted response did not contain true or false. taskResponse: ${ taskResponse } ` )
} else {
console . debug ( ` Checked task completion. taskResponse: ${ taskResponse } ` )
}
}
// Set a task in extensionPrompt context. Defaults to first incomplete
function setCurrentTask ( taskId = null ) {
const context = getContext ( ) ;
currentTask = { } ;
// Set current task to either the next incomplete task, or the index
if ( taskId === null ) {
currentTask = globalTasks . find ( task => ! task . completed ) || { } ;
} else {
const { _ , index } = getTaskById ( taskId )
currentTask = globalTasks [ index ] ;
}
// Get the task description and add to extension prompt
const description = currentTask . description || null ;
// Now update the extension prompt
if ( description ) {
2023-07-25 15:36:27 +02:00
const extensionPromptText = objectivePrompts . currentTask . replace ( /{{task}}/gi , description ) ;
$ ( '.objective-task' ) . css ( { 'border-color' : '' , 'border-width' : '' } ) // Clear highlights
currentTask . descriptionSpan . css ( { 'border-color' : 'yellow' , 'border-width' : '2px' } ) ; // Highlight current task
2023-07-20 19:32:15 +02:00
context . setExtensionPrompt ( MODULE _NAME , extensionPromptText , 1 , $ ( '#objective-chat-depth' ) . val ( ) ) ;
console . info ( ` Current task in context.extensionPrompts.Objective is ${ JSON . stringify ( context . extensionPrompts . Objective ) } ` ) ;
} else {
context . setExtensionPrompt ( MODULE _NAME , '' ) ;
console . info ( ` No current task ` ) ;
}
saveState ( ) ;
}
let taskIdCounter = 0
function getNextTaskId ( ) {
// Make sure id does not exist
while ( globalTasks . find ( task => task . id == taskIdCounter ) != undefined ) {
taskIdCounter += 1
}
const nextId = taskIdCounter
console . log ( ` TaskID assigned: ${ nextId } ` )
taskIdCounter += 1
return nextId
}
class ObjectiveTask {
id
description
completed
parent
children
// UI Elements
taskHtml
descriptionSpan
completedCheckbox
deleteTaskButton
addTaskButton
constructor ( { id = undefined , description , completed = false , parent = null } ) {
this . description = description
this . parent = parent
this . children = [ ]
this . completed = completed
// Generate a new ID if none specified
if ( id == undefined ) {
this . id = getNextTaskId ( )
} else {
this . id = id
}
}
// Complete the current task, setting next task to next incomplete task
completeTask ( ) {
this . completed = true
console . info ( ` Task successfully completed: ${ JSON . stringify ( this . description ) } ` )
setCurrentTask ( )
updateUiTaskList ( )
}
// Add a single task to the UI and attach event listeners for user edits
addUiElement ( ) {
const template = `
< div id = "objective-task-label-${this.id}" class = "flex1 checkbox_label" >
< input id = "objective-task-complete-${this.id}" type = "checkbox" >
2023-07-25 15:36:27 +02:00
< span class = "text_pole objective-task" style = "display: block" id = "objective-task-description-${this.id}" contenteditable > $ { this . description } < / s p a n >
2023-07-20 19:32:15 +02:00
< div id = "objective-task-delete-${this.id}" class = "objective-task-button fa-solid fa-xmark fa-2x" title = "Delete Task" > < / d i v >
< div id = "objective-task-add-${this.id}" class = "objective-task-button fa-solid fa-plus fa-2x" title = "Add Task" > < / d i v >
< / d i v > < b r >
` ;
// Add the filled out template
$ ( '#objective-tasks' ) . append ( template ) ;
this . completedCheckbox = $ ( ` #objective-task-complete- ${ this . id } ` ) ;
this . descriptionSpan = $ ( ` #objective-task-description- ${ this . id } ` ) ;
this . addButton = $ ( ` #objective-task-add- ${ this . id } ` ) ;
this . deleteButton = $ ( ` #objective-task-delete- ${ this . id } ` ) ;
// Add event listeners and set properties
$ ( ` #objective-task-complete- ${ this . id } ` ) . prop ( 'checked' , this . completed ) ;
$ ( ` #objective-task-complete- ${ this . id } ` ) . on ( 'click' , ( ) => ( this . onCompleteClick ( ) ) ) ;
$ ( ` #objective-task-description- ${ this . id } ` ) . on ( 'keyup' , ( ) => ( this . onDescriptionUpdate ( ) ) ) ;
$ ( ` #objective-task-description- ${ this . id } ` ) . on ( 'focusout' , ( ) => ( this . onDescriptionFocusout ( ) ) ) ;
$ ( ` #objective-task-delete- ${ this . id } ` ) . on ( 'click' , ( ) => ( this . onDeleteClick ( ) ) ) ;
$ ( ` #objective-task-add- ${ this . id } ` ) . on ( 'click' , ( ) => ( this . onAddClick ( ) ) ) ;
}
onCompleteClick ( ) {
this . completed = this . completedCheckbox . prop ( 'checked' )
setCurrentTask ( ) ;
}
onDescriptionUpdate ( ) {
this . description = this . descriptionSpan . text ( ) ;
}
onDescriptionFocusout ( ) {
setCurrentTask ( ) ;
}
onDeleteClick ( ) {
deleteTask ( this . id ) ;
}
onAddClick ( ) {
const { _ , index } = getTaskById ( this . id )
addTask ( "" , index + 1 ) ;
setCurrentTask ( ) ;
updateUiTaskList ( ) ;
}
toSaveState ( ) {
return {
"id" : this . id ,
"description" : this . description ,
"completed" : this . completed ,
"parent" : this . parent ,
}
}
}
//###############################//
//# UI AND Settings #//
//###############################//
const defaultSettings = {
objective : "" ,
tasks : [ ] ,
chatDepth : 2 ,
checkFrequency : 3 ,
2023-07-25 15:36:27 +02:00
hideTasks : false ,
prompts : defaultPrompts ,
2023-07-20 19:32:15 +02:00
}
// Convenient single call. Not much at the moment.
function resetState ( ) {
loadSettings ( ) ;
}
//
function saveState ( ) {
const context = getContext ( ) ;
if ( currentChatId == "" ) {
currentChatId = context . chatId
}
// Convert globalTasks for saving
const tasks = globalTasks . map ( task => { return task . toSaveState ( ) } )
chat _metadata [ 'objective' ] = {
objective : globalObjective ,
tasks : tasks ,
checkFrequency : $ ( '#objective-check-frequency' ) . val ( ) ,
chatDepth : $ ( '#objective-chat-depth' ) . val ( ) ,
hideTasks : $ ( '#objective-hide-tasks' ) . prop ( 'checked' ) ,
2023-07-25 15:36:27 +02:00
prompts : objectivePrompts ,
2023-07-20 19:32:15 +02:00
}
saveMetadataDebounced ( ) ;
}
// Dump core state
function debugObjectiveExtension ( ) {
console . log ( JSON . stringify ( {
2023-07-25 15:36:27 +02:00
"currentTask" : currentTask . description ,
2023-07-20 19:32:15 +02:00
"globalObjective" : globalObjective ,
"globalTasks" : globalTasks . map ( v => { return v . toSaveState ( ) } ) ,
2023-07-25 15:36:27 +02:00
"chat_metadata" : chat _metadata [ 'objective' ] ,
"extension_settings" : extension _settings [ 'objective' ] ,
"prompts" : objectivePrompts
2023-07-20 19:32:15 +02:00
} , null , 2 ) )
}
window . debugObjectiveExtension = debugObjectiveExtension
// Populate UI task list
function updateUiTaskList ( ) {
$ ( '#objective-tasks' ) . empty ( )
// Show tasks if there are any
if ( globalTasks . length > 0 ) {
for ( const task of globalTasks ) {
task . addUiElement ( )
}
} else {
// Show button to add tasks if there are none
$ ( '#objective-tasks' ) . append ( `
< input id = "objective-task-add-first" type = "button" class = "menu_button" value = "Add Task" >
` )
$ ( "#objective-task-add-first" ) . on ( 'click' , ( ) => {
addTask ( "" )
setCurrentTask ( )
updateUiTaskList ( )
} )
}
}
// Trigger creation of new tasks with given objective.
async function onGenerateObjectiveClick ( ) {
globalObjective = $ ( '#objective-text' ) . val ( )
await generateTasks ( )
saveState ( )
}
// Update extension prompts
function onChatDepthInput ( ) {
saveState ( )
setCurrentTask ( ) // Ensure extension prompt is updated
}
// Update how often we check for task completion
function onCheckFrequencyInput ( ) {
checkCounter = $ ( "#objective-check-frequency" ) . val ( )
$ ( '#objective-counter' ) . text ( checkCounter )
saveState ( )
}
function onHideTasksInput ( ) {
$ ( '#objective-tasks' ) . prop ( 'hidden' , $ ( '#objective-hide-tasks' ) . prop ( 'checked' ) )
saveState ( )
}
2023-07-25 15:36:27 +02:00
function onEditPromptClick ( ) {
let popupText = ''
popupText += `
< div class = "objective_prompt_modal" >
< div >
< label for = "objective-prompt-generate" > Generation Prompt < / l a b e l >
< textarea id = "objective-prompt-generate" type = "text" class = "text_pole textarea_compact" rows = "8" > < / t e x t a r e a >
< label for = "objective-prompt-check" > Completion Check Prompt < / l a b e l >
< textarea id = "objective-prompt-check" type = "text" class = "text_pole textarea_compact" rows = "8" > < / t e x t a r e a >
< label for = "objective-prompt-extension-prompt" > Injected Prompt < / l a b e l >
< textarea id = "objective-prompt-extension-prompt" type = "text" class = "text_pole textarea_compact" rows = "8" > < / t e x t a r e a >
< / d i v >
2023-07-27 14:26:05 +02:00
< div class = "objective_prompt_block" >
< input id = "objective-custom-prompt-name" style = "flex-grow:2" type = "text" class = "flex1 heightFitContent text_pole widthNatural" maxlength = "250" placeholder = "Custom Prompt Name" >
< input id = "objective-custom-prompt-save" style = "flex-grow:1" class = "menu_button" type = "submit" value = "Save Prompt" / >
< / d i v >
< div class = "objective_prompt_block" >
< label for = "objective-prompt-load" > Load Prompt < / l a b e l >
2023-07-25 15:36:27 +02:00
< select id = "objective-prompt-load" > < select >
2023-07-27 14:26:05 +02:00
< input id = "objective-custom-prompt-delete" class = "menu_button" type = "submit" value = "Delete Prompt" / >
2023-07-25 15:36:27 +02:00
< / d i v >
< / d i v > `
callPopup ( popupText , 'text' )
populateCustomPrompts ( )
// Set current values
$ ( '#objective-prompt-generate' ) . val ( objectivePrompts . createTask )
$ ( '#objective-prompt-check' ) . val ( objectivePrompts . checkTaskCompleted )
$ ( '#objective-prompt-extension-prompt' ) . val ( objectivePrompts . currentTask )
// Handle value updates
$ ( '#objective-prompt-generate' ) . on ( 'input' , ( ) => {
objectivePrompts . createTask = $ ( '#objective-prompt-generate' ) . val ( )
} )
$ ( '#objective-prompt-check' ) . on ( 'input' , ( ) => {
objectivePrompts . checkTaskCompleted = $ ( '#objective-prompt-check' ) . val ( )
} )
$ ( '#objective-prompt-extension-prompt' ) . on ( 'input' , ( ) => {
objectivePrompts . currentTask = $ ( '#objective-prompt-extension-prompt' ) . val ( )
} )
// Handle save
$ ( '#objective-custom-prompt-save' ) . on ( 'click' , ( ) => {
addCustomPrompt ( $ ( '#objective-custom-prompt-name' ) . val ( ) , objectivePrompts )
} )
// Handle delete
$ ( '#objective-custom-prompt-delete' ) . on ( 'click' , ( ) => {
const optionSelected = $ ( "#objective-prompt-load" ) . find ( ':selected' ) . val ( )
deleteCustomPrompt ( optionSelected )
} )
// Handle load
$ ( '#objective-prompt-load' ) . on ( 'change' , loadCustomPrompt )
}
function addCustomPrompt ( customPromptName , customPrompts ) {
if ( customPromptName == "" ) {
toastr . warning ( "Please set custom prompt name to save." )
return
}
2023-07-25 15:45:07 +02:00
if ( customPromptName == "default" ) {
toastr . error ( "Cannot save over default prompt" )
return
}
extension _settings . objective . customPrompts [ customPromptName ] = { }
2023-07-25 15:36:27 +02:00
Object . assign ( extension _settings . objective . customPrompts [ customPromptName ] , customPrompts )
saveSettingsDebounced ( )
populateCustomPrompts ( )
}
function deleteCustomPrompt ( customPromptName ) {
if ( customPromptName == "default" ) {
toastr . error ( "Cannot delete default prompt" )
return
}
delete extension _settings . objective . customPrompts [ customPromptName ]
saveSettingsDebounced ( )
populateCustomPrompts ( )
loadCustomPrompt ( )
}
function loadCustomPrompt ( ) {
const optionSelected = $ ( "#objective-prompt-load" ) . find ( ':selected' ) . val ( )
console . log ( optionSelected )
objectivePrompts = extension _settings . objective . customPrompts [ optionSelected ]
$ ( '#objective-prompt-generate' ) . val ( objectivePrompts . createTask )
$ ( '#objective-prompt-check' ) . val ( objectivePrompts . checkTaskCompleted )
$ ( '#objective-prompt-extension-prompt' ) . val ( objectivePrompts . currentTask )
}
function populateCustomPrompts ( ) {
// Populate saved prompts
$ ( '#objective-prompt-load' ) . empty ( )
for ( const customPromptName in extension _settings . objective . customPrompts ) {
const option = document . createElement ( 'option' ) ;
option . innerText = customPromptName ;
option . value = customPromptName ;
option . selected = customPromptName
$ ( '#objective-prompt-load' ) . append ( option )
}
}
2023-07-20 19:32:15 +02:00
function loadSettings ( ) {
// Load/Init settings for chatId
currentChatId = getContext ( ) . chatId
2023-07-25 15:36:27 +02:00
// Init extension settings
if ( Object . keys ( extension _settings . objective ) . length === 0 ) {
Object . assign ( extension _settings . objective , { 'customPrompts' : { 'default' : defaultPrompts } } )
}
2023-07-20 19:32:15 +02:00
// Bail on home screen
if ( currentChatId == undefined ) {
return
}
// Migrate existing settings
if ( currentChatId in extension _settings . objective ) {
chat _metadata [ 'objective' ] = extension _settings . objective [ currentChatId ] ;
delete extension _settings . objective [ currentChatId ] ;
}
if ( ! ( 'objective' in chat _metadata ) ) {
Object . assign ( chat _metadata , { objective : defaultSettings } ) ;
}
// Update globals
globalObjective = chat _metadata [ 'objective' ] . objective
globalTasks = chat _metadata [ 'objective' ] . tasks . map ( task => {
return new ObjectiveTask ( {
id : task . id ,
description : task . description ,
completed : task . completed ,
parent : task . parent ,
} )
} ) ;
checkCounter = chat _metadata [ 'objective' ] . checkFrequency
// Update UI elements
$ ( '#objective-counter' ) . text ( checkCounter )
$ ( "#objective-text" ) . text ( globalObjective )
updateUiTaskList ( )
$ ( '#objective-chat-depth' ) . val ( chat _metadata [ 'objective' ] . chatDepth )
$ ( '#objective-check-frequency' ) . val ( chat _metadata [ 'objective' ] . checkFrequency )
$ ( '#objective-hide-tasks' ) . prop ( 'checked' , chat _metadata [ 'objective' ] . hideTasks )
2023-07-25 15:36:27 +02:00
$ ( '#objective-tasks' ) . prop ( 'hidden' , $ ( '#objective-hide-tasks' ) . prop ( 'checked' ) )
2023-07-20 19:32:15 +02:00
setCurrentTask ( )
}
function addManualTaskCheckUi ( ) {
$ ( '#extensionsMenu' ) . prepend ( `
< div id = "objective-task-manual-check-menu-item" class = "list-group-item flex-container flexGap5" >
< div id = "objective-task-manual-check" class = "extensionsMenuExtensionButton fa-regular fa-square-check" / > < / d i v >
Manual Task Check
< / d i v > ` )
$ ( '#objective-task-manual-check-menu-item' ) . attr ( 'title' , 'Trigger AI check of completed tasks' ) . on ( 'click' , checkTaskCompleted )
}
jQuery ( ( ) => {
const settingsHtml = `
< div class = "objective-settings" >
< div class = "inline-drawer" >
< div class = "inline-drawer-toggle inline-drawer-header" >
< b > Objective < / b >
< div class = "inline-drawer-icon fa-solid fa-circle-chevron-down down" > < / d i v >
< / d i v >
< div class = "inline-drawer-content" >
< label for = "objective-text" > < small > Enter an objective and generate tasks . The AI will attempt to complete tasks autonomously < / s m a l l > < / l a b e l >
< textarea id = "objective-text" type = "text" class = "text_pole textarea_compact" rows = "4" > < / t e x t a r e a >
< div class = "objective_block flex-container" >
< input id = "objective-generate" class = "menu_button" type = "submit" value = "Auto-Generate Tasks" / >
< label class = "checkbox_label" > < input id = "objective-hide-tasks" type = "checkbox" > Hide Tasks < / l a b e l >
< / d i v >
< div id = "objective-tasks" > < / d i v >
< div class = "objective_block margin-bot-10px" >
< div class = "objective_block objective_block_control flex1 flexFlowColumn" >
< label for = "objective-chat-depth" > Position in Chat < / l a b e l >
< input id = "objective-chat-depth" class = "text_pole widthUnset" type = "number" min = "0" max = "99" / >
< / d i v >
< br >
< div class = "objective_block objective_block_control flex1" >
< label for = "objective-check-frequency" > Task Check Frequency < / l a b e l >
< input id = "objective-check-frequency" class = "text_pole widthUnset" type = "number" min = "0" max = "99" / >
< small > ( 0 = disabled ) < / s m a l l >
< / d i v >
< / d i v >
< span > Messages until next AI task completion check < span id = "objective-counter" > 0 < / s p a n > < / s p a n >
2023-07-25 15:36:27 +02:00
< div class = "objective_block flex-container" >
< input id = "objective_prompt_edit" class = "menu_button" type = "submit" value = "Edit Prompts" / >
< / d i v >
2023-07-20 19:32:15 +02:00
< hr class = "sysHR" >
< / d i v >
< / d i v > ` ;
addManualTaskCheckUi ( )
$ ( '#extensions_settings' ) . append ( settingsHtml ) ;
$ ( '#objective-generate' ) . on ( 'click' , onGenerateObjectiveClick )
$ ( '#objective-chat-depth' ) . on ( 'input' , onChatDepthInput )
$ ( "#objective-check-frequency" ) . on ( 'input' , onCheckFrequencyInput )
$ ( '#objective-hide-tasks' ) . on ( 'click' , onHideTasksInput )
2023-07-25 15:36:27 +02:00
$ ( '#objective_prompt_edit' ) . on ( 'click' , onEditPromptClick )
2023-07-20 19:32:15 +02:00
loadSettings ( )
eventSource . on ( event _types . CHAT _CHANGED , ( ) => {
resetState ( )
} ) ;
eventSource . on ( event _types . MESSAGE _RECEIVED , ( ) => {
if ( currentChatId == undefined ) {
return
}
if ( $ ( "#objective-check-frequency" ) . val ( ) > 0 ) {
// Check only at specified interval
if ( checkCounter <= 0 ) {
checkTaskCompleted ( ) ;
}
checkCounter -= 1
}
setCurrentTask ( ) ;
$ ( '#objective-counter' ) . text ( checkCounter )
} ) ;
registerSlashCommand ( 'taskcheck' , checkTaskCompleted , [ ] , ' – checks if the current task is completed' , true , true ) ;
} ) ;