Skip to content
Closed
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e5dc709
feat: wire retry and edit buttons on user messages
tellaho Apr 14, 2026
71a9323
fix: sync ChatInput text state when initialValue prop changes
tellaho Apr 14, 2026
47d1154
fix: focus textarea when entering edit mode
tellaho Apr 14, 2026
5eef949
feat: inline edit UX — edit message in-place with Save/Cancel
tellaho Apr 14, 2026
7486ec5
feat: full-width inline edit with hinted background and info message
tellaho Apr 14, 2026
0bdae1e
fix: stale closure, focus churn, dead draft write, dead i18n keys
tellaho Apr 14, 2026
4ece14d
style: user bubble with bg-muted on content div, max-w-640px, no avatar
tellaho Apr 14, 2026
b7dcb1a
refactor: replace CSS group-hover with Radix HoverCard for message ac…
tellaho Apr 14, 2026
5d6e8bf
fix: strip all visual chrome from HoverCard actions, add gap for overlap
tellaho Apr 14, 2026
536d623
fix: restore Radix open/close animations on HoverCard actions
tellaho Apr 14, 2026
18690e7
fix: add sideOffset to HoverCard actions for breathing room
tellaho Apr 14, 2026
6a8567e
test: add inline edit tests and fix hover test for HoverCard
tellaho Apr 14, 2026
553de5b
fix: guard handleSaveEdit against vanished messages
tellaho Apr 14, 2026
e5061bc
cleanup: HoverCard bare variant, dedup resize, gate edit state, es lo…
tellaho Apr 14, 2026
2348e1e
fix: increase bottom padding so last message hover actions aren't cli…
tellaho Apr 14, 2026
6456a76
fix: lower z-index on bare HoverCard so popovers/menus win
tellaho Apr 14, 2026
2eecade
style: restyle inline edit action bar — default buttons, flipped layout
tellaho Apr 14, 2026
79b2836
style: even padding on user message bubble
tellaho Apr 14, 2026
4f68bb6
fix: retry/edit preserve attachments & persona, remove MessageBranch …
tellaho Apr 14, 2026
143ab86
fix: address 3 review blockers — unify deferred sends, gate retry, na…
tellaho Apr 15, 2026
cbf6732
fix: preserve pathless browser-uploaded file attachments on retry/edit
tellaho Apr 15, 2026
913932c
fix: update retry/edit tests to match new acpSendMessage signature (n…
tellaho Apr 16, 2026
a5d12c2
feat: truncate backend conversation on edit/retry via _meta.truncate_…
tellaho Apr 16, 2026
39c6149
feat: wire truncate_before_message_id through frontend for edit/retry
tellaho Apr 17, 2026
def6cca
fix: sync message IDs between frontend and backend for edit/retry tru…
tellaho Apr 17, 2026
afa1c06
chore: downgrade truncation log from info to debug
tellaho Apr 17, 2026
c5aa09f
test: add unit tests for ThreadManager.truncate_from_message
tellaho Apr 17, 2026
87bfe5a
fix: IME composition guard and queued-send race in edit flow
tellaho Apr 17, 2026
4823251
fix: atomic truncation, precise boundary, and snapshot+restore
tellaho Apr 17, 2026
103865b
fix: use .catch() instead of try/catch for async snapshot restore
tellaho Apr 17, 2026
c7e96ed
fix: guard Escape key against IME composition in inline edit
tellaho Apr 17, 2026
becf37f
fix: sync local message ID to backend via acpSendMessage
tellaho Apr 17, 2026
da05d60
fix: separate user message ID from assistant notification preset ID
tellaho Apr 20, 2026
6e1d68d
debug: add tracing to on_prompt truncation path
tellaho Apr 20, 2026
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
37 changes: 37 additions & 0 deletions crates/goose-acp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,43 @@ impl GooseAcpAgent {
.get_session_agent(&thread_id, Some(cancel_token.clone()))
.await?;

// Handle edit/retry truncation — if the client requests it via _meta,
// truncate both storage layers before appending the new message.
// The two tables use different ID schemes (tmsg_ vs msg_), so we use
// a timestamp bridge: truncate thread_messages by message_id to get
// the timestamp, then truncate messages by that timestamp.
if let Some(truncate_id) = args
.meta
.as_ref()
.and_then(|m| m.get("truncate_before_message_id"))
.and_then(|v| v.as_str())
{
let (rows_deleted, created_ts) = self
.thread_manager
.truncate_from_message(&thread_id, truncate_id)
.await
Comment thread
tellaho marked this conversation as resolved.
.map_err(|e| {
sacp::Error::internal_error().data(format!("Failed to truncate thread: {}", e))
})?;

if created_ts > 0 {
self.session_manager
.truncate_conversation(&internal_session_id, created_ts)
.await
Comment thread
tellaho marked this conversation as resolved.
Outdated
.map_err(|e| {
sacp::Error::internal_error()
.data(format!("Failed to truncate session: {}", e))
})?;
}

tracing::info!(
thread_id = %thread_id,
truncate_id = %truncate_id,
rows_deleted = rows_deleted,
"Truncated conversation for edit/retry"
);
}

let user_message = self.convert_acp_prompt_to_message(args.prompt);

self.thread_manager
Expand Down
37 changes: 37 additions & 0 deletions crates/goose/src/session/thread_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,43 @@ impl ThreadManager {
self.get_thread(&new_id).await
}

/// Truncate a thread from a specific message onward (inclusive).
/// Returns (rows_deleted, created_timestamp) so callers can bridge
/// into session-level truncation which uses timestamps.
pub async fn truncate_from_message(
&self,
thread_id: &str,
message_id: &str,
) -> Result<(u64, i64)> {
let pool = self.storage.pool().await?;

// Find the target message's autoincrement id and timestamp
let row = sqlx::query_as::<_, (i64, i64)>(
"SELECT id, created_timestamp FROM thread_messages WHERE thread_id = ? AND message_id = ? LIMIT 1",
)
.bind(thread_id)
.bind(message_id)
.fetch_optional(pool)
.await?;

let Some((row_id, created_ts)) = row else {
return Ok((0, 0));
};

let result = sqlx::query("DELETE FROM thread_messages WHERE thread_id = ? AND id >= ?")
.bind(thread_id)
.bind(row_id)
.execute(pool)
.await?;

sqlx::query("UPDATE threads SET updated_at = CURRENT_TIMESTAMP WHERE id = ?")
.bind(thread_id)
.execute(pool)
.await?;

Ok((result.rows_affected(), created_ts))
}

pub async fn list_messages(&self, thread_id: &str) -> Result<Vec<Message>> {
let pool = self.storage.pool().await?;
let rows = sqlx::query_as::<_, (Option<String>, String, Option<String>, String, i64, String)>(
Expand Down
24 changes: 22 additions & 2 deletions ui/goose2/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,29 @@ const EXCEPTIONS = {
"Drag-and-drop handlers for session-to-project moves and project reorder, plus activeProjectId highlight.",
},
"src/features/chat/ui/ChatView.tsx": {
limit: 535,
limit: 600,
justification:
"ACP prewarm guards, project-aware working dir selection, working context sync, and chat bootstrapping still live together here.",
"ACP prewarm guards, project-aware working dir selection, working context sync, chat bootstrapping, and inline edit/retry orchestration still live together here.",
},
"src/features/chat/hooks/__tests__/useChat.test.ts": {
limit: 840,
justification:
"Edit/retry integration tests cover attachment preservation, persona round-tripping, truncation, and draft lifecycle in one cohesive suite.",
},
"src/features/chat/stores/chatStore.ts": {
limit: 520,
justification:
"Edit-mode draft state, per-session editing message tracking, and cleanup logic added for inline edit/retry.",
},
"src/features/chat/ui/MessageBubble.tsx": {
limit: 610,
justification:
"Inline edit textarea, save/cancel controls, and attachment re-display for edit/retry colocated with message rendering.",
},
"src/features/chat/ui/__tests__/MessageBubble.test.tsx": {
limit: 560,
justification:
"Edit/retry button visibility, inline edit save/cancel, and attachment preservation tests added to existing bubble suite.",
},
"src/features/chat/ui/__tests__/ContextPanel.test.tsx": {
limit: 550,
Expand Down
Loading
Loading