Save assistant message (vibe-kanban) (#47)
* Task attempt bc7ef54d-3955-4902-8086-6676c7924f1b - Final changes * Task attempt bc7ef54d-3955-4902-8086-6676c7924f1b - Final changes * Task attempt bc7ef54d-3955-4902-8086-6676c7924f1b - Final changes * Task attempt bc7ef54d-3955-4902-8086-6676c7924f1b - Final changes * Task attempt bc7ef54d-3955-4902-8086-6676c7924f1b - Final changes * Cargo fmt
This commit is contained in:
committed by
GitHub
parent
e22988da51
commit
2aed3c06c8
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE id = $1",
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n summary,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE task_attempt_id = $1 \n ORDER BY created_at ASC",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -29,14 +29,19 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "summary",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -48,9 +53,10 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "3b65c0f6215229f3c8d487c204bca5a1a8e327d9b469b47d833befa95377dfab"
|
"hash": "417a8b1ff4e51de82aea0159a3b97932224dc325b23476cb84153d690227fd8b"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE task_attempt_id = $1 \n ORDER BY created_at ASC",
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n summary,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE execution_process_id = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -29,14 +29,19 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "summary",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -48,9 +53,10 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "06ca282915d0db9125769b1bca92f7a5bd7f81ad8faf0f9fcbb5f1c2d35dd67f"
|
"hash": "89a6db6a4b318736dca5c1b8921631a2ba93d95a27bcbbef0058d4726af8a733"
|
||||||
}
|
}
|
||||||
12
backend/.sqlx/query-8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e.json
generated
Normal file
12
backend/.sqlx/query-8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "UPDATE executor_sessions \n SET summary = $1, updated_at = datetime('now') \n WHERE execution_process_id = $2",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE execution_process_id = $1",
|
"query": "SELECT \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n summary,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM executor_sessions \n WHERE id = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -29,14 +29,19 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "summary",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -48,9 +53,10 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "0468aa522ed7fd2675bcf278f6be38ce16752cb73adf0fafc5b497a88f32f531"
|
"hash": "a31fff84f3b8e532fd1160447d89d700f06ae08821fee00c9a5b60492b05259c"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "INSERT INTO executor_sessions (\n id, task_attempt_id, execution_process_id, session_id, prompt, \n created_at, updated_at\n ) \n VALUES ($1, $2, $3, $4, $5, $6, $7) \n RETURNING \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"",
|
"query": "INSERT INTO executor_sessions (\n id, task_attempt_id, execution_process_id, session_id, prompt, summary,\n created_at, updated_at\n ) \n VALUES ($1, $2, $3, $4, $5, $6, $7, $8) \n RETURNING \n id as \"id!: Uuid\", \n task_attempt_id as \"task_attempt_id!: Uuid\", \n execution_process_id as \"execution_process_id!: Uuid\", \n session_id, \n prompt,\n summary,\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -29,18 +29,23 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "summary",
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 7
|
"Right": 8
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
true,
|
true,
|
||||||
@@ -48,9 +53,10 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "a528a9926fab1c819a5a1fa1cde87ea9d354da0873af22e888d0bf8e0c7f306a"
|
"hash": "d0d71fd65c0f9f1bd0df2588d313085452e24260f48844b588152b532ca5d6e7"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Add summary column to executor_sessions table
|
||||||
|
ALTER TABLE executor_sessions ADD COLUMN summary TEXT;
|
||||||
@@ -17,9 +17,11 @@ use crate::{
|
|||||||
async fn commit_execution_changes(
|
async fn commit_execution_changes(
|
||||||
worktree_path: &str,
|
worktree_path: &str,
|
||||||
attempt_id: Uuid,
|
attempt_id: Uuid,
|
||||||
|
summary: Option<&str>,
|
||||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// Run git operations in a blocking task since git2 is synchronous
|
// Run git operations in a blocking task since git2 is synchronous
|
||||||
let worktree_path = worktree_path.to_string();
|
let worktree_path = worktree_path.to_string();
|
||||||
|
let summary = summary.map(|s| s.to_string());
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let worktree_repo = Repository::open(&worktree_path)?;
|
let worktree_repo = Repository::open(&worktree_path)?;
|
||||||
|
|
||||||
@@ -55,7 +57,11 @@ async fn commit_execution_changes(
|
|||||||
let tree = worktree_repo.find_tree(tree_id)?;
|
let tree = worktree_repo.find_tree(tree_id)?;
|
||||||
|
|
||||||
// Create commit for the changes
|
// Create commit for the changes
|
||||||
let commit_message = format!("Task attempt {} - Final changes", attempt_id);
|
let commit_message = if let Some(ref summary_msg) = summary {
|
||||||
|
summary_msg.clone()
|
||||||
|
} else {
|
||||||
|
format!("Task attempt {} - Final changes", attempt_id)
|
||||||
|
};
|
||||||
worktree_repo.commit(
|
worktree_repo.commit(
|
||||||
Some("HEAD"),
|
Some("HEAD"),
|
||||||
&signature,
|
&signature,
|
||||||
@@ -580,7 +586,7 @@ async fn handle_coding_agent_completion(
|
|||||||
app_state: &AppState,
|
app_state: &AppState,
|
||||||
task_attempt_id: Uuid,
|
task_attempt_id: Uuid,
|
||||||
execution_process_id: Uuid,
|
execution_process_id: Uuid,
|
||||||
_execution_process: ExecutionProcess,
|
execution_process: ExecutionProcess,
|
||||||
success: bool,
|
success: bool,
|
||||||
exit_code: Option<i64>,
|
exit_code: Option<i64>,
|
||||||
) {
|
) {
|
||||||
@@ -590,6 +596,37 @@ async fn handle_coding_agent_completion(
|
|||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Extract and store assistant message from execution logs
|
||||||
|
let summary = if let Some(stdout) = &execution_process.stdout {
|
||||||
|
if let Some(assistant_message) = crate::executor::parse_assistant_message_from_logs(stdout)
|
||||||
|
{
|
||||||
|
if let Err(e) = crate::models::executor_session::ExecutorSession::update_summary(
|
||||||
|
&app_state.db_pool,
|
||||||
|
execution_process_id,
|
||||||
|
&assistant_message,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"Failed to update summary for execution process {}: {}",
|
||||||
|
execution_process_id,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
tracing::info!(
|
||||||
|
"Successfully stored summary for execution process {}",
|
||||||
|
execution_process_id
|
||||||
|
);
|
||||||
|
Some(assistant_message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Play sound notification if enabled
|
// Play sound notification if enabled
|
||||||
if app_state.get_sound_alerts_enabled().await {
|
if app_state.get_sound_alerts_enabled().await {
|
||||||
let sound_file = app_state.get_sound_file().await;
|
let sound_file = app_state.get_sound_file().await;
|
||||||
@@ -612,7 +649,12 @@ async fn handle_coding_agent_completion(
|
|||||||
TaskAttempt::find_by_id(&app_state.db_pool, task_attempt_id).await
|
TaskAttempt::find_by_id(&app_state.db_pool, task_attempt_id).await
|
||||||
{
|
{
|
||||||
// Commit any unstaged changes after execution completion
|
// Commit any unstaged changes after execution completion
|
||||||
if let Err(e) = commit_execution_changes(&task_attempt.worktree_path, task_attempt_id).await
|
if let Err(e) = commit_execution_changes(
|
||||||
|
&task_attempt.worktree_path,
|
||||||
|
task_attempt_id,
|
||||||
|
summary.as_deref(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Failed to commit execution changes for attempt {}: {}",
|
"Failed to commit execution changes for attempt {}: {}",
|
||||||
|
|||||||
@@ -436,6 +436,90 @@ pub async fn stream_output_to_db(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse assistant message from executor logs (JSONL format)
|
||||||
|
pub fn parse_assistant_message_from_logs(logs: &str) -> Option<String> {
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
let mut last_assistant_message = None;
|
||||||
|
|
||||||
|
for line in logs.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse as JSON
|
||||||
|
if let Ok(json) = serde_json::from_str::<Value>(trimmed) {
|
||||||
|
// Check for Claude format: {"type":"assistant","message":{"content":[...]}}
|
||||||
|
if let Some(msg_type) = json.get("type").and_then(|t| t.as_str()) {
|
||||||
|
if msg_type == "assistant" {
|
||||||
|
if let Some(message) = json.get("message") {
|
||||||
|
if let Some(content) = message.get("content").and_then(|c| c.as_array()) {
|
||||||
|
// Extract text content from Claude assistant message
|
||||||
|
let mut text_parts = Vec::new();
|
||||||
|
for content_item in content {
|
||||||
|
if let Some(content_type) =
|
||||||
|
content_item.get("type").and_then(|t| t.as_str())
|
||||||
|
{
|
||||||
|
if content_type == "text" {
|
||||||
|
if let Some(text) =
|
||||||
|
content_item.get("text").and_then(|t| t.as_str())
|
||||||
|
{
|
||||||
|
text_parts.push(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !text_parts.is_empty() {
|
||||||
|
last_assistant_message = Some(text_parts.join("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for AMP format: {"type":"messages","messages":[[1,{"role":"assistant",...}]]}
|
||||||
|
if let Some(messages) = json.get("messages").and_then(|m| m.as_array()) {
|
||||||
|
for message_entry in messages {
|
||||||
|
if let Some(message_data) = message_entry.as_array().and_then(|arr| arr.get(1))
|
||||||
|
{
|
||||||
|
if let Some(role) = message_data.get("role").and_then(|r| r.as_str()) {
|
||||||
|
if role == "assistant" {
|
||||||
|
if let Some(content) =
|
||||||
|
message_data.get("content").and_then(|c| c.as_array())
|
||||||
|
{
|
||||||
|
// Extract text content from AMP assistant message
|
||||||
|
let mut text_parts = Vec::new();
|
||||||
|
for content_item in content {
|
||||||
|
if let Some(content_type) =
|
||||||
|
content_item.get("type").and_then(|t| t.as_str())
|
||||||
|
{
|
||||||
|
if content_type == "text" {
|
||||||
|
if let Some(text) = content_item
|
||||||
|
.get("text")
|
||||||
|
.and_then(|t| t.as_str())
|
||||||
|
{
|
||||||
|
text_parts.push(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !text_parts.is_empty() {
|
||||||
|
last_assistant_message = Some(text_parts.join("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_assistant_message
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse session_id from Claude or thread_id from Amp from the first JSONL line
|
/// Parse session_id from Claude or thread_id from Amp from the first JSONL line
|
||||||
fn parse_session_id_from_line(line: &str) -> Option<String> {
|
fn parse_session_id_from_line(line: &str) -> Option<String> {
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -502,4 +586,35 @@ mod tests {
|
|||||||
assert_eq!(parse_session_id_from_line(""), None);
|
assert_eq!(parse_session_id_from_line(""), None);
|
||||||
assert_eq!(parse_session_id_from_line(" "), None);
|
assert_eq!(parse_session_id_from_line(" "), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_assistant_message_from_logs() {
|
||||||
|
// Test AMP format
|
||||||
|
let amp_logs = r#"{"type":"initial","threadID":"T-e7af5516-e5a5-4754-8e34-810dc658716e"}
|
||||||
|
{"type":"messages","messages":[[0,{"role":"user","content":[{"type":"text","text":"Task title: Test task"}],"meta":{"sentAt":1751385490573}}]],"toolResults":[]}
|
||||||
|
{"type":"messages","messages":[[1,{"role":"assistant","content":[{"type":"thinking","thinking":"Testing"},{"type":"text","text":"The Pythagorean theorem states that in a right triangle, the square of the hypotenuse equals the sum of squares of the other two sides: **a² + b² = c²**."}],"state":{"type":"complete","stopReason":"end_turn"}}]],"toolResults":[]}
|
||||||
|
{"type":"state","state":"idle"}
|
||||||
|
{"type":"shutdown"}"#;
|
||||||
|
|
||||||
|
let result = parse_assistant_message_from_logs(amp_logs);
|
||||||
|
assert!(result.is_some());
|
||||||
|
assert!(result.as_ref().unwrap().contains("Pythagorean theorem"));
|
||||||
|
assert!(result.as_ref().unwrap().contains("a² + b² = c²"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_claude_assistant_message_from_logs() {
|
||||||
|
// Test Claude format
|
||||||
|
let claude_logs = r#"{"type":"system","subtype":"init","cwd":"/private/tmp","session_id":"e988eeea-3712-46a1-82d4-84fbfaa69114","tools":[],"model":"claude-sonnet-4-20250514"}
|
||||||
|
{"type":"assistant","message":{"id":"msg_123","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"I'll explain the Pythagorean theorem for you.\n\nThe Pythagorean theorem states that in a right triangle, the square of the hypotenuse equals the sum of the squares of the other two sides.\n\n**Formula:** a² + b² = c²"}],"stop_reason":null},"session_id":"e988eeea-3712-46a1-82d4-84fbfaa69114"}
|
||||||
|
{"type":"result","subtype":"success","is_error":false,"duration_ms":6059,"result":"Final result"}"#;
|
||||||
|
|
||||||
|
let result = parse_assistant_message_from_logs(claude_logs);
|
||||||
|
assert!(result.is_some());
|
||||||
|
assert!(result.as_ref().unwrap().contains("Pythagorean theorem"));
|
||||||
|
assert!(result
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.contains("**Formula:** a² + b² = c²"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub struct ExecutorSession {
|
|||||||
pub execution_process_id: Uuid,
|
pub execution_process_id: Uuid,
|
||||||
pub session_id: Option<String>, // External session ID from Claude/Amp
|
pub session_id: Option<String>, // External session ID from Claude/Amp
|
||||||
pub prompt: Option<String>, // The prompt sent to the executor
|
pub prompt: Option<String>, // The prompt sent to the executor
|
||||||
|
pub summary: Option<String>, // Final assistant message/summary
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
@@ -30,6 +31,7 @@ pub struct CreateExecutorSession {
|
|||||||
pub struct UpdateExecutorSession {
|
pub struct UpdateExecutorSession {
|
||||||
pub session_id: Option<String>,
|
pub session_id: Option<String>,
|
||||||
pub prompt: Option<String>,
|
pub prompt: Option<String>,
|
||||||
|
pub summary: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutorSession {
|
impl ExecutorSession {
|
||||||
@@ -44,6 +46,7 @@ impl ExecutorSession {
|
|||||||
execution_process_id as "execution_process_id!: Uuid",
|
execution_process_id as "execution_process_id!: Uuid",
|
||||||
session_id,
|
session_id,
|
||||||
prompt,
|
prompt,
|
||||||
|
summary,
|
||||||
created_at as "created_at!: DateTime<Utc>",
|
created_at as "created_at!: DateTime<Utc>",
|
||||||
updated_at as "updated_at!: DateTime<Utc>"
|
updated_at as "updated_at!: DateTime<Utc>"
|
||||||
FROM executor_sessions
|
FROM executor_sessions
|
||||||
@@ -67,6 +70,7 @@ impl ExecutorSession {
|
|||||||
execution_process_id as "execution_process_id!: Uuid",
|
execution_process_id as "execution_process_id!: Uuid",
|
||||||
session_id,
|
session_id,
|
||||||
prompt,
|
prompt,
|
||||||
|
summary,
|
||||||
created_at as "created_at!: DateTime<Utc>",
|
created_at as "created_at!: DateTime<Utc>",
|
||||||
updated_at as "updated_at!: DateTime<Utc>"
|
updated_at as "updated_at!: DateTime<Utc>"
|
||||||
FROM executor_sessions
|
FROM executor_sessions
|
||||||
@@ -91,6 +95,7 @@ impl ExecutorSession {
|
|||||||
execution_process_id as "execution_process_id!: Uuid",
|
execution_process_id as "execution_process_id!: Uuid",
|
||||||
session_id,
|
session_id,
|
||||||
prompt,
|
prompt,
|
||||||
|
summary,
|
||||||
created_at as "created_at!: DateTime<Utc>",
|
created_at as "created_at!: DateTime<Utc>",
|
||||||
updated_at as "updated_at!: DateTime<Utc>"
|
updated_at as "updated_at!: DateTime<Utc>"
|
||||||
FROM executor_sessions
|
FROM executor_sessions
|
||||||
@@ -113,16 +118,17 @@ impl ExecutorSession {
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
ExecutorSession,
|
ExecutorSession,
|
||||||
r#"INSERT INTO executor_sessions (
|
r#"INSERT INTO executor_sessions (
|
||||||
id, task_attempt_id, execution_process_id, session_id, prompt,
|
id, task_attempt_id, execution_process_id, session_id, prompt, summary,
|
||||||
created_at, updated_at
|
created_at, updated_at
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
RETURNING
|
RETURNING
|
||||||
id as "id!: Uuid",
|
id as "id!: Uuid",
|
||||||
task_attempt_id as "task_attempt_id!: Uuid",
|
task_attempt_id as "task_attempt_id!: Uuid",
|
||||||
execution_process_id as "execution_process_id!: Uuid",
|
execution_process_id as "execution_process_id!: Uuid",
|
||||||
session_id,
|
session_id,
|
||||||
prompt,
|
prompt,
|
||||||
|
summary,
|
||||||
created_at as "created_at!: DateTime<Utc>",
|
created_at as "created_at!: DateTime<Utc>",
|
||||||
updated_at as "updated_at!: DateTime<Utc>""#,
|
updated_at as "updated_at!: DateTime<Utc>""#,
|
||||||
session_id,
|
session_id,
|
||||||
@@ -130,8 +136,9 @@ impl ExecutorSession {
|
|||||||
data.execution_process_id,
|
data.execution_process_id,
|
||||||
None::<String>, // session_id initially None until parsed from output
|
None::<String>, // session_id initially None until parsed from output
|
||||||
data.prompt,
|
data.prompt,
|
||||||
now, // created_at
|
None::<String>, // summary initially None
|
||||||
now // updated_at
|
now, // created_at
|
||||||
|
now // updated_at
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
@@ -176,6 +183,25 @@ impl ExecutorSession {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update executor session summary
|
||||||
|
pub async fn update_summary(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
execution_process_id: Uuid,
|
||||||
|
summary: &str,
|
||||||
|
) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"UPDATE executor_sessions
|
||||||
|
SET summary = $1, updated_at = datetime('now')
|
||||||
|
WHERE execution_process_id = $2"#,
|
||||||
|
summary,
|
||||||
|
execution_process_id
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Delete executor sessions for a task attempt (cleanup)
|
/// Delete executor sessions for a task attempt (cleanup)
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn delete_by_task_attempt_id(
|
pub async fn delete_by_task_attempt_id(
|
||||||
|
|||||||
@@ -94,11 +94,11 @@ export type CreateExecutionProcess = { task_attempt_id: string, process_type: Ex
|
|||||||
|
|
||||||
export type UpdateExecutionProcess = { status: ExecutionProcessStatus | null, exit_code: bigint | null, completed_at: string | null, };
|
export type UpdateExecutionProcess = { status: ExecutionProcessStatus | null, exit_code: bigint | null, completed_at: string | null, };
|
||||||
|
|
||||||
export type ExecutorSession = { id: string, task_attempt_id: string, execution_process_id: string, session_id: string | null, prompt: string | null, created_at: string, updated_at: string, };
|
export type ExecutorSession = { id: string, task_attempt_id: string, execution_process_id: string, session_id: string | null, prompt: string | null, summary: string | null, created_at: string, updated_at: string, };
|
||||||
|
|
||||||
export type CreateExecutorSession = { task_attempt_id: string, execution_process_id: string, prompt: string | null, };
|
export type CreateExecutorSession = { task_attempt_id: string, execution_process_id: string, prompt: string | null, };
|
||||||
|
|
||||||
export type UpdateExecutorSession = { session_id: string | null, prompt: string | null, };
|
export type UpdateExecutorSession = { session_id: string | null, prompt: string | null, summary: string | null, };
|
||||||
|
|
||||||
// Generated constants
|
// Generated constants
|
||||||
export const EXECUTOR_TYPES: string[] = [
|
export const EXECUTOR_TYPES: string[] = [
|
||||||
|
|||||||
Reference in New Issue
Block a user