From 81fedb862ccca4b882c75aa60c4c7025970095bd Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 22 Apr 2026 16:34:06 +0200 Subject: [PATCH] fix: ensure chat log lines are newline-terminated assistant events are streamed without trailing newlines, causing line-buffered 9P reads to lag one turn behind. Close the assistant block with \n on role transitions and after Submit returns. Set ChatOffset at EOF before the user event so offset spans the full exchange. Remove 500-char truncation of tool call args in FormatEvent. --- pkg/store/batch.go | 16 ++++++++++++---- pkg/store/session.go | 10 ++++++++++ pkg/store/sessionfile.go | 32 +++++++++++++++++++++----------- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/pkg/store/batch.go b/pkg/store/batch.go index 4ebbcc4..6824eb6 100644 --- a/pkg/store/batch.go +++ b/pkg/store/batch.go @@ -373,13 +373,24 @@ func (s *BatchStore) executeJob(ctx context.Context, job *batchJob) (string, err assistantStarted = true } case "user", "call", "tool": - assistantStarted = false + if assistantStarted { + job.appendLog([]byte("\n")) + assistantStarted = false + } case "error": errBuf.WriteString(ev.Content) } job.appendLog(FormatEvent(ev)) }) + // Ensure log ends with newline (assistant content has no trailing \n). + job.mu.Lock() + if len(job.log) > 0 && job.log[len(job.log)-1] != '\n' { + job.log = append(job.log, '\n') + job.logVers++ + } + job.mu.Unlock() + usage := core.Usage() ctxsz := core.CtxSz() @@ -620,9 +631,6 @@ func FormatEvent(ev agent.Event) []byte { return []byte(ev.Content) case "call": args := squashWhitespace(ev.Content) - if len(args) > 500 { - args = args[:500] + "..." - } return []byte("-> " + ev.Name + "(" + args + ")\n") case "tool": return []byte(strings.TrimRight(ev.Content, "\n") + "\n") diff --git a/pkg/store/session.go b/pkg/store/session.go index 8cd7d31..d92516d 100644 --- a/pkg/store/session.go +++ b/pkg/store/session.go @@ -52,6 +52,16 @@ func (sess *Session) AppendLog(data []byte) { sess.mu.Unlock() } +// EnsureTrailingNewline appends a newline if the log doesn't already end with one. +func (sess *Session) EnsureTrailingNewline() { + sess.mu.Lock() + if len(sess.log) > 0 && sess.log[len(sess.log)-1] != '\n' { + sess.log = append(sess.log, '\n') + sess.logVers++ + } + sess.mu.Unlock() +} + // LogInfo returns the current log length and version atomically. func (sess *Session) LogInfo() (length int, vers uint32) { sess.mu.RLock() diff --git a/pkg/store/sessionfile.go b/pkg/store/sessionfile.go index 731a50b..c246807 100644 --- a/pkg/store/sessionfile.go +++ b/pkg/store/sessionfile.go @@ -159,7 +159,9 @@ func (s *SessionFileStore) writeFile(name string, data []byte) error { case "chat": return s.saveTranscript([]byte(input)) case "prompt": - s.sess.Core.Submit(s.sess.Ctx, input, s.makePublish()) + pub := s.makePublish() + s.sess.Core.Submit(s.sess.Ctx, input, pub) + s.sess.EnsureTrailingNewline() case "fifo.in": s.sess.Core.Queue(input) case "ctl": @@ -352,21 +354,29 @@ func (s *SessionFileStore) currentWaitValue(name string) string { func (s *SessionFileStore) makePublish() func(agent.Event) { assistantStarted := false return func(ev agent.Event) { - switch ev.Role { - case "user", "call", "tool": - assistantStarted = false - case "assistant": - if !assistantStarted { - s.sess.AppendLog([]byte("assistant: ")) - assistantStarted = true - } - } - s.sess.AppendLog(FormatEvent(ev)) if ev.Role == "user" { + if assistantStarted { + s.sess.AppendLog([]byte("\n")) + assistantStarted = false + } s.sess.mu.Lock() s.sess.ChatOffset = len(s.sess.log) s.sess.mu.Unlock() + } else { + switch ev.Role { + case "call", "tool": + if assistantStarted { + s.sess.AppendLog([]byte("\n")) + assistantStarted = false + } + case "assistant": + if !assistantStarted { + s.sess.AppendLog([]byte("assistant: ")) + assistantStarted = true + } + } } + s.sess.AppendLog(FormatEvent(ev)) } }