feat: live visibility into what the agent is running (no more silent waiting)
Operator: "I'd like to be able to see in the chat what the agent is actually running, right now I just wait while nothing happens." Two compounding gaps: 1. The auto-continuation worker (cmd/nomos/continue.go) had zero live push — its result only appeared on a manual page reload, so approving a plan and watching the chat looked completely dead even while the agent was actively working. 2. Even with polling, continueSession only persisted ONE message at the very end of a continuation — a continuation that runs several tool calls before concluding would still show total silence for however long that took. Fixed both: - web/src/lib/stores/chat.ts: polls the current session's messages every 3s between turns (never while a live stream owns the message list) and merges in anything new. Started after a live turn ends and when a session loads; stopped on new-chat/session-switch. - cmd/nomos/store.go: insertMessageReturningID/updateMessage — lets a message be created as a placeholder and updated in place. - cmd/nomos/continue.go: continueSession now inserts a placeholder the instant it starts (renders as the existing "thinking" dots — immediate feedback that something is happening) and updates that SAME row after EVERY tool call, not just at the end. A poll within ~3s of any tool call landing shows it — individual `run` commands appear as the agent issues them, not just the final rolled-up summary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,34 @@ func (s *store) saveMessage(ctx context.Context, sessionID, role string, content
|
||||
return err
|
||||
}
|
||||
|
||||
// insertMessageReturningID and updateMessage exist for the auto-continuation
|
||||
// worker's live-progress persistence (see continue.go): rather than saving
|
||||
// one message only once the whole continuation finishes — which could be
|
||||
// several minutes of silence in the UI even though frontend polling exists —
|
||||
// the worker inserts a placeholder immediately and updates the SAME row as
|
||||
// each tool call completes, so a poller sees individual steps land, not just
|
||||
// a final rolled-up summary.
|
||||
func (s *store) insertMessageReturningID(ctx context.Context, sessionID, role string, content json.RawMessage) (uuid.UUID, error) {
|
||||
if s == nil {
|
||||
return uuid.Nil, nil
|
||||
}
|
||||
var id uuid.UUID
|
||||
err := s.pool.QueryRow(ctx,
|
||||
`INSERT INTO agent_messages (session_id, role, content) VALUES ($1, $2, $3) RETURNING id`,
|
||||
sessionID, role, truncateToolResults(content)).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *store) updateMessage(ctx context.Context, id uuid.UUID, content json.RawMessage) error {
|
||||
if s == nil || id == uuid.Nil {
|
||||
return nil
|
||||
}
|
||||
_, err := s.pool.Exec(ctx,
|
||||
`UPDATE agent_messages SET content = $2 WHERE id = $1`,
|
||||
id, truncateToolResults(content))
|
||||
return err
|
||||
}
|
||||
|
||||
func truncateToolResults(content json.RawMessage) json.RawMessage {
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(content, &m); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user