P1 backend: chat persistence, tc-llm providers, runtime loop, gateway SSE

- tc-db: sessions/messages/steps/runs/run_events repos (atomic seq assignment,
  history with ordered step traces, journal replay-from-offset); migration 0003
- tc-llm: provider-neutral ChatRequest/LlmEvent; ScriptedProvider (scenario
  TOML, word-level deltas, multi-turn tool legs — ships in production for
  e2e/air-gap smoke), AnthropicProvider (Messages SSE), OpenAiCompatProvider
  (vLLM/Ollama/llama.cpp); opt-in live tests via TC_LIVE_LLM=1
- tc-runtime: run loop with persist-before-emit event journal, real built-in
  clock.now tool, step rows on the reply message, tool-error resilience,
  broadcast channels for live attach
- tc-api: agent CRUD + settings/full (tenant-isolated, RBAC'd, audited),
  sessions create/list/history?tools=true, POST /api/gateway SSE with
  monotonic ids and exact resumeFrom journal replay (tested equal to live)
- teamclaw-server: config-driven provider factory

83 Rust tests green, all against real Postgres / real TCP.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-09 23:16:06 -05:00
co-authored by Claude Fable 5
parent fc173f170d
commit 32008c9ef0
58 changed files with 4378 additions and 11 deletions
+53
View File
@@ -0,0 +1,53 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
/// One gateway event with its journal sequence number. The wire contract
/// with the frontend (`frontend/src/lib/gateway/events.ts` mirrors it).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunEventEnvelope {
pub seq: i64,
#[serde(flatten)]
pub event: RunEventBody,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RunEventBody {
RunStarted {
run_id: Uuid,
},
TextDelta {
delta: String,
},
StepStarted {
step_seq: i32,
tool: String,
input: Value,
},
StepFinished {
step_seq: i32,
status: String,
output: Value,
},
RunCompleted {
message_id: String,
},
Error {
message: String,
},
}
impl RunEventBody {
/// Stable name stored in `run_events.event_type`.
pub fn type_name(&self) -> &'static str {
match self {
RunEventBody::RunStarted { .. } => "run_started",
RunEventBody::TextDelta { .. } => "text_delta",
RunEventBody::StepStarted { .. } => "step_started",
RunEventBody::StepFinished { .. } => "step_finished",
RunEventBody::RunCompleted { .. } => "run_completed",
RunEventBody::Error { .. } => "error",
}
}
}