Merge pull request #1020 from rozsival/upstream

feat(channels): use plain text as default output formatter for Signal
This commit is contained in:
Jaber Jaber
2026-04-10 19:12:22 +03:00
committed by GitHub
12 changed files with 43 additions and 27 deletions
+1 -3
View File
@@ -484,9 +484,7 @@ pub async fn get_agent_session(
let filtered_messages: Vec<&openfang_types::message::Message> = session
.messages
.iter()
.filter(|m| {
include_system || m.role != openfang_types::message::Role::System
})
.filter(|m| include_system || m.role != openfang_types::message::Role::System)
.collect();
// Two-pass approach: ToolUse blocks live in Assistant messages while
@@ -418,7 +418,10 @@ async fn test_agent_session_filters_system_messages() {
let messages = body["messages"].as_array().unwrap();
assert_eq!(messages.len(), 3, "include_system=true should return all 3");
assert_eq!(messages[0]["role"], "System");
assert_eq!(messages[0]["content"], "INTERNAL SYSTEM PROMPT — must not leak to UI");
assert_eq!(
messages[0]["content"],
"INTERNAL SYSTEM PROMPT — must not leak to UI"
);
assert_eq!(body["message_count"], 3);
assert_eq!(body["raw_message_count"], 3);
}
+5
View File
@@ -517,6 +517,7 @@ fn default_output_format_for_channel(channel_type: &str) -> OutputFormat {
"telegram" => OutputFormat::TelegramHtml,
"slack" => OutputFormat::SlackMrkdwn,
"wecom" => OutputFormat::PlainText,
"signal" => OutputFormat::PlainText,
_ => OutputFormat::Markdown,
}
}
@@ -1932,6 +1933,10 @@ mod tests {
default_output_format_for_channel("discord"),
OutputFormat::Markdown
);
assert_eq!(
default_output_format_for_channel("signal"),
OutputFormat::PlainText
)
}
#[tokio::test]
+4 -1
View File
@@ -1011,7 +1011,10 @@ mod tests {
assert!(payload["d"].is_null());
let s = serde_json::to_string(&payload).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed, serde_json::json!({"op": 1, "d": serde_json::Value::Null}));
assert_eq!(
parsed,
serde_json::json!({"op": 1, "d": serde_json::Value::Null})
);
}
#[test]
+1 -4
View File
@@ -127,10 +127,7 @@ impl HandsState {
if let Some(sel) = self.marketplace_list.selected() {
if sel < self.definitions.len() {
// TODO: add text-input modal for custom instance names (#878 follow-up)
return HandsAction::ActivateHand(
self.definitions[sel].id.clone(),
None,
);
return HandsAction::ActivateHand(self.definitions[sel].id.clone(), None);
}
}
}
+1 -3
View File
@@ -1141,9 +1141,7 @@ metrics = []
fn test_activate_same_hand_unnamed_twice_still_rejects() {
let reg = test_registry_with_dummy_hand("test-hand");
reg.activate("test-hand", HashMap::new(), None).unwrap();
let err = reg
.activate("test-hand", HashMap::new(), None)
.unwrap_err();
let err = reg.activate("test-hand", HashMap::new(), None).unwrap_err();
assert!(matches!(err, HandError::AlreadyActive(_)));
}
}
+6 -7
View File
@@ -898,12 +898,12 @@ impl OpenFangKernel {
// Auto-detect embedding provider by checking API key env vars in
// priority order. First match wins.
const API_KEY_PROVIDERS: &[(&str, &str)] = &[
("OPENAI_API_KEY", "openai"),
("GROQ_API_KEY", "groq"),
("MISTRAL_API_KEY", "mistral"),
("TOGETHER_API_KEY", "together"),
("OPENAI_API_KEY", "openai"),
("GROQ_API_KEY", "groq"),
("MISTRAL_API_KEY", "mistral"),
("TOGETHER_API_KEY", "together"),
("FIREWORKS_API_KEY", "fireworks"),
("COHERE_API_KEY", "cohere"),
("COHERE_API_KEY", "cohere"),
];
let detected_from_key = API_KEY_PROVIDERS
@@ -1144,8 +1144,7 @@ impl OpenFangKernel {
!= entry.manifest.tool_allowlist
|| disk_manifest.tool_blocklist
!= entry.manifest.tool_blocklist
|| disk_manifest.skills
!= entry.manifest.skills
|| disk_manifest.skills != entry.manifest.skills
|| disk_manifest.mcp_servers
!= entry.manifest.mcp_servers;
if changed {
+2 -2
View File
@@ -308,8 +308,8 @@ impl McpConnection {
// rmcp 1.3+ marks StreamableHttpClientTransportConfig as #[non_exhaustive].
// Use the official builder API (credit: @jefflower, PR #986).
let config = StreamableHttpClientTransportConfig::with_uri(url)
.custom_headers(custom_headers);
let config =
StreamableHttpClientTransportConfig::with_uri(url).custom_headers(custom_headers);
let transport = StreamableHttpClientTransport::from_config(config);
@@ -1090,7 +1090,11 @@ mod tests {
is_error,
content,
..
} => tool_use_id == "memory_store:0" && *is_error && content.contains("interrupted"),
} => {
tool_use_id == "memory_store:0"
&& *is_error
&& content.contains("interrupted")
}
_ => false,
})
} else {
+1 -2
View File
@@ -3141,8 +3141,7 @@ async fn tool_process_start(
// shared validate_command_allowlist handles Deny / Full / Allowlist and
// falls through to allow commands listed in safe_bins or allowed_commands.
if let Some(policy) = exec_policy {
if let Err(reason) =
crate::subprocess_sandbox::validate_command_allowlist(command, policy)
if let Err(reason) = crate::subprocess_sandbox::validate_command_allowlist(command, policy)
{
return Err(format!(
"process_start blocked: {reason}. Current exec_policy.mode = '{:?}'. \
+5 -1
View File
@@ -506,7 +506,11 @@ mod tests {
assert!(check_ssrf("http://169.254.169.254/latest/meta-data/", &allow).is_err());
// Also verify hostname-based metadata blocks
let allow2 = vec!["metadata.google.internal".to_string()];
assert!(check_ssrf("http://metadata.google.internal/computeMetadata/v1/", &allow2).is_err());
assert!(check_ssrf(
"http://metadata.google.internal/computeMetadata/v1/",
&allow2
)
.is_err());
}
#[test]
+8 -2
View File
@@ -358,7 +358,10 @@ impl WebSearchEngine {
let resp = self
.client
.get(format!("{}/search", self.config.searxng.url.trim_end_matches('/')))
.get(format!(
"{}/search",
self.config.searxng.url.trim_end_matches('/')
))
.query(&[
("q", query),
("format", "json"),
@@ -451,7 +454,10 @@ impl WebSearchEngine {
let resp = self
.client
.get(format!("{}/config", self.config.searxng.url.trim_end_matches('/')))
.get(format!(
"{}/config",
self.config.searxng.url.trim_end_matches('/')
))
.header("User-Agent", "Mozilla/5.0 (compatible; OpenFangAgent/0.1)")
.send()
.await