314 lines
12 KiB
C++
314 lines
12 KiB
C++
#include "planning.h"
|
|
#include <iomanip>
|
|
|
|
namespace humanus {
|
|
|
|
/**
|
|
* Execute the planning tool with the given command and parameters.
|
|
*
|
|
* Parameters:
|
|
* - command: The operation to perform
|
|
* - plan_id: Unique identifier for the plan
|
|
* - title: Title for the plan (used with create command)
|
|
* - steps: List of steps for the plan (used with create command)
|
|
* - step_index: Index of the step to update (used with mark_step command)
|
|
* - step_status: Status to set for a step (used with mark_step command)
|
|
* - step_notes: Additional notes for a step (used with mark_step command)
|
|
*/
|
|
ToolResult PlanningTool::execute(const json& args) {
|
|
try {
|
|
std::string command = args.value("command", "");
|
|
std::string plan_id = args.value("plan_id", "");
|
|
std::string title = args.value("title", "");
|
|
std::vector<std::string> steps = args.value("steps", std::vector<std::string>());
|
|
int step_index = args.value("step_index", -1);
|
|
std::string step_status = args.value("step_status", "not_started");
|
|
std::string step_notes = args.value("step_notes", "");
|
|
|
|
if (command == "create") {
|
|
return _create_plan(plan_id, title, steps);
|
|
} else if (command == "update") {
|
|
return _update_plan(plan_id, title, steps);
|
|
} else if (command == "list") {
|
|
return _list_plans();
|
|
} else if (command == "get") {
|
|
return _get_plan(plan_id);
|
|
} else if (command == "set_active") {
|
|
return _set_active_plan(plan_id);
|
|
} else if (command == "mark_step") {
|
|
return _mark_step(plan_id, step_index, step_status, step_notes);
|
|
} else if (command == "delete") {
|
|
return _delete_plan(plan_id);
|
|
} else {
|
|
throw std::runtime_error("Unrecognized command: " + command + ". Allowed commands are: create, update, list, get, set_active, mark_step, delete");
|
|
}
|
|
} catch (const std::exception& e) {
|
|
return ToolError(e.what());
|
|
}
|
|
}
|
|
|
|
// Create a new plan with the given ID, title, and steps.
|
|
ToolResult PlanningTool::_create_plan(const std::string& plan_id, const std::string& title, const std::vector<std::string>& steps) {
|
|
if (plan_id.empty()) {
|
|
return ToolError("Parameter `plan_id` is required for command: create");
|
|
}
|
|
|
|
if (plans.find(plan_id) != plans.end()) {
|
|
return ToolError("Plan with ID " + plan_id + " already exists. Use 'update' to modify existing plans.");
|
|
}
|
|
|
|
if (title.empty()) {
|
|
return ToolError("Parameter `title` is required for command: create");
|
|
}
|
|
|
|
if (steps.empty()) {
|
|
return ToolError("Parameter `steps` must be a non-empty list of strings for command: create");
|
|
}
|
|
|
|
// Create a new plan with initialized step statuses
|
|
json plan = {
|
|
{"plan_id", plan_id},
|
|
{"title", title},
|
|
{"steps", steps},
|
|
{"step_statuses", std::vector<std::string>(steps.size(), "not_started")},
|
|
{"step_notes", std::vector<std::string>(steps.size(), "")}
|
|
};
|
|
|
|
plans[plan_id] = plan;
|
|
_current_plan_id = plan_id; // Set as active plan
|
|
|
|
return ToolResult(
|
|
"Plan created successfully with ID: " + plan_id + "\n\n" + _format_plan(plan)
|
|
);
|
|
}
|
|
|
|
// Update an existing plan with new title or steps.
|
|
ToolResult PlanningTool::_update_plan(const std::string& plan_id, const std::string& title, const std::vector<std::string>& steps) {
|
|
if (plan_id.empty()) {
|
|
return ToolError("Parameter `plan_id` is required for command: update");
|
|
}
|
|
|
|
if (plans.find(plan_id) == plans.end()) {
|
|
return ToolError("No plan found with ID: " + plan_id);
|
|
}
|
|
|
|
json plan = plans[plan_id];
|
|
|
|
if (!title.empty()) {
|
|
plan["title"] = title;
|
|
}
|
|
|
|
if (!steps.empty()) {
|
|
// Preserve existing step statuses for unchanged steps
|
|
auto old_steps = plan["steps"];
|
|
auto old_step_statuses = plan["step_statuses"];
|
|
auto old_step_notes = plan["step_notes"];
|
|
|
|
// Create new step statuses and notes
|
|
std::vector<std::string> new_step_statuses;
|
|
std::vector<std::string> new_step_notes;
|
|
|
|
for (size_t i = 0; i < steps.size(); ++i) {
|
|
// If the step exists at the same position in old steps, preserve status and notes
|
|
if (i < old_steps.size() && old_steps[i] == steps[i]) {
|
|
new_step_statuses.push_back(old_step_statuses[i]);
|
|
new_step_notes.push_back(old_step_notes[i]);
|
|
} else {
|
|
new_step_statuses.push_back("not_started");
|
|
new_step_notes.push_back("");
|
|
}
|
|
}
|
|
|
|
plan["steps"] = steps;
|
|
plan["step_statuses"] = new_step_statuses;
|
|
plan["step_notes"] = new_step_notes;
|
|
}
|
|
|
|
plans[plan_id] = plan; // Note: Remember to update the plan in the map
|
|
|
|
return ToolResult(
|
|
"Plan updated successfully with ID: " + plan_id + "\n\n" + _format_plan(plan)
|
|
);
|
|
}
|
|
|
|
// List all available plans.
|
|
ToolResult PlanningTool::_list_plans() {
|
|
if (plans.empty()) {
|
|
return ToolResult(
|
|
"No plans available. Create a plan with the 'create' command."
|
|
);
|
|
}
|
|
|
|
std::string output = "Available plans:\n";
|
|
for (const auto& [plan_id, plan] : plans) {
|
|
std::string current_marker = plan_id == _current_plan_id ? " (active)" : "";
|
|
bool completed = std::all_of(plan["step_statuses"].begin(), plan["step_statuses"].end(), [](const std::string& status) {
|
|
return status == "completed";
|
|
});
|
|
int total = plan["steps"].size();
|
|
std::string progress = std::to_string(completed) + "/" + std::to_string(total) + " steps completed";
|
|
output += "• " + plan_id + current_marker + ": " + plan.value("title", "Unknown Plan") + " - " + progress + "\n";
|
|
}
|
|
|
|
return ToolResult(output);
|
|
}
|
|
|
|
// Get details of a specific plan.
|
|
ToolResult PlanningTool::_get_plan(const std::string& plan_id) {
|
|
std::string _plan_id = plan_id;
|
|
|
|
if (plan_id.empty()) {
|
|
// If no plan_id is provided, use the current active plan
|
|
if (_current_plan_id.empty()) {
|
|
return ToolError("No active plan. Please specify a plan_id or set an active plan.");
|
|
}
|
|
_plan_id = _current_plan_id;
|
|
}
|
|
|
|
if (plans.find(_plan_id) == plans.end()) {
|
|
return ToolError("No plan found with ID: " + _plan_id);
|
|
}
|
|
|
|
json plan = plans[_plan_id];
|
|
return ToolResult(_format_plan(plan));
|
|
}
|
|
|
|
// Set a plan as the active plan.
|
|
ToolResult PlanningTool::_set_active_plan(const std::string& plan_id) {
|
|
if (plan_id.empty()) {
|
|
return ToolError("Parameter `plan_id` is required for command: set_active");
|
|
}
|
|
|
|
if (plans.find(plan_id) == plans.end()) {
|
|
return ToolError("No plan found with ID: " + plan_id);
|
|
}
|
|
|
|
_current_plan_id = plan_id;
|
|
return ToolResult(
|
|
"Plan '" + plan_id + "' is now the active plan.\n\n" + _format_plan(plans[plan_id])
|
|
);
|
|
}
|
|
|
|
// Mark a step with a specific status and optional notes.
|
|
ToolResult PlanningTool::_mark_step(const std::string& plan_id, int step_index, const std::string& step_status, const std::string& step_notes) {
|
|
std::string _plan_id = plan_id;
|
|
|
|
if (plan_id.empty()) {
|
|
// If no plan_id is provided, use the current active plan
|
|
if (_current_plan_id.empty()) {
|
|
return ToolError("No active plan. Please specify a plan_id or set an active plan.");
|
|
}
|
|
_plan_id = _current_plan_id;
|
|
}
|
|
|
|
if (plans.find(_plan_id) == plans.end()) {
|
|
return ToolError("No plan found with ID: " + _plan_id);
|
|
}
|
|
|
|
json plan = plans[_plan_id];
|
|
|
|
if (step_index < 0 || step_index >= plan["steps"].size()) {
|
|
return ToolError("Invalid step index: " + std::to_string(step_index) + ". Valid indices range from 0 to " + std::to_string((int)plan["steps"].size() - 1));
|
|
}
|
|
|
|
if (!step_status.empty()) {
|
|
if (step_status != "not_started" && step_status != "in_progress" && step_status != "completed" && step_status != "blocked") {
|
|
return ToolError("Invalid step status: " + step_status + ". Valid statuses are: not_started, in_progress, completed, blocked");
|
|
}
|
|
plan["step_statuses"][step_index] = step_status;
|
|
}
|
|
|
|
if (!step_notes.empty()) {
|
|
plan["step_notes"][step_index] = step_notes;
|
|
}
|
|
|
|
plans[_plan_id] = plan;
|
|
|
|
return ToolResult(
|
|
"Step " + std::to_string(step_index) + " updated in plan '" + _plan_id + "'.\n\n" + _format_plan(plan)
|
|
);
|
|
}
|
|
|
|
// Delete a plan.
|
|
ToolResult PlanningTool::_delete_plan(const std::string& plan_id) {
|
|
if (plan_id.empty()) {
|
|
return ToolError("Parameter `plan_id` is required for command: delete");
|
|
}
|
|
|
|
if (plans.find(plan_id) == plans.end()) {
|
|
return ToolError("No plan found with ID: " + plan_id);
|
|
}
|
|
|
|
plans.erase(plan_id);
|
|
|
|
// If the deleted plan was the active plan, clear the active plan
|
|
if (_current_plan_id == plan_id) {
|
|
_current_plan_id.clear();
|
|
}
|
|
|
|
return ToolResult(
|
|
"Plan '" + plan_id + "' has been deleted."
|
|
);
|
|
}
|
|
|
|
// Format a plan for display.
|
|
std::string PlanningTool::_format_plan(const json& plan) {
|
|
std::stringstream output_ss;
|
|
output_ss << "Plan: " << plan.value("title", "Unknown Plan") << " (ID: " << plan["plan_id"].get<std::string>() << ")\n";
|
|
int current_length = output_ss.str().length();
|
|
|
|
for (int i = 0; i < current_length; i++) {
|
|
output_ss << "=";
|
|
}
|
|
|
|
output_ss << "\n\n";
|
|
|
|
// Calculate progress statistics
|
|
int total_steps = plan["steps"].size();
|
|
int completed_steps = std::count_if(plan["step_statuses"].begin(), plan["step_statuses"].end(), [](const std::string& status) {
|
|
return status == "completed";
|
|
});
|
|
int in_progress_steps = std::count_if(plan["step_statuses"].begin(), plan["step_statuses"].end(), [](const std::string& status) {
|
|
return status == "in_progress";
|
|
});
|
|
int blocked_steps = std::count_if(plan["step_statuses"].begin(), plan["step_statuses"].end(), [](const std::string& status) {
|
|
return status == "blocked";
|
|
});
|
|
int not_started_steps = std::count_if(plan["step_statuses"].begin(), plan["step_statuses"].end(), [](const std::string& status) {
|
|
return status == "not_started";
|
|
});
|
|
|
|
output_ss << "Progress: " << completed_steps << "/" << total_steps << " steps completed ";
|
|
if (total_steps > 0) {
|
|
double percentage = (double)completed_steps / total_steps * 100;
|
|
output_ss << "(" << std::fixed << std::setprecision(1) << percentage << "%)\n";
|
|
} else {
|
|
output_ss << "(0%)\n";
|
|
}
|
|
|
|
output_ss << "Status: " << completed_steps << " completed, " << in_progress_steps << " in progress, " << blocked_steps << " blocked, " << not_started_steps << " not started\n\n";
|
|
output_ss << "Steps:\n";
|
|
|
|
static std::map<std::string, std::string> status_symbols = {
|
|
{"not_started", "[ ]"},
|
|
{"in_progress", "[→]"},
|
|
{"completed", "[✓]"},
|
|
{"blocked", "[!]"}
|
|
};
|
|
|
|
// Add each step with its status and notes
|
|
for (size_t i = 0; i < plan["steps"].size(); ++i) {
|
|
std::string step = plan["steps"][i];
|
|
std::string step_status = plan["step_statuses"][i];
|
|
std::string step_notes = plan["step_notes"][i];
|
|
std::string status_symbol = status_symbols.find(step_status) != status_symbols.end() ? status_symbols[step_status] : "[ ]";
|
|
output_ss << i << ". " + status_symbols[step_status] << " " << step << "\n";
|
|
if (!step_notes.empty()) {
|
|
output_ss << " Notes: " << step_notes << "\n";
|
|
}
|
|
}
|
|
|
|
return output_ss.str();
|
|
}
|
|
|
|
} // namespace humanus
|