Skip to content

Commit 397a4de

Browse files
committed
feat(app-core): wire compaction hook + register compaction_hooks module
Beads: jcode-afp (restore_todos_after_compaction — done in jcode-wje) jcode-h08 (wire compaction hook into compaction.rs) * Added pub(crate) mod compaction_hooks; to server.rs * Wired restore_todos_after_compaction into Agent::poll_compaction_completion_event after note_compaction_applied(). Handles error via crate::logging::warn (never aborts compaction). * message_to_json_value helper converts session messages for the transcript scan. * Fixed compaction_hooks.rs imports to use correct type names (TodoEvent not TodoUpdated, String status not TodoStatus).
1 parent 2d0a7a5 commit 397a4de

3 files changed

Lines changed: 42 additions & 10 deletions

File tree

crates/jcode-app-core/src/agent/compaction.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@ impl Agent {
2626
self.note_compaction_applied();
2727
self.persist_session_best_effort("compaction completion");
2828

29+
// Restore todos from the post-compaction message log so the
30+
// model retains task context after compaction. Source pattern:
31+
// claude-code v1 sessionRestore.ts:extractTodosFromTranscript.
32+
// Fail-safe: tracing::warn on error, never abort compaction.
33+
{
34+
use crate::server::compaction_hooks::restore_todos_after_compaction;
35+
let messages_json: Vec<serde_json::Value> = self
36+
.session
37+
.messages_for_provider()
38+
.iter()
39+
.map(message_to_json_value)
40+
.collect();
41+
if let Err(e) = restore_todos_after_compaction(
42+
&self.session.id,
43+
&messages_json,
44+
) {
45+
crate::logging::warn(&format!(
46+
"failed to restore todos after compaction for session={}: {e}",
47+
self.session.id,
48+
));
49+
}
50+
}
51+
2952
// PostCompact hook (fire-and-forget)
3053
let registry = self.hook_registry.clone();
3154
let config = self.dispatch_config.clone();
@@ -532,3 +555,11 @@ impl Agent {
532555
};
533556
}
534557
}
558+
559+
/// Convert a stored Message to a generic JSON value for the compaction-hooks
560+
/// transcript scan. Shape: `{role, content}` where content is the array of
561+
/// content blocks (text + tool_use). Best-effort serialization; any failure
562+
/// yields Null and the scan simply skips that message.
563+
fn message_to_json_value(message: &crate::message::Message) -> serde_json::Value {
564+
serde_json::to_value(message).unwrap_or(serde_json::Value::Null)
565+
}

crates/jcode-app-core/src/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod await_members_state;
22
mod background_tasks;
33
mod client_actions;
4+
pub(crate) mod compaction_hooks;
45
mod client_api;
56
mod client_comm;
67
mod client_comm_channels;

crates/jcode-app-core/src/server/compaction_hooks.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
//! block cuối cùng của tool "todo", restore state. Zero in-memory state,
88
//! fail-safe.
99
10-
use crate::bus::{Bus, BusEvent, TodoUpdated};
10+
use crate::bus::{Bus, BusEvent, TodoEvent};
1111
use anyhow::Result;
12+
1213
use chrono::Utc;
13-
use jcode_task_types::{TodoItem, TodoStatus};
14+
use jcode_task_types::TodoItem;
1415
use serde_json::Value;
1516

1617
/// Extract most recent todo list from message log.
@@ -66,15 +67,15 @@ fn parse_one(item: Value) -> Option<TodoItem> {
6667
let content = item.get("content")?.as_str()?.to_string();
6768
let status_str = item.get("status")?.as_str()?;
6869
let status = match status_str {
69-
"pending" => TodoStatus::Pending,
70-
"in_progress" => TodoStatus::InProgress,
71-
"completed" => TodoStatus::Completed,
72-
"blocked" => TodoStatus::Blocked,
70+
"pending" => "pending",
71+
"in_progress" => "in_progress",
72+
"completed" => "completed",
73+
"blocked" => "blocked",
7374
_ => return None,
7475
};
7576
Some(TodoItem {
7677
content,
77-
status,
78+
status: status.to_string(),
7879
active_form: item
7980
.get("active_form")
8081
.and_then(|v| v.as_str())
@@ -94,10 +95,9 @@ pub fn restore_todos_after_compaction(
9495
return Ok(Vec::new());
9596
}
9697
crate::todo::save_todos(session_id, &todos)?;
97-
Bus::global().publish(BusEvent::TodoUpdated(TodoUpdated {
98+
Bus::global().publish(BusEvent::TodoUpdated(TodoEvent {
9899
session_id: session_id.to_string(),
99100
todos: todos.clone(),
100-
at: Utc::now(),
101101
}));
102102
Ok(todos)
103103
}
@@ -132,7 +132,7 @@ mod tests {
132132
let todos = extract_todos_from_transcript(&messages);
133133
assert_eq!(todos.len(), 2);
134134
assert_eq!(todos[0].content, "task A");
135-
assert_eq!(todos[1].status, TodoStatus::InProgress);
135+
assert_eq!(todos[1].status, "in_progress");
136136
}
137137

138138
#[test]

0 commit comments

Comments
 (0)