update: SystemItem1本化

This commit is contained in:
2026-05-14 14:36:29 +09:00
parent e4b66345aa
commit f9def2d5bb
14 changed files with 752 additions and 379 deletions
+88 -29
View File
@@ -483,21 +483,13 @@ impl App {
self.blocks.push(Block::UserMessage { segments });
self.assistant_streaming = false;
}
Event::Notify { message } => {
self.blocks.push(Block::Notify { message });
self.assistant_streaming = false;
}
Event::PodEvent(event) => {
self.blocks.push(Block::PodEvent { event });
self.assistant_streaming = false;
}
Event::SessionRotated { entry } => {
self.reset_for_rotation();
self.apply_log_entry_raw(&entry);
self.assistant_streaming = false;
}
Event::HookInjectedItems { entry } => {
self.apply_log_entry_raw(&entry);
Event::SystemItem { item } => {
self.apply_system_item(&item);
self.assistant_streaming = false;
}
Event::TurnStart { .. } => {
@@ -984,11 +976,51 @@ impl App {
self.push_history_item(&item_value);
}
}
session_store::LogEntry::SystemItems { items, .. } => {
for system_item in items {
let value =
serde_json::to_value(&system_item).expect("SystemItem is Serialize");
self.apply_system_item(&value);
}
}
// Non-history-bearing variants don't affect the block view.
_ => {}
}
}
/// Dispatch one `SystemItem` JSON value into the appropriate block.
///
/// Kind-based routing replaces the old free-text `[Notification]` /
/// `[File: …]` parsing path: each kind maps directly to a typed
/// block (`Block::Notify`, `Block::PodEvent`, …).
fn apply_system_item(&mut self, value: &serde_json::Value) {
let Ok(item) = serde_json::from_value::<session_store::SystemItem>(value.clone()) else {
// Unknown / forward-compat shape: fall back to rendering the
// raw text payload (if any) as a generic system message.
if let Some(text) = value.get("body").and_then(|b| b.as_str()) {
self.task_store.apply_system_message_text(text);
self.blocks.push(Block::SystemMessage {
text: text.to_owned(),
});
}
return;
};
match item {
session_store::SystemItem::Notification { message, .. } => {
self.blocks.push(Block::Notify { message });
}
session_store::SystemItem::PodEvent { event, .. } => {
self.blocks.push(Block::PodEvent { event });
}
session_store::SystemItem::FileAttachment { body, .. }
| session_store::SystemItem::Knowledge { body, .. }
| session_store::SystemItem::Workflow { body, .. } => {
self.task_store.apply_system_message_text(&body);
self.blocks.push(Block::SystemMessage { text: body });
}
}
}
/// Sweep all current tool-call blocks: any that never resolved into
/// a Done / Error state get marked Incomplete. Called after a
/// snapshot replay so dangling in-flight tool calls in the seed
@@ -1422,18 +1454,14 @@ mod completion_flow_tests {
}
#[test]
fn live_hook_injected_items_event_appends_system_message_block() {
fn live_system_item_workflow_appends_system_message_block() {
let mut app = App::new("test".into());
let entry = serde_json::json!({
"kind": "hook_injected_items",
"ts": 1,
"items": [{
"kind": "message",
"role": "system",
"content": [{ "kind": "text", "text": "[Workflow /build]\nRun the build" }],
}],
let item = serde_json::json!({
"kind": "workflow",
"slug": "build",
"body": "[Workflow /build]\nRun the build",
});
app.handle_pod_event(Event::HookInjectedItems { entry });
app.handle_pod_event(Event::SystemItem { item });
assert!(matches!(
app.blocks.as_slice(),
@@ -1441,6 +1469,39 @@ mod completion_flow_tests {
));
}
#[test]
fn live_system_item_notification_appends_notify_block() {
let mut app = App::new("test".into());
let item = serde_json::json!({
"kind": "notification",
"message": "hi",
"body": "[Notification] hi",
});
app.handle_pod_event(Event::SystemItem { item });
assert!(matches!(
app.blocks.as_slice(),
[Block::Notify { message }] if message == "hi"
));
}
#[test]
fn live_system_item_pod_event_appends_pod_event_block() {
let mut app = App::new("test".into());
let item = serde_json::json!({
"kind": "pod_event",
"event": { "kind": "turn_ended", "pod_name": "child" },
"body": "[Notification] pod `child` finished a turn",
});
app.handle_pod_event(Event::SystemItem { item });
assert_eq!(app.blocks.len(), 1);
match &app.blocks[0] {
Block::PodEvent {
event: protocol::PodEvent::TurnEnded { pod_name },
} => assert_eq!(pod_name, "child"),
_ => panic!("expected a PodEvent block"),
}
}
#[test]
fn compact_done_replaces_live_block() {
let mut app = App::new("test".into());
@@ -1577,15 +1638,13 @@ mod completion_flow_tests {
```json\n{\n \"tasks\": [\n {\n \"taskid\": 4,\n \
\"status\": \"inprogress\",\n \"subject\": \"from snapshot\",\n \
\"description\": \"d\"\n }\n ]\n}\n```\n";
app.handle_pod_event(Event::HookInjectedItems {
entry: serde_json::json!({
"kind": "hook_injected_items",
"ts": 1,
"items": [{
"kind": "message",
"role": "system",
"content": [{ "kind": "text", "text": snapshot }],
}],
// Snapshot text injected as a workflow body (kind doesn't matter
// for task-store parsing, only the text contents do).
app.handle_pod_event(Event::SystemItem {
item: serde_json::json!({
"kind": "workflow",
"slug": "task-snapshot",
"body": snapshot,
}),
});