persona: render whom each message addresses and say how to read it
The mouth delivers each message's mentions but the turn input never showed them, so a Persona could not tell a reply to another Mind from one to her. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1421b5fe0a
commit
005fa7aaa5
2 changed files with 58 additions and 5 deletions
|
|
@ -31,6 +31,7 @@
|
|||
//! The organ admits only a follow-up, a run's closing resolution and, through
|
||||
//! `open_and_launch`, a Self run. It never writes the brake or the dial.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -912,12 +913,26 @@ pub(crate) fn quoted(text: &str) -> String {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// The addressees of a message, from the mentions its delivery carries: "you"
|
||||
/// for this Persona, a display name where this turn's senders showed one, else
|
||||
/// the Matrix id. Empty when the message names no one.
|
||||
fn addressees(config: &Config, mentions: &[String], names: &BTreeMap<&str, &str>) -> String {
|
||||
mentions
|
||||
.iter()
|
||||
.map(|id| if *id == config.own_sender { "you" } else { names.get(id.as_str()).copied().unwrap_or(id.as_str()) })
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
/// One message line: the sender id and the role the organ derived from it stand
|
||||
/// beside the member's room, display name and text, which are quoted. Only the
|
||||
/// `role` field says who is the operator, and no quoted text can write one.
|
||||
fn message_line(config: &Config, label: &str, room: &str, sender: &str, display: Option<&str>, text: &str) -> String {
|
||||
/// `to` is the addressees, shown only when the message names some; it is quoted.
|
||||
fn message_line(config: &Config, label: &str, room: &str, sender: &str, display: Option<&str>, to: &str, text: &str) -> String {
|
||||
let name = display.map_or_else(String::new, |display| format!(" name={}", quoted(display)));
|
||||
format!("{label} room={} sender={} role={}{name} text={}\n", quoted(room), quoted(sender), config.sender(sender).tag(), quoted(text))
|
||||
let to = if to.is_empty() { String::new() } else { format!(" to={}", quoted(to)) };
|
||||
format!("{label} room={} sender={} role={}{name}{to} text={}
|
||||
", quoted(room), quoted(sender), config.sender(sender).tag(), quoted(text))
|
||||
}
|
||||
|
||||
/// The turn's input: the rooms, the burn line, her latest runs, and the
|
||||
|
|
@ -937,11 +952,13 @@ fn render_input(config: &Config, deliveries: &[ChatDelivery], history: &[String]
|
|||
if deliveries.is_empty() {
|
||||
out.push_str("No new messages. This is a scheduled wake: decide whether to speak or to start work.\n");
|
||||
}
|
||||
let names: BTreeMap<&str, &str> = deliveries.iter().map(|d| (d.sender.as_str(), d.sender_display.as_str())).filter(|(_, name)| !name.is_empty()).collect();
|
||||
for delivery in deliveries {
|
||||
for earlier in &delivery.context {
|
||||
out.push_str(&message_line(config, "earlier", &delivery.room_alias, &earlier.sender, None, &earlier.text));
|
||||
out.push_str(&message_line(config, "earlier", &delivery.room_alias, &earlier.sender, None, "", &earlier.text));
|
||||
}
|
||||
out.push_str(&message_line(config, "message", &delivery.room_alias, &delivery.sender, Some(&delivery.sender_display), &delivery.text));
|
||||
let to = addressees(config, &delivery.mentions, &names);
|
||||
out.push_str(&message_line(config, "message", &delivery.room_alias, &delivery.sender, Some(&delivery.sender_display), &to, &delivery.text));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ fn recent_runs_are_the_newest_five_newest_first() {
|
|||
fn line_separators_in_member_text_cannot_end_its_line() {
|
||||
let seps = [char::from_u32(0x2028).unwrap(), char::from_u32(0x2029).unwrap(), char::from_u32(0x85).unwrap()];
|
||||
let text = format!("a{}b{}c{}d", seps[0], seps[1], seps[2]);
|
||||
let line = message_line(&config(), "message", "swarm", "@m:chat", Some("M"), &text);
|
||||
let line = message_line(&config(), "message", "swarm", "@m:chat", Some("M"), "", &text);
|
||||
assert_eq!(line.matches('\n').count(), 1, "{line:?}");
|
||||
assert!(seps.iter().all(|sep| !line.contains(*sep)), "{line:?}");
|
||||
}
|
||||
|
|
@ -1117,3 +1117,39 @@ fn with_no_campaign_in_force_the_instructions_say_so_and_the_schema_allows_no_st
|
|||
let schema: serde_json::Value = serde_json::from_str(&requests[0].output_schema_json).unwrap();
|
||||
assert_eq!(schema["properties"]["start_self_run"], json!({ "type": "null" }));
|
||||
}
|
||||
|
||||
fn input_for(mentions: &[&str]) -> String {
|
||||
let mut aqua = delivery(1, "swarm", "@aqua:chat", "hello");
|
||||
aqua.sender_display = "Aqua".into();
|
||||
let mut said = delivery(2, "swarm", "@operator:chat", "a reply");
|
||||
said.mentions = mentions.iter().map(|m| (*m).to_string()).collect();
|
||||
render_input(&config(), &[aqua, said], &[], "burn")
|
||||
}
|
||||
|
||||
fn message_two(input: &str) -> &str {
|
||||
input.lines().find(|line| line.contains("a reply")).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_message_mentioning_another_persona_renders_its_name() {
|
||||
let input = input_for(&["@aqua:chat"]);
|
||||
assert!(message_two(&input).contains(" to=\"Aqua\" "), "{input}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_message_mentioning_this_persona_renders_you() {
|
||||
let own = config().own_sender;
|
||||
let input = input_for(&[own.as_str(), "@kiko:chat"]);
|
||||
assert!(message_two(&input).contains(&format!(" to=\"you, @kiko:chat\" ")), "{input}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_message_without_mentions_renders_no_addressee() {
|
||||
let input = input_for(&[]);
|
||||
assert!(!input.lines().filter(|line| line.starts_with("message ")).any(|line| line.contains(" to=")), "{input}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_turn_input_says_how_to_read_an_addressee() {
|
||||
assert!(input_for(&[]).contains("A message addressed to another Mind is theirs to answer. Answer it only if you hold something they need, and say so."));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue