feat(code): CodeSession + agent loop + Matrix room bridge
phase 2 server core: - CodeSession: create/resume sessions, Matrix room per project, Mistral conversation lifecycle, tool dispatch loop - agent loop: user input → Mistral → tool calls → route (client via gRPC / server via ToolRegistry) → collect results → respond - Matrix bridge: all messages posted to project room, accessible from any Matrix client - code_sessions SQLite table (Postgres-compatible schema) - coding mode context injection (project path, git info, prompt.md)
This commit is contained in:
@@ -83,6 +83,19 @@ impl Store {
|
||||
PRIMARY KEY (localpart, service)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS code_sessions (
|
||||
session_id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
room_id TEXT NOT NULL,
|
||||
conversation_id TEXT,
|
||||
project_path TEXT NOT NULL,
|
||||
project_name TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
last_active TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS research_sessions (
|
||||
session_id TEXT PRIMARY KEY,
|
||||
room_id TEXT NOT NULL,
|
||||
@@ -244,6 +257,120 @@ impl Store {
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Code Sessions (sunbeam code)
|
||||
// =========================================================================
|
||||
|
||||
/// Find an active code session for a user + project.
|
||||
pub fn find_code_session(
|
||||
&self,
|
||||
user_id: &str,
|
||||
project_name: &str,
|
||||
) -> Option<(String, String, String)> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
conn.query_row(
|
||||
"SELECT session_id, room_id, conversation_id FROM code_sessions
|
||||
WHERE user_id = ?1 AND project_name = ?2 AND status = 'active'
|
||||
ORDER BY last_active DESC LIMIT 1",
|
||||
params![user_id, project_name],
|
||||
|row| Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
)),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Create a new code session.
|
||||
pub fn create_code_session(
|
||||
&self,
|
||||
session_id: &str,
|
||||
user_id: &str,
|
||||
room_id: &str,
|
||||
project_path: &str,
|
||||
project_name: &str,
|
||||
model: &str,
|
||||
) {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
if let Err(e) = conn.execute(
|
||||
"INSERT INTO code_sessions (session_id, user_id, room_id, project_path, project_name, model)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
params![session_id, user_id, room_id, project_path, project_name, model],
|
||||
) {
|
||||
warn!("Failed to create code session: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the conversation_id for a code session.
|
||||
pub fn set_code_session_conversation(
|
||||
&self,
|
||||
session_id: &str,
|
||||
conversation_id: &str,
|
||||
) {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
if let Err(e) = conn.execute(
|
||||
"UPDATE code_sessions SET conversation_id = ?1, last_active = datetime('now')
|
||||
WHERE session_id = ?2",
|
||||
params![conversation_id, session_id],
|
||||
) {
|
||||
warn!("Failed to update code session conversation: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Touch the last_active timestamp.
|
||||
pub fn touch_code_session(&self, session_id: &str) {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
if let Err(e) = conn.execute(
|
||||
"UPDATE code_sessions SET last_active = datetime('now') WHERE session_id = ?1",
|
||||
params![session_id],
|
||||
) {
|
||||
warn!("Failed to touch code session: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// End a code session.
|
||||
pub fn end_code_session(&self, session_id: &str) {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
if let Err(e) = conn.execute(
|
||||
"UPDATE code_sessions SET status = 'ended' WHERE session_id = ?1",
|
||||
params![session_id],
|
||||
) {
|
||||
warn!("Failed to end code session: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a room is a code session room.
|
||||
pub fn is_code_room(&self, room_id: &str) -> bool {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
conn.query_row(
|
||||
"SELECT 1 FROM code_sessions WHERE room_id = ?1 AND status = 'active' LIMIT 1",
|
||||
params![room_id],
|
||||
|_| Ok(()),
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Get project context for a code room.
|
||||
pub fn get_code_room_context(
|
||||
&self,
|
||||
room_id: &str,
|
||||
) -> Option<(String, String, String)> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
conn.query_row(
|
||||
"SELECT project_name, project_path, model FROM code_sessions
|
||||
WHERE room_id = ?1 AND status = 'active'
|
||||
ORDER BY last_active DESC LIMIT 1",
|
||||
params![room_id],
|
||||
|row| Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
)),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Service Users (OIDC → service username mapping)
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user