diff --git a/data/scripts/o b/data/scripts/o index 01c8d54..c18811f 100755 --- a/data/scripts/o +++ b/data/scripts/o @@ -36,7 +36,7 @@ ROOT_ENTRIES="session elevate agents backends models help ctl eventwait complete SESSION_FILES="env paused connected ctl name id elevate" # Agent-level files (exist under session/{name}/agent/{name}/) -AGENT_FILES="prompt prompt.prev fifo.in fifo.out chat connection state statewait log offset plan cfg ctl cwd usage cost ctxsz models systemprompt context tail tools name id proc" +AGENT_FILES="prompt prompt.prev fifo.in fifo.out chat chat.raw connection state statewait log offset plan cfg ctl cwd usage cost ctxsz models systemprompt context tail tools name id proc" is_root_path() { local path="$1" @@ -523,7 +523,7 @@ cmd_tui() { # Pane 0: chat (top, 80%) - show last 1000 lines of log, then tail chat tmux -CC new-session -d -s "$tmux_session" -c "$HOME" \ -e "session=$s" -e "agent=$a" - tmux send-keys -t "$tmux_session" "o log -n 1000 | grep -v '\\[\\[\\[.*\\]\\]\\]'; o chatstream | grep -v '\\[\\[\\[.*\\]\\]\\]'" Enter + tmux send-keys -t "$tmux_session" "o log -n 1000; o chatstream" Enter # Pane 1: prompt REPL (bottom, 20% height) tmux split-window -t "$tmux_session" -v -l 20% \ diff --git a/fs/handlers.go b/fs/handlers.go index ac6ddb9..ff9674e 100644 --- a/fs/handlers.go +++ b/fs/handlers.go @@ -1,6 +1,7 @@ package fs import ( + "bytes" "context" "encoding/json" "fmt" @@ -493,12 +494,77 @@ func readAgentChat(ctx HandlerCtx) ([]byte, error) { return data, nil } +func readAgentChatText(ctx HandlerCtx) ([]byte, error) { + ctx.AgentLog.mu.RLock() + defer ctx.AgentLog.mu.RUnlock() + return stripMarkers(ctx.AgentLog.log), nil +} + func streamAgentChat(ctx HandlerCtx, cctx context.Context, base string) ([]byte, string, error) { merged, cancel := mergeCtx(ctx, cctx) defer cancel() return streamChat(ctx.AgentLog, merged, base) } +func streamAgentChatText(ctx HandlerCtx, cctx context.Context, base string) ([]byte, string, error) { + merged, cancel := mergeCtx(ctx, cctx) + defer cancel() + data, nextBase, err := streamChat(ctx.AgentLog, merged, base) + if err != nil || len(data) == 0 { + return data, nextBase, err + } + return stripMarkers(data), nextBase, nil +} + +// stripMarkers removes [[[...]]] marker lines and source fence lines from raw +// chat data. Partial lines (no trailing \n) are always passed through — they +// are streaming content, never markers. +func stripMarkers(data []byte) []byte { + var out []byte + inFence := false + for len(data) > 0 { + // Find next newline + nl := bytes.IndexByte(data, '\n') + if nl < 0 { + // Partial line at end — always content, emit it + out = append(out, data...) + break + } + line := data[:nl+1] // includes \n + data = data[nl+1:] + + // Drop [[[...]]] marker lines + trimmed := bytes.TrimRight(line, "\n") + if len(trimmed) >= 7 && bytes.HasPrefix(trimmed, []byte("[[[")) && bytes.HasSuffix(trimmed, []byte("]]]")) { + continue + } + + // Drop fence open/close lines (``` with optional language suffix) + stripped := bytes.TrimSpace(trimmed) + if bytes.HasPrefix(stripped, []byte("```")) { + if inFence { + inFence = false + continue + } + // Opening fence — only drop if it's a source fence (tool output), + // not markdown fences in assistant prose. Tool fences follow + // immediately after a marker line, so if we just dropped a marker + // and this is the next line, it's a source fence. + // Simplification: drop all bare ``` lines (no content on the line). + // Keep fences that are part of assistant markdown (they have text before/after). + if len(stripped) == 3 || (len(stripped) > 3 && stripped[3] != '`') { + inFence = true + continue + } + } + + if !inFence { + out = append(out, line...) + } + } + return out +} + func readAgentConnection(ctx HandlerCtx) ([]byte, error) { if ctx.Session.ToolsConn() == nil { return nil, fmt.Errorf("agent tool server unavailable") diff --git a/fs/spec.go b/fs/spec.go index bbcf021..f5560ef 100644 --- a/fs/spec.go +++ b/fs/spec.go @@ -230,7 +230,14 @@ FileNode("generate", 0666, ), FileNode("chat", 0444, - Doc("Full conversation history. Supports streaming reads for real-time updates."), + Doc("Filtered text presentation of conversation. Strips markers and source fences for terminal display."), + Stat(statAgentChat), + Read(readAgentChatText), + Stream(streamAgentChatText), + ), + + FileNode("chat.raw", 0444, + Doc("Full conversation with block markers and fences. Used by GUIs."), Stat(statAgentChat), Read(readAgentChat), Stream(streamAgentChat), diff --git a/kde b/kde index eed4a10..26076b0 160000 --- a/kde +++ b/kde @@ -1 +1 @@ -Subproject commit eed4a1089edfffc897c4f1d1f2bfa9abed236785 +Subproject commit 26076b0ae96ebadb7a34518bda3b22b83dc6b2aa