Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions proptest-regressions/event_preview.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 0c081b28fcee5c8c4b5439561a52585269c1cfb4d75510d9001f584fb8ac3aea # shrinks to body = "", detail = ""
14 changes: 14 additions & 0 deletions specs/task-agent-chat-owner-approval.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ expiration, and one-shot state before releasing a coding-agent operation.
prevents an approval click from becoming undecryptable when the bridge device
was registered after an older room session was established.
- Keep the existing `org.octos.approval_request` protocol backward compatible.
- Custom room-message previews show only the HTML-escaped fallback body, never
the Rust debug representation or extra protocol fields.

## Boundaries

Expand All @@ -50,6 +52,8 @@ expiration, and one-shot state before releasing a coding-agent operation.
- src/shared/mod.rs
- src/home/room_screen.rs
- src/sliding_sync.rs
- src/event_preview.rs
- proptest-regressions/event_preview.txt
- resources/i18n/en.json
- resources/i18n/zh-CN.json

Expand All @@ -69,6 +73,16 @@ expiration, and one-shot state before releasing a coding-agent operation.

## Completion Criteria

### Rule: custom-preview — preview depends only on escaped fallback body

Scenario: Custom message summaries do not expose protocol internals
Test: custom_message_preview_shows_escaped_body_without_protocol_details
Test: prop_custom_preview_depends_only_on_escaped_body
Given a custom room message with a fallback body and arbitrary extra fields
When text_preview_of_message creates a room or reply summary
Then the preview is exactly the HTML-escaped fallback body
And protocol fields and Rust debug output do not appear

Scenario: Public project-room status is read-only
Test: test_agentchat_public_status_has_no_actions
Given a timeline event has msgtype `com.agentchat.approval.status.v1`
Expand Down
36 changes: 32 additions & 4 deletions src/event_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,10 +478,7 @@ fn text_preview_of_message(
htmlize::escape_text(&video.body)
}
),
MessageType::_Custom(custom) => format!(
"[Custom message]: {:?}",
custom,
),
MessageType::_Custom(_) => htmlize::escape_text(msg.body()).to_string(),
other => format!(
"[Unknown message type]: {}",
htmlize::escape_text(other.body()),
Expand Down Expand Up @@ -834,3 +831,34 @@ pub fn text_preview_of_room_membership_change(
};
Some(TextPreview::from((text, BeforeText::UsernameWithoutColon)))
}

#[cfg(test)]
mod preview_tests {
use super::*;
use proptest::prelude::*;

fn custom_message(body: &str, detail: &str) -> MessageType {
serde_json::from_value(serde_json::json!({
"msgtype": "com.agentchat.approval.status.v1",
"body": body,
"com.agentchat.approval": { "kind": "status", "internal_detail": detail },
})).expect("valid custom room message")
}

#[test]
fn custom_message_preview_shows_escaped_body_without_protocol_details() {
let message = custom_message("等待 <owner> & approval", "PRIVATE_SENTINEL");
let preview = text_preview_of_message(&message, "agent").text;
assert_eq!(preview, "等待 &lt;owner&gt; &amp; approval");
assert!(!preview.contains("PRIVATE_SENTINEL"));
assert!(!preview.contains("CustomEventContent"));
}

proptest! {
#[test]
fn prop_custom_preview_depends_only_on_escaped_body(body in any::<String>(), detail in any::<String>()) {
let preview = text_preview_of_message(&custom_message(&body, &detail), "agent").text;
prop_assert_eq!(preview, htmlize::escape_text(&body).to_string());
}
}
}
Loading