mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-09 02:43:29 +08:00
feat: add ecc2 session messaging primitives
This commit is contained in:
166
ecc2/src/main.rs
166
ecc2/src/main.rs
@@ -50,6 +50,11 @@ enum Commands {
|
||||
/// Session ID or alias
|
||||
session_id: String,
|
||||
},
|
||||
/// Send or inspect inter-session messages
|
||||
Messages {
|
||||
#[command(subcommand)]
|
||||
command: MessageCommands,
|
||||
},
|
||||
/// Run as background daemon
|
||||
Daemon,
|
||||
#[command(hide = true)]
|
||||
@@ -65,6 +70,40 @@ enum Commands {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
enum MessageCommands {
|
||||
/// Send a structured message between sessions
|
||||
Send {
|
||||
#[arg(long)]
|
||||
from: String,
|
||||
#[arg(long)]
|
||||
to: String,
|
||||
#[arg(long, value_enum)]
|
||||
kind: MessageKindArg,
|
||||
#[arg(long)]
|
||||
text: String,
|
||||
#[arg(long)]
|
||||
context: Option<String>,
|
||||
#[arg(long)]
|
||||
file: Vec<String>,
|
||||
},
|
||||
/// Show recent messages for a session
|
||||
Inbox {
|
||||
session_id: String,
|
||||
#[arg(long, default_value_t = 10)]
|
||||
limit: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
||||
enum MessageKindArg {
|
||||
Handoff,
|
||||
Query,
|
||||
Response,
|
||||
Completed,
|
||||
Conflict,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
@@ -108,6 +147,49 @@ async fn main() -> Result<()> {
|
||||
let resumed_id = session::manager::resume_session(&db, &cfg, &session_id).await?;
|
||||
println!("Session resumed: {resumed_id}");
|
||||
}
|
||||
Some(Commands::Messages { command }) => match command {
|
||||
MessageCommands::Send {
|
||||
from,
|
||||
to,
|
||||
kind,
|
||||
text,
|
||||
context,
|
||||
file,
|
||||
} => {
|
||||
let from = resolve_session_id(&db, &from)?;
|
||||
let to = resolve_session_id(&db, &to)?;
|
||||
let message = build_message(kind, text, context, file)?;
|
||||
comms::send(&db, &from, &to, &message)?;
|
||||
println!("Message sent: {} -> {}", short_session(&from), short_session(&to));
|
||||
}
|
||||
MessageCommands::Inbox { session_id, limit } => {
|
||||
let session_id = resolve_session_id(&db, &session_id)?;
|
||||
let messages = db.list_messages_for_session(&session_id, limit)?;
|
||||
let unread_before = db
|
||||
.unread_message_counts()?
|
||||
.get(&session_id)
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
if unread_before > 0 {
|
||||
let _ = db.mark_messages_read(&session_id)?;
|
||||
}
|
||||
|
||||
if messages.is_empty() {
|
||||
println!("No messages for {}", short_session(&session_id));
|
||||
} else {
|
||||
println!("Messages for {}", short_session(&session_id));
|
||||
for message in messages {
|
||||
println!(
|
||||
"{} {} -> {} | {}",
|
||||
message.timestamp.format("%H:%M:%S"),
|
||||
short_session(&message.from_session),
|
||||
short_session(&message.to_session),
|
||||
comms::preview(&message.msg_type, &message.content)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(Commands::Daemon) => {
|
||||
println!("Starting ECC daemon...");
|
||||
session::daemon::run(db, cfg).await?;
|
||||
@@ -125,6 +207,53 @@ async fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_session_id(db: &session::store::StateStore, value: &str) -> Result<String> {
|
||||
if value == "latest" {
|
||||
return db
|
||||
.get_latest_session()?
|
||||
.map(|session| session.id)
|
||||
.ok_or_else(|| anyhow::anyhow!("No sessions found"));
|
||||
}
|
||||
|
||||
db.get_session(value)?
|
||||
.map(|session| session.id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Session not found: {value}"))
|
||||
}
|
||||
|
||||
fn build_message(
|
||||
kind: MessageKindArg,
|
||||
text: String,
|
||||
context: Option<String>,
|
||||
files: Vec<String>,
|
||||
) -> Result<comms::MessageType> {
|
||||
Ok(match kind {
|
||||
MessageKindArg::Handoff => comms::MessageType::TaskHandoff {
|
||||
task: text,
|
||||
context: context.unwrap_or_default(),
|
||||
},
|
||||
MessageKindArg::Query => comms::MessageType::Query { question: text },
|
||||
MessageKindArg::Response => comms::MessageType::Response { answer: text },
|
||||
MessageKindArg::Completed => comms::MessageType::Completed {
|
||||
summary: text,
|
||||
files_changed: files,
|
||||
},
|
||||
MessageKindArg::Conflict => {
|
||||
let file = files
|
||||
.first()
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow::anyhow!("Conflict messages require at least one --file"))?;
|
||||
comms::MessageType::Conflict {
|
||||
file,
|
||||
description: context.unwrap_or(text),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn short_session(session_id: &str) -> String {
|
||||
session_id.chars().take(8).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -139,4 +268,41 @@ mod tests {
|
||||
_ => panic!("expected resume subcommand"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_parses_messages_send_command() {
|
||||
let cli = Cli::try_parse_from([
|
||||
"ecc",
|
||||
"messages",
|
||||
"send",
|
||||
"--from",
|
||||
"planner",
|
||||
"--to",
|
||||
"worker",
|
||||
"--kind",
|
||||
"query",
|
||||
"--text",
|
||||
"Need context",
|
||||
])
|
||||
.expect("messages send should parse");
|
||||
|
||||
match cli.command {
|
||||
Some(Commands::Messages {
|
||||
command:
|
||||
MessageCommands::Send {
|
||||
from,
|
||||
to,
|
||||
kind,
|
||||
text,
|
||||
..
|
||||
},
|
||||
}) => {
|
||||
assert_eq!(from, "planner");
|
||||
assert_eq!(to, "worker");
|
||||
assert!(matches!(kind, MessageKindArg::Query));
|
||||
assert_eq!(text, "Need context");
|
||||
}
|
||||
_ => panic!("expected messages send subcommand"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user