Add project taskitem pool and planner fixes

This commit is contained in:
Bill
2026-03-18 01:02:21 -06:00
parent af6fa169b7
commit 69243141dd
15 changed files with 1860 additions and 91 deletions

View File

@@ -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))