omk 0.5.0

A Rust runtime for Kimi CLI. Turns prompts into proof-backed engineering runs with gates, worktrees, and replay.
Documentation
use crate::runtime::events::{
    Event, EventKind, GateResultPayload, ProofWrittenPayload, RunStartedPayload,
    TaskCompletedPayload, TaskGraphMutationPayload, WorkerStartedPayload,
};

use super::events::{ChildGoalEvent, PlanNode, PlanNodeStatus};

pub fn to_child_event(envelope: &Event) -> Option<ChildGoalEvent> {
    match envelope.kind {
        EventKind::RunStarted => {
            let goal_id = envelope.run_id.0.clone();
            let mut plan = Vec::new();
            if let Some(ref payload) = envelope.payload {
                if let Ok(p) = serde_json::from_value::<RunStartedPayload>(payload.clone()) {
                    plan.push(p.description);
                }
            }
            Some(ChildGoalEvent::Created { goal_id, plan })
        }
        EventKind::TaskGraphMutated => {
            let total_tasks = envelope
                .payload
                .as_ref()
                .and_then(|p| serde_json::from_value::<TaskGraphMutationPayload>(p.clone()).ok())
                .map(|p| p.total_tasks_after)
                .unwrap_or(0);

            let nodes: Vec<PlanNode> = (0..total_tasks)
                .map(|i| PlanNode {
                    id: format!("task-{i}"),
                    label: "Task".to_string(),
                    status: PlanNodeStatus::Pending,
                })
                .collect();

            Some(ChildGoalEvent::PlanUpdated { revision: 0, nodes })
        }
        EventKind::WorkerStarted => {
            let (worker_id, task) = envelope
                .payload
                .as_ref()
                .and_then(|p| serde_json::from_value::<WorkerStartedPayload>(p.clone()).ok())
                .map(|p| (p.worker_id.0, p.role))
                .unwrap_or_else(|| {
                    (
                        envelope.actor.clone().unwrap_or_default(),
                        envelope.actor.clone().unwrap_or_default(),
                    )
                });
            Some(ChildGoalEvent::WorkerStarted { worker_id, task })
        }
        EventKind::TaskOutput => {
            let worker_id = envelope.actor.clone().unwrap_or_default();
            let msg = envelope
                .payload
                .as_ref()
                .and_then(|p| p.get("message"))
                .and_then(|v| v.as_str())
                .unwrap_or("")
                .to_string();
            Some(ChildGoalEvent::WorkerProgress { worker_id, msg })
        }
        EventKind::TaskCompleted => {
            let worker_id = envelope
                .payload
                .as_ref()
                .and_then(|p| serde_json::from_value::<TaskCompletedPayload>(p.clone()).ok())
                .map(|p| p.worker_id.0)
                .unwrap_or_else(|| envelope.actor.clone().unwrap_or_default());
            Some(ChildGoalEvent::WorkerCompleted {
                worker_id,
                files: 0,
                ok: true,
            })
        }
        EventKind::TaskFailed => {
            let worker_id = envelope.actor.clone().unwrap_or_default();
            Some(ChildGoalEvent::WorkerCompleted {
                worker_id,
                files: 0,
                ok: false,
            })
        }
        EventKind::GatePassed => {
            let gate = envelope
                .payload
                .as_ref()
                .and_then(|p| serde_json::from_value::<GateResultPayload>(p.clone()).ok())
                .map(|p| p.name)
                .unwrap_or_else(|| "unknown".to_string());
            Some(ChildGoalEvent::GateTransition {
                gate,
                from: "unknown".to_string(),
                to: "passed".to_string(),
            })
        }
        EventKind::GateFailed => {
            let gate = envelope
                .payload
                .as_ref()
                .and_then(|p| serde_json::from_value::<GateResultPayload>(p.clone()).ok())
                .map(|p| p.name)
                .unwrap_or_else(|| "unknown".to_string());
            Some(ChildGoalEvent::GateTransition {
                gate,
                from: "unknown".to_string(),
                to: "failed".to_string(),
            })
        }
        EventKind::ProofWritten => {
            let (status, path) = envelope
                .payload
                .as_ref()
                .and_then(|p| serde_json::from_value::<ProofWrittenPayload>(p.clone()).ok())
                .map(|p| (p.status, p.proof_path))
                .unwrap_or_default();
            if status == "ready" {
                Some(ChildGoalEvent::ProofReady { path })
            } else {
                None
            }
        }
        EventKind::RunFailed => {
            let reason = envelope
                .payload
                .as_ref()
                .and_then(|p| p.get("message"))
                .and_then(|v| v.as_str())
                .unwrap_or("goal failed")
                .to_string();
            Some(ChildGoalEvent::Failed { reason })
        }
        EventKind::ManualInterrupt => Some(ChildGoalEvent::Cancelled),
        _ => None,
    }
}