use std::io::{BufRead, Write};
use serde_json::{json, Value};
use crate::shim::config;
const PROTOCOL_VERSION: &str = "2025-06-18";
const SERVER_NAME: &str = "falsegreen";
const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
const INSTRUCTIONS: &str = r#"This server owns task completion status.
Two workflows are supported:
## A. Dynamic workflow (no pre-existing task)
When no task is active, the agent can self-provision the full
completion-authority pipeline:
1. falsegreen_create_task(workspace, goal) — register a new task
2. falsegreen_save_contract_draft(task_id, contract) — draft acceptance criteria
3. falsegreen_validate_contract(task_id) — validate the draft
4. falsegreen_freeze_contract(task_id) — freeze the contract
5. falsegreen_get_assignment() — compact immutable requirements
6. Implement the software
7. falsegreen_check_completion() — independent completion decision
## B. Pre-frozen task workflow (host sets active task)
When a session already has one active frozen task:
1. falsegreen_get_assignment() — compact immutable requirements
2. Implement the software
3. falsegreen_check_completion() — independent completion decision
Do not invent or retype task IDs when the session already has an active task.
Optional task_id arguments exist only for multi-task/debug hosts.
Claim completion only when falsegreen_check_completion (or falsegreen_get_status)
returns status=accepted."#;
pub fn run() -> ! {
let stdin = std::io::stdin();
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let mut line = String::new();
loop {
line.clear();
match stdin.lock().read_line(&mut line) {
Ok(0) => break, Ok(_) => {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let request: Value = match serde_json::from_str(trimmed) {
Ok(v) => v,
Err(_) => continue, };
let has_id = request.get("id").is_some();
let method = request
.get("method")
.and_then(|m| m.as_str())
.unwrap_or("");
let response = handle_request(&request, method);
if has_id {
if let Some(resp) = response {
let serialized = serde_json::to_string(&resp).unwrap_or_default();
let _ = writeln!(stdout, "{}", serialized);
let _ = stdout.flush();
}
}
}
Err(_) => break,
}
}
std::process::exit(0);
}
fn handle_request(request: &Value, method: &str) -> Option<Value> {
let id = request.get("id").cloned();
let params = request
.get("params")
.cloned()
.unwrap_or(Value::Null);
match method {
"initialize" => Some(json!({
"jsonrpc": "2.0",
"id": id,
"result": {
"protocolVersion": PROTOCOL_VERSION,
"capabilities": {
"experimental": {},
"prompts": {"listChanged": false},
"resources": {"subscribe": false, "listChanged": false},
"tools": {"listChanged": false},
},
"serverInfo": {
"name": SERVER_NAME,
"version": SERVER_VERSION,
},
"instructions": INSTRUCTIONS,
}
})),
"notifications/initialized" => None,
"tools/list" => Some(json!({
"jsonrpc": "2.0",
"id": id,
"result": {
"tools": tool_definitions(),
}
})),
"tools/call" => {
let tool_name = params
.get("name")
.and_then(|n| n.as_str())
.unwrap_or("");
let arguments = params
.get("arguments")
.cloned()
.unwrap_or(json!({}));
let result = forward_tool_call(tool_name, &arguments);
Some(json!({
"jsonrpc": "2.0",
"id": id,
"result": result,
}))
}
"prompts/list" => Some(json!({
"jsonrpc": "2.0",
"id": id,
"result": {"prompts": []}
})),
"resources/list" => Some(json!({
"jsonrpc": "2.0",
"id": id,
"result": {"resources": []}
})),
_ => Some(json!({
"jsonrpc": "2.0",
"id": id,
"error": {
"code": -32601,
"message": format!("method not found: {}", method),
}
})),
}
}
fn forward_tool_call(tool_name: &str, arguments: &Value) -> Value {
let url = config::api_url();
let key = match config::load_token() {
Some(t) => t,
None => {
return error_result("not authenticated — run `falsegreen login` first");
}
};
let rpc_request = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": tool_name,
"arguments": arguments,
}
});
let body = serde_json::to_string(&rpc_request).unwrap_or_else(|_| "{}".to_string());
let result = std::process::Command::new("curl")
.arg("-s")
.arg("-X")
.arg("POST")
.arg(&url)
.arg("-H")
.arg("Content-Type: application/json")
.arg("-H")
.arg(format!("X-API-KEY: {}", key))
.arg("-d")
.arg(&body)
.arg("--max-time")
.arg("120")
.output();
match result {
Ok(output) if output.status.success() => {
let response_body = String::from_utf8_lossy(&output.stdout);
match serde_json::from_str::<Value>(&response_body) {
Ok(v) => {
if let Some(result) = v.get("result") {
result.clone()
} else if let Some(error) = v.get("error") {
let msg = error.get("message").and_then(|m| m.as_str()).unwrap_or("unknown error");
error_result(msg)
} else {
v
}
}
Err(_) => error_result(&format!(
"invalid response from server: {}",
response_body
)),
}
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
error_result(&format!("request failed: {}", stderr))
}
Err(e) => error_result(&format!("failed to send request: {}", e)),
}
}
fn error_result(message: &str) -> Value {
json!({
"content": [{"type": "text", "text": message}],
"isError": true,
})
}
fn tool_definitions() -> Vec<Value> {
vec![
json!({
"name": "falsegreen_create_task",
"description": "Register a new task.",
"inputSchema": {
"type": "object",
"properties": {
"workspace": {"type": "string"},
"goal": {"type": "string"},
"title": {"type": ["string", "null"], "default": null},
},
"required": ["workspace", "goal"],
},
}),
json!({
"name": "falsegreen_save_contract_draft",
"description": "Draft acceptance criteria for a task.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": "string"},
"contract": {"type": "object"},
},
"required": ["task_id", "contract"],
},
}),
json!({
"name": "falsegreen_validate_contract",
"description": "Validate the current contract draft.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_freeze_contract",
"description": "Freeze the contract (locks criteria + verifier).",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_get_assignment",
"description": "Get compact immutable requirements for the active task.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_check_completion",
"description": "Run the independent verifier and get a completion decision.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_get_status",
"description": "Get current task status without running verification.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_get_repair_feedback",
"description": "Get detailed failure feedback from the last verification run.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_get_contract_schema",
"description": "Get the JSON schema for contract documents.",
"inputSchema": {"type": "object", "properties": {}},
}),
json!({
"name": "falsegreen_get_contract",
"description": "Get the frozen contract for a task.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
"frozen": {"type": "boolean", "default": true},
},
},
}),
json!({
"name": "falsegreen_begin_implementation",
"description": "Transition task to implementing state.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_verify",
"description": "Run verification explicitly.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_consume_repair_cycle",
"description": "Consume a repair cycle.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
json!({
"name": "falsegreen_mark_unresolved",
"description": "Mark the task as unresolved with a reason.",
"inputSchema": {
"type": "object",
"properties": {
"reason": {"type": "string"},
"task_id": {"type": ["string", "null"], "default": null},
},
"required": ["reason"],
},
}),
json!({
"name": "falsegreen_get_audit_summary",
"description": "Get audit summary for a task.",
"inputSchema": {
"type": "object",
"properties": {
"task_id": {"type": ["string", "null"], "default": null},
},
},
}),
]
}