2026-02-11 01:34:27 +00:00
|
|
|
#pragma once
|
|
|
|
|
// Step 245: Headless EditorState — agent API surface without ImGui/SDL.
|
|
|
|
|
//
|
|
|
|
|
// Provides the same method interface that AgentRPCHandler.h expects,
|
|
|
|
|
// but with zero GUI dependencies. Uses std::chrono for timestamps,
|
|
|
|
|
// stderr for logging, and holds its own lightweight buffer map.
|
|
|
|
|
|
|
|
|
|
// --- Core AST and pipeline (no ImGui) ---
|
|
|
|
|
#include "ast/ASTNode.h"
|
|
|
|
|
#include "ast/Serialization.h"
|
|
|
|
|
#include "ast/Generator.h"
|
|
|
|
|
#include "ast/Annotation.h"
|
|
|
|
|
#include "ASTUtils.h"
|
|
|
|
|
#include "Pipeline.h"
|
|
|
|
|
#include "ASTMutationAPI.h"
|
|
|
|
|
#include "BatchMutationAPI.h"
|
|
|
|
|
#include "ContextAPI.h"
|
|
|
|
|
#include "CrossLanguageProjector.h"
|
|
|
|
|
#include "MemoryStrategyInference.h"
|
|
|
|
|
#include "AgentAnnotationAssistant.h"
|
|
|
|
|
#include "AgentPermissionPolicy.h"
|
|
|
|
|
#include "AgentCodeGen.h"
|
|
|
|
|
#include "AgentLibraryPolicy.h"
|
|
|
|
|
#include "WorkflowRecorder.h"
|
2026-02-11 05:59:02 +00:00
|
|
|
#include "FileOperations.h"
|
2026-02-11 06:29:17 +00:00
|
|
|
#include "CompactAST.h"
|
2026-02-11 15:36:24 +00:00
|
|
|
#include "StructuredDiagnostics.h"
|
2026-02-11 15:54:01 +00:00
|
|
|
#include "ResponseBudget.h"
|
2026-02-11 01:34:27 +00:00
|
|
|
#include "Orchestrator.h"
|
|
|
|
|
#include "IncrementalOptimizer.h"
|
|
|
|
|
#include "TextASTSync.h"
|
|
|
|
|
#include "BufferManager.h"
|
|
|
|
|
#include "EditorModePolicy.h"
|
|
|
|
|
#include "PrimitivesRegistry.h"
|
|
|
|
|
#include "SemanticTags.h"
|
2026-02-11 19:43:53 +00:00
|
|
|
#include "ProjectState.h"
|
Steps 266-268: Phase 10a — semantic annotation core + sidecar persistence (32/32 tests)
Step 266: 5 semantic annotation AST types (Intent, Complexity, Risk,
Contract, SemanticTag) with JSON roundtrip and compact AST integration.
Step 267: Sidecar AST persistence (.whetstone/<file>.ast.json) with
save/load/list RPC methods, MCP tools, and permission enforcement.
Step 268: Phase 10a integration tests — multi-file sidecar workflow,
all 5 types in compact view, idempotent roundtrip, source isolation.
Also includes Sprint 10 plan, GUI/installer polish, and Emacs integration fixes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:01:56 +00:00
|
|
|
#include "SidecarPersistence.h"
|
Steps 309-319: Sprint 11 Phases 11d-e — Kotlin/C# languages + workflow annotation foundation
Phase 11d (Steps 309-313): Kotlin + C# parsers and generators
- KotlinParser (regex-based): fun, suspend fun, class, data class, val/var
- KotlinGenerator: idiomatic Kotlin output with type mappings
- CSharpParser (regex-based): methods, async, class, interface
- CSharpGenerator: Allman braces, foreach, Task async, LINQ types
- Pipeline integration for both languages, 10 parsers + 10 generators
Phase 11e (Steps 314-319): Workflow annotation foundation
- AnnotationInference: generalized multi-subject inference engine
- Subject 9 routing annotations: ContextWidth, Review, Ambiguity,
Automatability, Priority, ImplementationStatus
- SkeletonAST: project specification before code exists
- Architect tooling: createSkeleton, addSkeletonNode, getProjectModel,
inferAnnotations RPCs + 4 MCP tools (42+ total)
- Inference-to-routing bridge: complexity→ambiguity, getter→deterministic
- TrainingDataExporter + TrainingDataGenerator scaffolding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:50:06 -07:00
|
|
|
#include "AnnotationInference.h"
|
|
|
|
|
#include "SkeletonAST.h"
|
2026-02-15 17:00:52 -07:00
|
|
|
#include "WorkflowState.h"
|
|
|
|
|
#include "WorkflowPersistence.h"
|
2026-02-15 19:20:00 -07:00
|
|
|
#include "RoutingEngine.h"
|
|
|
|
|
#include "WorkerRegistry.h"
|
|
|
|
|
#include "ContextAssembler.h"
|
2026-02-15 19:23:06 -07:00
|
|
|
#include "ReviewGate.h"
|
2026-02-16 12:47:58 -07:00
|
|
|
#include "WorkflowProgress.h"
|
2026-02-11 01:34:27 +00:00
|
|
|
|
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <map>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <chrono>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
// Headless agent state — no WebSocket, no AgentMarketplace (ImGui dep)
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
struct HeadlessAgentState {
|
|
|
|
|
std::map<std::string, AgentRole> roles;
|
|
|
|
|
AgentRole defaultRole = AgentRole::Linter;
|
|
|
|
|
WorkflowRecorder workflowRecorder;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
// Headless library state — no ImGui panels
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
struct HeadlessLibraryState {
|
|
|
|
|
PrimitivesRegistry primitives;
|
|
|
|
|
SemanticTags semanticTags;
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-12 01:27:23 +00:00
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
// HeadlessUndoStack — state-based undo/redo for headless buffers
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
struct HeadlessUndoState {
|
|
|
|
|
std::string text;
|
|
|
|
|
json astJson;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class HeadlessUndoStack {
|
|
|
|
|
public:
|
|
|
|
|
// Record current state as a checkpoint
|
|
|
|
|
void record(const std::string& text, Module* ast) {
|
|
|
|
|
// Truncate any redo states
|
|
|
|
|
if (position_ + 1 < (int)states_.size())
|
|
|
|
|
states_.resize(position_ + 1);
|
|
|
|
|
HeadlessUndoState s;
|
|
|
|
|
s.text = text;
|
|
|
|
|
if (ast) s.astJson = toJson(ast);
|
|
|
|
|
states_.push_back(std::move(s));
|
|
|
|
|
position_ = (int)states_.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool canUndo() const { return position_ > 0; }
|
|
|
|
|
bool canRedo() const { return position_ + 1 < (int)states_.size(); }
|
|
|
|
|
|
|
|
|
|
const HeadlessUndoState& undo() { return states_[--position_]; }
|
|
|
|
|
const HeadlessUndoState& redo() { return states_[++position_]; }
|
|
|
|
|
|
|
|
|
|
int undoDepth() const { return position_; }
|
|
|
|
|
int redoDepth() const {
|
|
|
|
|
return (int)states_.size() - position_ - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
std::vector<HeadlessUndoState> states_;
|
|
|
|
|
int position_ = -1;
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-11 01:34:27 +00:00
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
// HeadlessBufferState — lightweight buffer without ImGui widgets
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
struct HeadlessBufferState {
|
|
|
|
|
TextASTSync sync;
|
|
|
|
|
Orchestrator orchestrator;
|
|
|
|
|
IncrementalOptimizer incrementalOptimizer;
|
2026-02-11 06:29:17 +00:00
|
|
|
ASTVersionTracker versionTracker;
|
2026-02-11 15:45:31 +00:00
|
|
|
DiagnosticVersionTracker diagTracker;
|
2026-02-12 01:27:23 +00:00
|
|
|
HeadlessUndoStack undoStack;
|
2026-02-11 01:34:27 +00:00
|
|
|
std::string language = "python";
|
|
|
|
|
std::string path = "(untitled)";
|
|
|
|
|
std::string editBuf;
|
|
|
|
|
BufferManager::BufferMode bufferMode =
|
|
|
|
|
BufferManager::BufferMode::Structured;
|
|
|
|
|
bool orchestratorDirty = true;
|
|
|
|
|
bool modified = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
// HeadlessEditorState — agent-facing state, no GUI
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
struct HeadlessEditorState {
|
|
|
|
|
std::map<std::string, std::unique_ptr<HeadlessBufferState>>
|
|
|
|
|
bufferStates;
|
|
|
|
|
HeadlessBufferState* activeBuffer = nullptr;
|
|
|
|
|
HeadlessAgentState agent;
|
|
|
|
|
HeadlessLibraryState library;
|
2026-02-11 19:43:53 +00:00
|
|
|
ProjectState project;
|
2026-02-11 01:34:27 +00:00
|
|
|
std::string workspaceRoot;
|
|
|
|
|
std::string defaultLanguage = "python";
|
|
|
|
|
bool verbose = false;
|
2026-02-15 17:00:52 -07:00
|
|
|
std::optional<WorkflowState> workflow;
|
2026-02-16 12:47:58 -07:00
|
|
|
std::optional<WorkflowProgress> workflowProgress;
|
2026-02-15 19:20:00 -07:00
|
|
|
RoutingEngine routingEngine;
|
|
|
|
|
WorkerRegistry workerRegistry = WorkerRegistry::getDefaultRegistry();
|
|
|
|
|
ContextAssembler contextAssembler;
|
2026-02-15 19:23:06 -07:00
|
|
|
ReviewGate reviewGate;
|
|
|
|
|
ReviewPolicy reviewPolicy = ReviewPolicy::getDefault();
|
2026-02-11 01:34:27 +00:00
|
|
|
|
|
|
|
|
// --- Buffer access ---
|
|
|
|
|
|
|
|
|
|
HeadlessBufferState* active() { return activeBuffer; }
|
|
|
|
|
|
|
|
|
|
bool isStructured() const {
|
|
|
|
|
return activeBuffer &&
|
|
|
|
|
allowStructuredFeatures(activeBuffer->bufferMode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Module* activeAST() {
|
|
|
|
|
if (!activeBuffer) return nullptr;
|
|
|
|
|
if (!allowStructuredFeatures(activeBuffer->bufferMode))
|
|
|
|
|
return nullptr;
|
|
|
|
|
return activeBuffer->sync.getAST();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Module* mutationAST() {
|
|
|
|
|
if (!activeBuffer) return nullptr;
|
|
|
|
|
if (!allowStructuredFeatures(activeBuffer->bufferMode))
|
|
|
|
|
return nullptr;
|
|
|
|
|
if (activeBuffer->orchestratorDirty ||
|
|
|
|
|
!activeBuffer->orchestrator.getAST()) {
|
|
|
|
|
syncOrchestratorFromActive();
|
|
|
|
|
}
|
|
|
|
|
return activeBuffer->orchestrator.getAST();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void syncOrchestratorFromActive() {
|
|
|
|
|
if (!activeBuffer) return;
|
|
|
|
|
Module* ast = activeBuffer->sync.getAST();
|
|
|
|
|
if (ast) {
|
|
|
|
|
activeBuffer->orchestrator.setAST(cloneModule(ast));
|
|
|
|
|
activeBuffer->orchestratorDirty = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void applyOrchestratorToActive() {
|
|
|
|
|
if (!activeBuffer) return;
|
|
|
|
|
if (!isStructured()) return;
|
|
|
|
|
Module* ast = activeBuffer->orchestrator.getAST();
|
|
|
|
|
if (!ast) return;
|
|
|
|
|
activeBuffer->sync.setAST(cloneModule(ast));
|
|
|
|
|
activeBuffer->incrementalOptimizer.setRoot(
|
|
|
|
|
activeBuffer->sync.getAST());
|
|
|
|
|
Pipeline pipeline;
|
|
|
|
|
std::string code = pipeline.generate(
|
|
|
|
|
activeBuffer->sync.getAST(), activeBuffer->language);
|
|
|
|
|
activeBuffer->editBuf = code;
|
|
|
|
|
activeBuffer->orchestratorDirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Agent role management ---
|
|
|
|
|
|
|
|
|
|
AgentRole getAgentRole(const std::string& sessionId) const {
|
|
|
|
|
auto it = agent.roles.find(sessionId);
|
|
|
|
|
if (it != agent.roles.end()) return it->second;
|
|
|
|
|
return agent.defaultRole;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setAgentRole(const std::string& sessionId, AgentRole role) {
|
|
|
|
|
agent.roles[sessionId] = role;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string agentActorLabel(const std::string& sessionId) const {
|
|
|
|
|
AgentRole role = getAgentRole(sessionId);
|
|
|
|
|
return std::string(AgentPermissionPolicy::roleLabel(role)) +
|
|
|
|
|
":" + sessionId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Diagnostics ---
|
|
|
|
|
|
|
|
|
|
json buildDiagnosticsJson() const {
|
|
|
|
|
return json::array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
json buildSessionMetadata(bool /*autoRecording*/) const {
|
|
|
|
|
return {
|
|
|
|
|
{"mode", "headless"},
|
|
|
|
|
{"workspace", workspaceRoot},
|
|
|
|
|
{"language", defaultLanguage},
|
|
|
|
|
{"timestamp",
|
|
|
|
|
std::chrono::duration_cast<std::chrono::seconds>(
|
|
|
|
|
std::chrono::system_clock::now().time_since_epoch())
|
|
|
|
|
.count()}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Buffer operations ---
|
|
|
|
|
|
|
|
|
|
HeadlessBufferState* openBuffer(const std::string& filePath,
|
|
|
|
|
const std::string& content,
|
|
|
|
|
const std::string& lang) {
|
|
|
|
|
auto buf = std::make_unique<HeadlessBufferState>();
|
|
|
|
|
buf->path = filePath;
|
|
|
|
|
buf->language = lang.empty() ? defaultLanguage : lang;
|
|
|
|
|
buf->editBuf = content;
|
|
|
|
|
buf->bufferMode = BufferManager::BufferMode::Structured;
|
|
|
|
|
if (!content.empty()) {
|
|
|
|
|
Pipeline pipeline;
|
|
|
|
|
std::vector<ParseDiagnostic> diags;
|
|
|
|
|
auto mod = pipeline.parse(content, buf->language, diags);
|
|
|
|
|
if (mod) {
|
|
|
|
|
buf->sync.setAST(std::move(mod));
|
|
|
|
|
buf->orchestratorDirty = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
auto mod = std::make_unique<Module>(
|
|
|
|
|
"root", filePath, buf->language);
|
|
|
|
|
buf->sync.setAST(std::move(mod));
|
|
|
|
|
buf->orchestratorDirty = true;
|
|
|
|
|
}
|
|
|
|
|
HeadlessBufferState* raw = buf.get();
|
2026-02-12 01:27:23 +00:00
|
|
|
// Record initial state for undo
|
|
|
|
|
raw->undoStack.record(raw->editBuf, raw->sync.getAST());
|
2026-02-11 01:34:27 +00:00
|
|
|
bufferStates[filePath] = std::move(buf);
|
|
|
|
|
if (!activeBuffer) activeBuffer = raw;
|
|
|
|
|
return raw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool setActiveBuffer(const std::string& path) {
|
|
|
|
|
auto it = bufferStates.find(path);
|
|
|
|
|
if (it == bufferStates.end()) return false;
|
|
|
|
|
activeBuffer = it->second.get();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void closeBuffer(const std::string& path) {
|
|
|
|
|
auto it = bufferStates.find(path);
|
|
|
|
|
if (it == bufferStates.end()) return;
|
|
|
|
|
if (activeBuffer == it->second.get())
|
|
|
|
|
activeBuffer = nullptr;
|
|
|
|
|
bufferStates.erase(it);
|
|
|
|
|
if (!activeBuffer && !bufferStates.empty())
|
|
|
|
|
activeBuffer = bufferStates.begin()->second.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- RPC entry point ---
|
|
|
|
|
json processAgentRequest(const json& request,
|
|
|
|
|
const std::string& sessionId);
|
|
|
|
|
|
|
|
|
|
// --- Logging ---
|
|
|
|
|
void log(const std::string& msg) const {
|
|
|
|
|
if (verbose)
|
|
|
|
|
std::cerr << "[whetstone-headless] " << msg << "\n";
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Include the headless RPC handler and wire up processAgentRequest
|
|
|
|
|
#include "HeadlessAgentRPCHandler.h"
|
|
|
|
|
|
|
|
|
|
inline json HeadlessEditorState::processAgentRequest(
|
|
|
|
|
const json& request, const std::string& sessionId) {
|
|
|
|
|
return handleHeadlessAgentRequest(*this, request, sessionId);
|
|
|
|
|
}
|