Step 384: add workflow session protocol model
This commit is contained in:
@@ -2335,4 +2335,13 @@ target_link_libraries(step383_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step384_test tests/step384_test.cpp)
|
||||
target_include_directories(step384_test PRIVATE src)
|
||||
target_link_libraries(step384_test PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::tree-sitter::tree-sitter
|
||||
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
185
editor/src/WorkflowProtocol.h
Normal file
185
editor/src/WorkflowProtocol.h
Normal file
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
// Step 384: Workflow session protocol for MCP-driven orchestration.
|
||||
|
||||
#include "WorkflowState.h"
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct WorkflowSession {
|
||||
std::string sessionId;
|
||||
std::string projectName;
|
||||
std::string currentPhase = "init";
|
||||
std::string startedAt = workItemTimestamp();
|
||||
std::vector<std::string> commands;
|
||||
|
||||
json toJson() const {
|
||||
return {
|
||||
{"sessionId", sessionId},
|
||||
{"projectName", projectName},
|
||||
{"currentPhase", currentPhase},
|
||||
{"startedAt", startedAt},
|
||||
{"commands", commands}
|
||||
};
|
||||
}
|
||||
|
||||
static WorkflowSession fromJson(const json& j) {
|
||||
WorkflowSession s;
|
||||
s.sessionId = j.value("sessionId", "");
|
||||
s.projectName = j.value("projectName", "");
|
||||
s.currentPhase = j.value("currentPhase", "init");
|
||||
s.startedAt = j.value("startedAt", workItemTimestamp());
|
||||
if (j.contains("commands") && j["commands"].is_array()) {
|
||||
s.commands = j["commands"].get<std::vector<std::string>>();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
struct ProtocolAction {
|
||||
std::string phase;
|
||||
std::string command;
|
||||
std::string reason;
|
||||
|
||||
json toJson() const {
|
||||
return {{"phase", phase}, {"command", command}, {"reason", reason}};
|
||||
}
|
||||
};
|
||||
|
||||
inline std::vector<std::string> workflowProtocolPhases() {
|
||||
return {"init", "model", "annotate", "plan", "route",
|
||||
"execute", "assist", "review", "complete"};
|
||||
}
|
||||
|
||||
inline bool isWorkflowProtocolPhase(const std::string& phase) {
|
||||
const auto phases = workflowProtocolPhases();
|
||||
return std::find(phases.begin(), phases.end(), phase) != phases.end();
|
||||
}
|
||||
|
||||
inline int workflowPhaseIndex(const std::string& phase) {
|
||||
const auto phases = workflowProtocolPhases();
|
||||
for (size_t i = 0; i < phases.size(); ++i) {
|
||||
if (phases[i] == phase) return static_cast<int>(i);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline bool validateTransition(const std::string& from, const std::string& to) {
|
||||
int fromIdx = workflowPhaseIndex(from);
|
||||
int toIdx = workflowPhaseIndex(to);
|
||||
if (fromIdx < 0 || toIdx < 0) return false;
|
||||
if (fromIdx == toIdx) return true;
|
||||
return toIdx == fromIdx + 1;
|
||||
}
|
||||
|
||||
inline std::string protocolPhaseForCommand(const std::string& command) {
|
||||
if (command == "whetstone_create_skeleton") return "model";
|
||||
if (command == "whetstone_add_skeleton_node") return "model";
|
||||
if (command == "whetstone_infer_annotations") return "annotate";
|
||||
if (command == "whetstone_create_workflow") return "plan";
|
||||
if (command == "whetstone_orchestrate_advance") return "route";
|
||||
if (command == "whetstone_orchestrate_run_deterministic") return "execute";
|
||||
if (command == "whetstone_get_blockers" || command == "whetstone_submit_result")
|
||||
return "assist";
|
||||
if (command == "whetstone_get_review_policy" || command == "whetstone_set_review_policy")
|
||||
return "review";
|
||||
if (command == "whetstone_save_workflow") return "complete";
|
||||
return "";
|
||||
}
|
||||
|
||||
inline void recordWorkflowCommand(WorkflowSession& session,
|
||||
const std::string& command) {
|
||||
session.commands.push_back(command);
|
||||
std::string hintedPhase = protocolPhaseForCommand(command);
|
||||
if (hintedPhase.empty()) return;
|
||||
int current = workflowPhaseIndex(session.currentPhase);
|
||||
int hinted = workflowPhaseIndex(hintedPhase);
|
||||
if (hinted > current) session.currentPhase = hintedPhase;
|
||||
}
|
||||
|
||||
inline std::string detectProtocolPhaseFromWorkflow(const WorkflowState& workflow) {
|
||||
auto stats = workflow.getStats();
|
||||
if (stats.total == 0) return "plan";
|
||||
if (stats.complete == stats.total) return "complete";
|
||||
if (stats.review > 0) return "review";
|
||||
|
||||
bool hasExternalOrHuman = false;
|
||||
for (const auto& status : {WI_ASSIGNED, WI_IN_PROGRESS}) {
|
||||
for (const auto& item : workflow.queue.getByStatus(status)) {
|
||||
if (item.workerType == "slm" || item.workerType == "llm" ||
|
||||
item.workerType == "human") {
|
||||
hasExternalOrHuman = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasExternalOrHuman) return "assist";
|
||||
if (stats.ready > 0 || stats.assigned > 0 || stats.inProgress > 0) {
|
||||
return "execute";
|
||||
}
|
||||
return "route";
|
||||
}
|
||||
|
||||
inline ProtocolAction getNextAction(const WorkflowSession& session,
|
||||
const WorkflowState* workflow = nullptr) {
|
||||
std::string phase = session.currentPhase;
|
||||
if (workflow) {
|
||||
std::string inferred = detectProtocolPhaseFromWorkflow(*workflow);
|
||||
if (workflowPhaseIndex(inferred) > workflowPhaseIndex(phase)) {
|
||||
phase = inferred;
|
||||
}
|
||||
}
|
||||
|
||||
if (phase == "init") {
|
||||
return {"init", "whetstone_create_skeleton", "Initialize project skeleton."};
|
||||
}
|
||||
if (phase == "model") {
|
||||
return {"model", "whetstone_add_skeleton_node",
|
||||
"Add skeleton functions/classes until model is complete."};
|
||||
}
|
||||
if (phase == "annotate") {
|
||||
return {"annotate", "whetstone_infer_annotations",
|
||||
"Infer and adjust routing annotations before planning."};
|
||||
}
|
||||
if (phase == "plan") {
|
||||
return {"plan", "whetstone_create_workflow",
|
||||
"Create workflow tasks from skeleton annotations."};
|
||||
}
|
||||
if (phase == "route") {
|
||||
return {"route", "whetstone_orchestrate_advance",
|
||||
"Route and prepare ready tasks in a single advance batch."};
|
||||
}
|
||||
if (phase == "execute") {
|
||||
return {"execute", "whetstone_orchestrate_run_deterministic",
|
||||
"Complete deterministic/template tasks automatically."};
|
||||
}
|
||||
if (phase == "assist") {
|
||||
return {"assist", "whetstone_get_blockers",
|
||||
"Fetch blockers, then submit external results as needed."};
|
||||
}
|
||||
if (phase == "review") {
|
||||
return {"review", "whetstone_get_workflow_state",
|
||||
"Resolve review items and re-run deterministic orchestration."};
|
||||
}
|
||||
return {"complete", "whetstone_save_workflow",
|
||||
"Persist completed workflow state."};
|
||||
}
|
||||
|
||||
inline json getSessionSummary(const WorkflowSession& session,
|
||||
const WorkflowState* workflow = nullptr) {
|
||||
ProtocolAction next = getNextAction(session, workflow);
|
||||
json summary = {
|
||||
{"sessionId", session.sessionId},
|
||||
{"projectName", session.projectName},
|
||||
{"currentPhase", session.currentPhase},
|
||||
{"startedAt", session.startedAt},
|
||||
{"commandCount", (int)session.commands.size()},
|
||||
{"commands", session.commands},
|
||||
{"nextAction", next.toJson()}
|
||||
};
|
||||
if (workflow) {
|
||||
summary["workflowPhase"] = workflowPhaseToString(workflow->getPhase());
|
||||
summary["workflowStats"] = workflow->getStats().toJson();
|
||||
summary["inferredPhase"] = detectProtocolPhaseFromWorkflow(*workflow);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
177
editor/tests/step384_test.cpp
Normal file
177
editor/tests/step384_test.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
// Step 384: Workflow session protocol (12 tests)
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "WorkflowProtocol.h"
|
||||
|
||||
static WorkItem makeItem(const std::string& id,
|
||||
const std::string& workerType,
|
||||
const std::string& status) {
|
||||
WorkItem item;
|
||||
item.id = id;
|
||||
item.nodeId = id + "_node";
|
||||
item.nodeName = id;
|
||||
item.nodeType = "Function";
|
||||
item.bufferId = "buf";
|
||||
item.workerType = workerType;
|
||||
item.contextWidth = "local";
|
||||
item.priority = "medium";
|
||||
item.status = status;
|
||||
item.createdAt = workItemTimestamp();
|
||||
return item;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
|
||||
// Test 1: protocol phase ordering transitions are valid
|
||||
{
|
||||
auto phases = workflowProtocolPhases();
|
||||
for (size_t i = 1; i < phases.size(); ++i) {
|
||||
assert(validateTransition(phases[i - 1], phases[i]));
|
||||
}
|
||||
std::cout << "Test 1 PASSED: phase ordering transition validity\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 2: invalid transition is rejected
|
||||
{
|
||||
assert(!validateTransition("init", "route"));
|
||||
assert(!validateTransition("execute", "annotate"));
|
||||
std::cout << "Test 2 PASSED: invalid transitions rejected\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 3: next action suggestions map to phases
|
||||
{
|
||||
WorkflowSession s;
|
||||
s.currentPhase = "plan";
|
||||
auto a = getNextAction(s);
|
||||
assert(a.command == "whetstone_create_workflow");
|
||||
s.currentPhase = "execute";
|
||||
auto b = getNextAction(s);
|
||||
assert(b.command == "whetstone_orchestrate_run_deterministic");
|
||||
s.currentPhase = "complete";
|
||||
auto c = getNextAction(s);
|
||||
assert(c.command == "whetstone_save_workflow");
|
||||
std::cout << "Test 3 PASSED: next-action hints per phase\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 4: session tracks command history
|
||||
{
|
||||
WorkflowSession s;
|
||||
recordWorkflowCommand(s, "whetstone_create_skeleton");
|
||||
recordWorkflowCommand(s, "whetstone_add_skeleton_node");
|
||||
recordWorkflowCommand(s, "whetstone_create_workflow");
|
||||
assert(s.commands.size() == 3);
|
||||
assert(s.commands[0] == "whetstone_create_skeleton");
|
||||
std::cout << "Test 4 PASSED: command history tracked\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 5: summary includes core session fields
|
||||
{
|
||||
WorkflowSession s;
|
||||
s.sessionId = "sess-1";
|
||||
s.projectName = "proj";
|
||||
s.currentPhase = "model";
|
||||
recordWorkflowCommand(s, "whetstone_create_skeleton");
|
||||
auto summary = getSessionSummary(s);
|
||||
assert(summary["sessionId"] == "sess-1");
|
||||
assert(summary["projectName"] == "proj");
|
||||
assert(summary["commandCount"].get<int>() == 1);
|
||||
assert(summary.contains("nextAction"));
|
||||
std::cout << "Test 5 PASSED: summary fields correct\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 6: phase auto-detection from complete workflow
|
||||
{
|
||||
WorkflowState wf("p");
|
||||
WorkItem c = makeItem("c1", "template", WI_COMPLETE);
|
||||
wf.queue.enqueue(c);
|
||||
auto phase = detectProtocolPhaseFromWorkflow(wf);
|
||||
assert(phase == "complete");
|
||||
std::cout << "Test 6 PASSED: complete workflow auto-detected\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 7: phase auto-detection for assist stage
|
||||
{
|
||||
WorkflowState wf("p");
|
||||
wf.queue.enqueue(makeItem("a1", "llm", WI_IN_PROGRESS));
|
||||
auto phase = detectProtocolPhaseFromWorkflow(wf);
|
||||
assert(phase == "assist");
|
||||
std::cout << "Test 7 PASSED: assist phase auto-detected\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 8: session JSON persistence roundtrip
|
||||
{
|
||||
WorkflowSession s;
|
||||
s.sessionId = "sess-2";
|
||||
s.projectName = "alpha";
|
||||
s.currentPhase = "route";
|
||||
s.commands = {"whetstone_create_workflow", "whetstone_orchestrate_advance"};
|
||||
auto j = s.toJson();
|
||||
auto restored = WorkflowSession::fromJson(j);
|
||||
assert(restored.sessionId == s.sessionId);
|
||||
assert(restored.projectName == s.projectName);
|
||||
assert(restored.currentPhase == s.currentPhase);
|
||||
assert(restored.commands.size() == 2);
|
||||
std::cout << "Test 8 PASSED: session persistence roundtrip\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 9: empty project protocol remains in planning path
|
||||
{
|
||||
WorkflowSession s;
|
||||
s.currentPhase = "plan";
|
||||
WorkflowState wf("empty");
|
||||
auto next = getNextAction(s, &wf);
|
||||
assert(next.command == "whetstone_create_workflow");
|
||||
std::cout << "Test 9 PASSED: empty project plan behavior\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 10: unknown phases are rejected as protocol phases
|
||||
{
|
||||
assert(!isWorkflowProtocolPhase("unknown-phase"));
|
||||
assert(!validateTransition("unknown-phase", "init"));
|
||||
std::cout << "Test 10 PASSED: unknown phase guards\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 11: recorded commands advance session phase monotonically
|
||||
{
|
||||
WorkflowSession s;
|
||||
s.currentPhase = "init";
|
||||
recordWorkflowCommand(s, "whetstone_create_skeleton");
|
||||
assert(s.currentPhase == "model");
|
||||
recordWorkflowCommand(s, "whetstone_orchestrate_advance");
|
||||
assert(s.currentPhase == "route");
|
||||
recordWorkflowCommand(s, "whetstone_create_workflow");
|
||||
assert(s.currentPhase == "route"); // no backward transition
|
||||
std::cout << "Test 11 PASSED: monotonic phase advancement by command\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 12: workflow-aware next action can override stale session phase
|
||||
{
|
||||
WorkflowSession s;
|
||||
s.currentPhase = "plan";
|
||||
WorkflowState wf("assist");
|
||||
wf.queue.enqueue(makeItem("x1", "human", WI_IN_PROGRESS));
|
||||
auto next = getNextAction(s, &wf);
|
||||
assert(next.phase == "assist");
|
||||
assert(next.command == "whetstone_get_blockers");
|
||||
std::cout << "Test 12 PASSED: workflow-aware next action override\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
std::cout << "\nResults: " << passed << "/12\n";
|
||||
assert(passed == 12);
|
||||
return 0;
|
||||
}
|
||||
41
progress.md
41
progress.md
@@ -3022,6 +3022,47 @@ MCP orchestration tool loop closure.
|
||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`)
|
||||
- `editor/src/MCPServer.h` (`1652` > `600`)
|
||||
|
||||
### Step 384: Workflow Session Protocol
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implemented a formal MCP workflow-session protocol model with explicit phases,
|
||||
transition validation, command-history tracking, workflow-state phase inference,
|
||||
and next-action guidance for clients.
|
||||
|
||||
**Files created:**
|
||||
- `editor/src/WorkflowProtocol.h` — protocol model:
|
||||
- `WorkflowSession` (session metadata + command history)
|
||||
- `ProtocolAction` (phase + suggested command + reason)
|
||||
- phase helpers: `workflowProtocolPhases`, `validateTransition`,
|
||||
`protocolPhaseForCommand`, `recordWorkflowCommand`
|
||||
- workflow-aware helpers: `detectProtocolPhaseFromWorkflow`,
|
||||
`getNextAction`, `getSessionSummary`
|
||||
- `editor/tests/step384_test.cpp` — 12 tests covering:
|
||||
1. phase-order transition validity
|
||||
2. invalid transition rejection
|
||||
3. per-phase next-action suggestions
|
||||
4. command-history tracking
|
||||
5. session-summary accuracy
|
||||
6. complete-phase auto-detection from workflow
|
||||
7. assist-phase auto-detection from workflow
|
||||
8. session JSON roundtrip persistence
|
||||
9. empty-project plan behavior
|
||||
10. unknown-phase guards
|
||||
11. monotonic phase advancement via command recording
|
||||
12. workflow-aware next-action override
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` — `step384_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `step384_test` — PASS (12/12) new step coverage
|
||||
- `step383_test` — PASS (8/8) regression coverage
|
||||
- `step382_test` — PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/WorkflowProtocol.h` within header-size limit (`185` <= `600`)
|
||||
- `editor/tests/step384_test.cpp` within test-file size guidance (`177` lines)
|
||||
|
||||
# Roadmap Planning — Sprints 12-25+
|
||||
|
||||
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
||||
|
||||
Reference in New Issue
Block a user