Add project taskitem pool and planner fixes
This commit is contained in:
@@ -265,6 +265,306 @@
|
||||
});
|
||||
}
|
||||
|
||||
auto resolveTaskitemPoolProject = [&](const json& params) {
|
||||
const std::string requested = params.value("projectName", "");
|
||||
return taskitemPoolInferProjectName(state.workspaceRoot, requested);
|
||||
};
|
||||
|
||||
// --- importTaskitemPool ---
|
||||
if (method == "importTaskitemPool") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
if (state.workspaceRoot.empty())
|
||||
return headlessRpcError(id, -32000, "No workspace root");
|
||||
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
const std::string projectName = resolveTaskitemPoolProject(params);
|
||||
const bool replace = params.value("replace", false);
|
||||
const std::string sourceLabel = params.value("sourceLabel", params.value("taskitemsPath", ""));
|
||||
std::string lockError;
|
||||
auto poolLock = acquireTaskitemPoolLock(state.workspaceRoot, projectName, true, &lockError);
|
||||
if (!poolLock.has_value())
|
||||
return headlessRpcError(id, -32000, lockError);
|
||||
|
||||
taskitem_pool_json payload = params.value("taskitems", taskitem_pool_json::array());
|
||||
if ((!payload.is_array() || payload.empty()) && params.value("taskitemsPath", std::string()).size() > 0) {
|
||||
std::string importError;
|
||||
auto loaded = loadTaskitemImportPayload(state.workspaceRoot,
|
||||
params.value("taskitemsPath", ""),
|
||||
&importError);
|
||||
if (!loaded.has_value()) return headlessRpcError(id, -32000, importError);
|
||||
payload = loaded.value();
|
||||
}
|
||||
|
||||
taskitem_pool_json tasks = extractTaskitemArray(payload);
|
||||
if (!tasks.is_array() || tasks.empty())
|
||||
return headlessRpcError(id, -32602, "No taskitems to import");
|
||||
|
||||
TaskitemPoolState pool;
|
||||
if (auto existing = loadTaskitemPool(state.workspaceRoot, projectName); existing.has_value()) {
|
||||
pool = existing.value();
|
||||
} else {
|
||||
pool.projectName = projectName;
|
||||
pool.createdAt = workItemTimestamp();
|
||||
}
|
||||
if (replace) pool.items.clear();
|
||||
|
||||
int importedCount = 0;
|
||||
int updatedCount = 0;
|
||||
for (const auto& task : tasks) {
|
||||
if (!task.is_object()) continue;
|
||||
TaskitemPoolEntry entry = taskitemPoolEntryFromTask(task, sourceLabel);
|
||||
if (entry.itemId.empty()) continue;
|
||||
|
||||
bool replaced = false;
|
||||
for (auto& existing : pool.items) {
|
||||
if (existing.itemId != entry.itemId) continue;
|
||||
const std::string priorStatus = existing.status;
|
||||
const std::string priorAssignee = existing.assignee;
|
||||
const std::string priorClaimedAt = existing.claimedAt;
|
||||
const std::string priorCompletedAt = existing.completedAt;
|
||||
const taskitem_pool_json priorResult = existing.result;
|
||||
existing = entry;
|
||||
if (!replace && (priorStatus == "claimed" || priorStatus == "complete")) {
|
||||
existing.status = priorStatus;
|
||||
existing.assignee = priorAssignee;
|
||||
existing.claimedAt = priorClaimedAt;
|
||||
existing.completedAt = priorCompletedAt;
|
||||
existing.result = priorResult;
|
||||
}
|
||||
replaced = true;
|
||||
++updatedCount;
|
||||
break;
|
||||
}
|
||||
if (!replaced) {
|
||||
pool.items.push_back(entry);
|
||||
++importedCount;
|
||||
}
|
||||
}
|
||||
|
||||
recomputeTaskitemPoolStatuses(&pool);
|
||||
auto saveResult = saveTaskitemPool(state.workspaceRoot, pool);
|
||||
if (!saveResult.success) {
|
||||
return headlessRpcError(id, -32000, "Unable to save taskitem pool");
|
||||
}
|
||||
state.eventStream.emit({
|
||||
"taskitem_pool.imported",
|
||||
"",
|
||||
{
|
||||
{"projectName", projectName},
|
||||
{"importedCount", importedCount},
|
||||
{"updatedCount", updatedCount},
|
||||
{"counts", taskitemPoolCounts(pool)}
|
||||
},
|
||||
workItemTimestamp()
|
||||
});
|
||||
return headlessRpcResult(id, {
|
||||
{"success", true},
|
||||
{"projectName", projectName},
|
||||
{"path", saveResult.path},
|
||||
{"importedCount", importedCount},
|
||||
{"updatedCount", updatedCount},
|
||||
{"itemCount", saveResult.itemCount},
|
||||
{"counts", taskitemPoolCounts(pool)}
|
||||
});
|
||||
}
|
||||
|
||||
// --- listTaskitemPool ---
|
||||
if (method == "listTaskitemPool") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
if (state.workspaceRoot.empty())
|
||||
return headlessRpcError(id, -32000, "No workspace root");
|
||||
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
const std::string projectName = resolveTaskitemPoolProject(params);
|
||||
std::string lockError;
|
||||
auto poolLock = acquireTaskitemPoolLock(state.workspaceRoot, projectName, false, &lockError);
|
||||
if (!poolLock.has_value())
|
||||
return headlessRpcError(id, -32000, lockError);
|
||||
auto pool = loadTaskitemPool(state.workspaceRoot, projectName);
|
||||
if (!pool.has_value())
|
||||
return headlessRpcError(id, -32000, "Taskitem pool not found");
|
||||
|
||||
recomputeTaskitemPoolStatuses(&pool.value());
|
||||
const std::string statusFilter = params.value("status", "");
|
||||
json items = json::array();
|
||||
for (const auto& item : pool->items) {
|
||||
if (!statusFilter.empty() && item.status != statusFilter) continue;
|
||||
items.push_back(item.toJson());
|
||||
}
|
||||
return headlessRpcResult(id, {
|
||||
{"projectName", projectName},
|
||||
{"items", items},
|
||||
{"count", (int)items.size()},
|
||||
{"counts", taskitemPoolCounts(*pool)}
|
||||
});
|
||||
}
|
||||
|
||||
// --- claimTaskitem ---
|
||||
if (method == "claimTaskitem") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
if (state.workspaceRoot.empty())
|
||||
return headlessRpcError(id, -32000, "No workspace root");
|
||||
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
const std::string projectName = resolveTaskitemPoolProject(params);
|
||||
const std::string itemId = params.value("itemId", "");
|
||||
const std::string assignee = params.value("assignee", "");
|
||||
if (itemId.empty() || assignee.empty())
|
||||
return headlessRpcError(id, -32602, "Missing itemId or assignee");
|
||||
std::string lockError;
|
||||
auto poolLock = acquireTaskitemPoolLock(state.workspaceRoot, projectName, true, &lockError);
|
||||
if (!poolLock.has_value())
|
||||
return headlessRpcError(id, -32000, lockError);
|
||||
|
||||
auto pool = loadTaskitemPool(state.workspaceRoot, projectName);
|
||||
if (!pool.has_value())
|
||||
return headlessRpcError(id, -32000, "Taskitem pool not found");
|
||||
|
||||
recomputeTaskitemPoolStatuses(&pool.value());
|
||||
TaskitemPoolEntry* claimed = nullptr;
|
||||
for (auto& item : pool->items) {
|
||||
if (item.itemId != itemId) continue;
|
||||
if (item.status != "ready")
|
||||
return headlessRpcError(id, -32000, "Taskitem not ready");
|
||||
item.status = "claimed";
|
||||
item.assignee = assignee;
|
||||
item.workerType = params.value("workerType", item.workerType.empty() ? "llm" : item.workerType);
|
||||
item.claimedAt = workItemTimestamp();
|
||||
claimed = &item;
|
||||
break;
|
||||
}
|
||||
if (!claimed)
|
||||
return headlessRpcError(id, -32602, "Taskitem not found");
|
||||
|
||||
auto saveResult = saveTaskitemPool(state.workspaceRoot, *pool);
|
||||
if (!saveResult.success)
|
||||
return headlessRpcError(id, -32000, "Unable to save taskitem pool");
|
||||
state.eventStream.emit({
|
||||
"taskitem_pool.claimed",
|
||||
itemId,
|
||||
{{"projectName", projectName}, {"assignee", assignee}, {"workerType", claimed->workerType}},
|
||||
workItemTimestamp()
|
||||
});
|
||||
return headlessRpcResult(id, {
|
||||
{"success", true},
|
||||
{"projectName", projectName},
|
||||
{"item", claimed->toJson()},
|
||||
{"counts", taskitemPoolCounts(*pool)}
|
||||
});
|
||||
}
|
||||
|
||||
// --- releaseTaskitem ---
|
||||
if (method == "releaseTaskitem") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
if (state.workspaceRoot.empty())
|
||||
return headlessRpcError(id, -32000, "No workspace root");
|
||||
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
const std::string projectName = resolveTaskitemPoolProject(params);
|
||||
const std::string itemId = params.value("itemId", "");
|
||||
if (itemId.empty())
|
||||
return headlessRpcError(id, -32602, "Missing itemId");
|
||||
std::string lockError;
|
||||
auto poolLock = acquireTaskitemPoolLock(state.workspaceRoot, projectName, true, &lockError);
|
||||
if (!poolLock.has_value())
|
||||
return headlessRpcError(id, -32000, lockError);
|
||||
|
||||
auto pool = loadTaskitemPool(state.workspaceRoot, projectName);
|
||||
if (!pool.has_value())
|
||||
return headlessRpcError(id, -32000, "Taskitem pool not found");
|
||||
|
||||
TaskitemPoolEntry* released = nullptr;
|
||||
for (auto& item : pool->items) {
|
||||
if (item.itemId != itemId) continue;
|
||||
if (item.status != "claimed")
|
||||
return headlessRpcError(id, -32000, "Taskitem not claimed");
|
||||
const std::string expectedAssignee = params.value("assignee", "");
|
||||
if (!expectedAssignee.empty() && item.assignee != expectedAssignee)
|
||||
return headlessRpcError(id, -32000, "Taskitem claimed by different assignee");
|
||||
item.status = "ready";
|
||||
item.assignee.clear();
|
||||
item.claimedAt.clear();
|
||||
released = &item;
|
||||
break;
|
||||
}
|
||||
if (!released)
|
||||
return headlessRpcError(id, -32602, "Taskitem not found");
|
||||
|
||||
recomputeTaskitemPoolStatuses(&pool.value());
|
||||
auto saveResult = saveTaskitemPool(state.workspaceRoot, *pool);
|
||||
if (!saveResult.success)
|
||||
return headlessRpcError(id, -32000, "Unable to save taskitem pool");
|
||||
state.eventStream.emit({
|
||||
"taskitem_pool.released",
|
||||
itemId,
|
||||
{{"projectName", projectName}, {"reason", params.value("reason", "")}},
|
||||
workItemTimestamp()
|
||||
});
|
||||
return headlessRpcResult(id, {
|
||||
{"success", true},
|
||||
{"projectName", projectName},
|
||||
{"item", released->toJson()},
|
||||
{"counts", taskitemPoolCounts(*pool)}
|
||||
});
|
||||
}
|
||||
|
||||
// --- completePooledTaskitem ---
|
||||
if (method == "completePooledTaskitem") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
if (state.workspaceRoot.empty())
|
||||
return headlessRpcError(id, -32000, "No workspace root");
|
||||
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
const std::string projectName = resolveTaskitemPoolProject(params);
|
||||
const std::string itemId = params.value("itemId", "");
|
||||
if (itemId.empty())
|
||||
return headlessRpcError(id, -32602, "Missing itemId");
|
||||
std::string lockError;
|
||||
auto poolLock = acquireTaskitemPoolLock(state.workspaceRoot, projectName, true, &lockError);
|
||||
if (!poolLock.has_value())
|
||||
return headlessRpcError(id, -32000, lockError);
|
||||
|
||||
auto pool = loadTaskitemPool(state.workspaceRoot, projectName);
|
||||
if (!pool.has_value())
|
||||
return headlessRpcError(id, -32000, "Taskitem pool not found");
|
||||
|
||||
TaskitemPoolEntry* completed = nullptr;
|
||||
for (auto& item : pool->items) {
|
||||
if (item.itemId != itemId) continue;
|
||||
if (item.status != "claimed" && item.status != "ready")
|
||||
return headlessRpcError(id, -32000, "Taskitem not claimable for completion");
|
||||
item.status = "complete";
|
||||
item.completedAt = workItemTimestamp();
|
||||
item.result = params.value("result", json::object());
|
||||
completed = &item;
|
||||
break;
|
||||
}
|
||||
if (!completed)
|
||||
return headlessRpcError(id, -32602, "Taskitem not found");
|
||||
|
||||
recomputeTaskitemPoolStatuses(&pool.value());
|
||||
auto saveResult = saveTaskitemPool(state.workspaceRoot, *pool);
|
||||
if (!saveResult.success)
|
||||
return headlessRpcError(id, -32000, "Unable to save taskitem pool");
|
||||
state.eventStream.emit({
|
||||
"taskitem_pool.completed",
|
||||
itemId,
|
||||
{{"projectName", projectName}},
|
||||
workItemTimestamp()
|
||||
});
|
||||
return headlessRpcResult(id, {
|
||||
{"success", true},
|
||||
{"projectName", projectName},
|
||||
{"item", completed->toJson()},
|
||||
{"counts", taskitemPoolCounts(*pool)}
|
||||
});
|
||||
}
|
||||
|
||||
// --- createWorkflow ---
|
||||
if (method == "createWorkflow") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
|
||||
Reference in New Issue
Block a user