release: v1.9.0

This commit is contained in:
coso
2026-04-13 02:50:53 +08:00
parent 52624b1488
commit 0f15b8a71e
313 changed files with 19875 additions and 9864 deletions
+85 -1
View File
@@ -48,6 +48,35 @@ pub fn load_memory_source_candidates(
Ok(candidates)
}
pub fn load_session_memory_source_candidates(
conn: &Connection,
session_id: &str,
limit: usize,
min_message_length: usize,
) -> Result<Vec<MemorySourceCandidate>, String> {
let rows = AgentDao::list_message_text_rows_by_session_id(conn, session_id, limit)
.map_err(|e| format!("读取 session agent_messages 失败: {e}"))?;
let mut candidates = Vec::new();
let mut seen = HashSet::new();
for row in rows {
push_candidate(
&mut candidates,
&mut seen,
row.session_id,
row.role,
row.content,
row.timestamp_ms,
min_message_length,
);
}
candidates.sort_by(|a, b| b.created_at.cmp(&a.created_at));
candidates.truncate(limit);
Ok(candidates)
}
fn load_unified_general_candidates(
conn: &Connection,
from_timestamp: Option<i64>,
@@ -189,7 +218,7 @@ fn format_sqlite_datetime(timestamp_ms: i64) -> String {
#[cfg(test)]
mod tests {
use super::load_memory_source_candidates;
use super::{load_memory_source_candidates, load_session_memory_source_candidates};
use rusqlite::{params, Connection};
fn create_test_schema(conn: &Connection) {
@@ -368,4 +397,59 @@ mod tests {
assert!(session_ids.contains(&"agent-1"));
assert!(!session_ids.contains(&"legacy-only"));
}
#[test]
fn load_session_memory_source_candidates_only_reads_target_session() {
let conn = Connection::open_in_memory().expect("open in memory db");
create_test_schema(&conn);
conn.execute(
"INSERT INTO agent_sessions (id, model, created_at, updated_at) VALUES (?1, ?2, ?3, ?4)",
params![
"agent-1",
"claude-sonnet-4",
"2026-03-12T10:00:00+08:00",
"2026-03-12T10:00:00+08:00"
],
)
.unwrap();
conn.execute(
"INSERT INTO agent_sessions (id, model, created_at, updated_at) VALUES (?1, ?2, ?3, ?4)",
params![
"agent-2",
"claude-sonnet-4",
"2026-03-12T10:05:00+08:00",
"2026-03-12T10:05:00+08:00"
],
)
.unwrap();
conn.execute(
"INSERT INTO agent_messages (session_id, role, content_json, timestamp) VALUES (?1, ?2, ?3, ?4)",
params![
"agent-1",
"user",
r#"[{"type":"text","text":"请记住我现在更偏好 pnpm 工作流"}]"#,
"2026-03-12T10:00:00+08:00"
],
)
.unwrap();
conn.execute(
"INSERT INTO agent_messages (session_id, role, content_json, timestamp) VALUES (?1, ?2, ?3, ?4)",
params![
"agent-2",
"assistant",
r#"[{"type":"text","text":"这条消息不该进入 agent-1 的自动抽取"}]"#,
"2026-03-12T10:05:00+08:00"
],
)
.unwrap();
let candidates = load_session_memory_source_candidates(&conn, "agent-1", 10, 1)
.expect("load session candidates");
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].session_id, "agent-1");
assert!(candidates[0].content.contains("pnpm"));
}
}
@@ -1156,11 +1156,39 @@ mod tests {
}
}
fn write_request_telemetry_fixture(root: &Path) {
let request_logs_dir = root.join("request_logs");
fs::create_dir_all(&request_logs_dir).expect("create request logs dir");
let mut log = lime_infra::telemetry::RequestLog::new(
"req-analysis-1".to_string(),
lime_core::ProviderType::OpenAI,
"gpt-5.4".to_string(),
false,
);
log.session_id = Some("session-1".to_string());
log.thread_id = Some("thread-1".to_string());
log.turn_id = Some("turn-1".to_string());
log.pending_request_id = Some("req-1".to_string());
log.queued_turn_id = Some("queued-1".to_string());
log.mark_success(320, 200);
fs::write(
request_logs_dir.join("requests_2026-03-27.jsonl"),
format!(
"{}\n",
serde_json::to_string(&log).expect("serialize request log")
),
)
.expect("write request log");
}
#[test]
fn should_export_runtime_analysis_handoff_to_workspace() {
let temp_dir = TempDir::new().expect("temp dir");
let detail = build_detail();
let thread_read = build_thread_read();
write_request_telemetry_fixture(temp_dir.path());
let result = export_runtime_analysis_handoff(&detail, &thread_read, temp_dir.path())
.expect("export");
@@ -1189,7 +1217,8 @@ mod tests {
assert!(brief.contains("外部分析交接简报"));
assert!(brief.contains("pending request:1"));
assert!(brief.contains("证据关联与可观测覆盖"));
assert!(brief.contains("requestTelemetry (unlinked)"));
assert!(brief.contains("requestTelemetry"));
assert!(!brief.contains("requestTelemetry (unlinked)"));
assert!(brief.contains("/workspace/lime"));
let context = fs::read_to_string(context_path).expect("context");
@@ -1199,6 +1228,7 @@ mod tests {
assert!(context.contains("\"observability\""));
assert!(context.contains("\"correlationKeys\""));
assert!(context.contains("\"gapSignals\""));
assert!(context.contains("\"matchedRequestCount\": 1"));
assert!(context.contains("/workspace/lime"));
assert!(!context.contains(temp_dir.path().to_string_lossy().as_ref()));
}
File diff suppressed because it is too large Load Diff
@@ -7,8 +7,7 @@
use crate::agent::SessionDetail;
use crate::commands::aster_agent_cmd::AgentRuntimeThreadReadModel;
use crate::services::runtime_evidence_pack_service::{
build_runtime_observability_summary_json, export_runtime_evidence_pack,
RuntimeEvidencePackExportResult,
export_runtime_evidence_pack, RuntimeEvidencePackExportResult,
};
use crate::services::runtime_handoff_artifact_service::{
export_runtime_handoff_bundle, RuntimeHandoffBundleExportResult,
@@ -145,12 +144,7 @@ pub fn export_runtime_replay_case(
let recent_artifacts = collect_recent_artifact_paths(detail);
let pending_requests = collect_pending_request_inputs(detail, thread_read);
let recent_timeline = collect_recent_timeline_items(detail);
let observability_summary = build_runtime_observability_summary_json(
detail,
thread_read,
&recent_artifacts,
&evidence_pack.known_gaps,
);
let observability_summary = read_observability_summary_from_evidence_pack(&evidence_pack)?;
let success_criteria = build_success_criteria(
detail,
thread_read,
@@ -283,6 +277,29 @@ fn write_replay_file(
})
}
fn read_observability_summary_from_evidence_pack(
evidence_pack: &RuntimeEvidencePackExportResult,
) -> Result<Value, String> {
let runtime_path = Path::new(&evidence_pack.pack_absolute_root).join("runtime.json");
let raw = fs::read_to_string(&runtime_path).map_err(|error| {
format!(
"读取 evidence pack runtime.json 失败 {}: {error}",
runtime_path.display()
)
})?;
let payload = serde_json::from_str::<Value>(raw.as_str()).map_err(|error| {
format!(
"解析 evidence pack runtime.json 失败 {}: {error}",
runtime_path.display()
)
})?;
Ok(payload
.pointer("/observabilitySummary")
.cloned()
.unwrap_or(Value::Null))
}
#[allow(clippy::too_many_arguments)]
fn build_input_json(
detail: &SessionDetail,
@@ -1261,6 +1278,7 @@ mod tests {
assert!(input.contains("\"pending_request\""));
assert!(input.contains("\"observability\""));
assert!(input.contains("\"requestTelemetry\""));
assert!(!input.contains("\"artifactValidator\""));
let expected = fs::read_to_string(expected_path).expect("expected");
assert!(expected.contains("不要要求与原始会话完全相同的工具调用顺序"));
@@ -1274,6 +1292,6 @@ mod tests {
assert!(links.contains("\"handoffBundle\""));
assert!(links.contains("\"evidencePack\""));
assert!(links.contains("\"observabilitySummary\""));
assert!(links.contains("\"artifactValidator\""));
assert!(!links.contains("\"artifactValidator\""));
}
}