summaryrefslogtreecommitdiff
path: root/Omni/Agent/Log.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-22 17:07:32 -0500
committerBen Sima <ben@bensima.com>2025-11-22 17:08:11 -0500
commit832a0a7d88d0553e7edf055addb2c3a6f9f492ab (patch)
tree805e0aa0e174f2a3c61cb44fb3fe71562d77af2b /Omni/Agent/Log.hs
parent3e232940a6769cc2a238dc7b41b7c7b215295963 (diff)
parentbb15513a94140c22aa3aea510314f60c94df4d97 (diff)
task: complete t-1o2bxd11zv9 (Merge)
https: //ampcode.com/threads/T-ca3b086b-5a85-422a-b13d-256784c04221 Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-ca3b086b-5a85-422a-b13d-256784c04221
Diffstat (limited to 'Omni/Agent/Log.hs')
-rw-r--r--Omni/Agent/Log.hs213
1 files changed, 83 insertions, 130 deletions
diff --git a/Omni/Agent/Log.hs b/Omni/Agent/Log.hs
index dd66abc..07770d0 100644
--- a/Omni/Agent/Log.hs
+++ b/Omni/Agent/Log.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}
@@ -7,34 +6,22 @@
module Omni.Agent.Log where
import Alpha
-import Data.Aeson (Value (..), decode)
-import qualified Data.Aeson.KeyMap as KM
+import Data.Aeson ((.:), (.:?))
+import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Lazy as BL
import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)
-import qualified Data.Text.Encoding as TextEnc
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as TE
import qualified Data.Text.IO as TIO
-import qualified Data.Vector as V
import qualified System.Console.ANSI as ANSI
import qualified System.IO as IO
import System.IO.Unsafe (unsafePerformIO)
-import Text.Printf (printf)
-
--- | Parsed log entry
-data LogEntry = LogEntry
- { leMessage :: Maybe Text,
- leLevel :: Maybe Text,
- leToolName :: Maybe Text,
- leBatches :: Maybe [[Text]],
- leMethod :: Maybe Text,
- lePath :: Maybe Text
- }
- deriving (Show, Eq)
-- | Status of the agent for the UI
data Status = Status
{ statusWorker :: Text,
statusTask :: Maybe Text,
- statusThreadId :: Maybe Text,
+ statusThread :: Maybe Text,
statusFiles :: Int,
statusCredits :: Double,
statusTime :: Text, -- formatted time string
@@ -47,7 +34,7 @@ emptyStatus workerName =
Status
{ statusWorker = workerName,
statusTask = Nothing,
- statusThreadId = Nothing,
+ statusThread = Nothing,
statusFiles = 0,
statusCredits = 0.0,
statusTime = "00:00",
@@ -64,9 +51,10 @@ init :: Text -> IO ()
init workerName = do
IO.hSetBuffering IO.stderr IO.LineBuffering
writeIORef currentStatus (emptyStatus workerName)
- -- Reserve 5 lines at bottom
- replicateM_ 5 (IO.hPutStrLn IO.stderr "")
- ANSI.hCursorUp IO.stderr 5
+ -- Reserve 2 lines at bottom
+ IO.hPutStrLn IO.stderr ""
+ IO.hPutStrLn IO.stderr ""
+ ANSI.hCursorUp IO.stderr 2
-- | Update the status
update :: (Status -> Status) -> IO ()
@@ -78,96 +66,14 @@ update f = do
updateActivity :: Text -> IO ()
updateActivity msg = update (\s -> s {statusActivity = msg})
--- | Process a log line from the agent and update status if relevant
-processLogLine :: Text -> IO ()
-processLogLine line = do
- let entry = parseLine line
- for_ (entry +> formatLogEntry) updateActivity
-
--- | Parse a JSON log line into a LogEntry
-parseLine :: Text -> Maybe LogEntry
-parseLine line = do
- let lbs = BL.fromStrict (TextEnc.encodeUtf8 line)
- obj <- decode lbs
- case obj of
- Object o ->
- Just
- LogEntry
- { leMessage = getString "message" o,
- leLevel = getString "level" o,
- leToolName = getString "toolName" o,
- leBatches = getBatches o,
- leMethod = getString "method" o,
- lePath = getString "path" o
- }
- _ -> Nothing
- where
- getString k o =
- case KM.lookup k o of
- Just (String s) -> Just s
- _ -> Nothing
-
- getBatches o =
- case KM.lookup "batches" o of
- Just (Array b) ->
- Just
- <| mapMaybe
- ( \case
- Array b0 ->
- Just
- <| mapMaybe
- ( \case
- String s -> Just s
- _ -> Nothing
- )
- (V.toList b0)
- _ -> Nothing
- )
- (V.toList b)
- _ -> Nothing
-
--- | Format a log entry into a user-friendly status message (NO EMOJIS)
-formatLogEntry :: LogEntry -> Maybe Text
-formatLogEntry LogEntry {..} =
- case leMessage of
- Just "executing 1 tools in 1 batch(es)" -> do
- let tools = fromMaybe [] leBatches
- let firstTool = case tools of
- ((t : _) : _) -> t
- _ -> "unknown"
- Just ("THOUGHT: Planning tool execution (" <> firstTool <> ")")
- Just "Tool Bash permitted - action: allow" ->
- Just "TOOL: Bash command executed"
- Just "Processing tool completion for ledger"
- | isJust leToolName ->
- Just ("TOOL: " <> fromMaybe "unknown" leToolName <> " completed")
- Just "ide-fs" | leMethod == Just "readFile" ->
- case lePath of
- Just p -> Just ("READ: " <> p)
- _ -> Nothing
- Just "System prompt build complete (no changes)" ->
- Just "THINKING..."
- Just "System prompt build complete (first build)" ->
- Just "STARTING new task context"
- Just msg
- | leLevel == Just "error" ->
- Just ("ERROR: " <> msg)
- _ -> Nothing
-
-- | Log a scrolling message (appears above status bars)
log :: Text -> IO ()
log msg = do
- -- Clear status bars (5 lines)
- ANSI.hClearLine IO.stderr
- ANSI.hCursorDown IO.stderr 1
- ANSI.hClearLine IO.stderr
- ANSI.hCursorDown IO.stderr 1
- ANSI.hClearLine IO.stderr
- ANSI.hCursorDown IO.stderr 1
+ -- Clear status bars
ANSI.hClearLine IO.stderr
ANSI.hCursorDown IO.stderr 1
ANSI.hClearLine IO.stderr
- ANSI.hCursorUp IO.stderr 4
+ ANSI.hCursorUp IO.stderr 1
-- Print message (scrolls screen)
TIO.hPutStrLn IO.stderr msg
@@ -176,43 +82,90 @@ log msg = do
-- (Since we scrolled, we are now on the line above where the first status line should be)
render
--- | Render the 5 status lines (Vertical Layout)
+-- | Render the two status lines
render :: IO ()
render = do
Status {..} <- readIORef currentStatus
+ -- Line 1: Meta
+ -- [Worker: name] Task: t-123 | Thread: T-abc | Files: 3 | Credits: $0.45 | Time: 05:23
let taskStr = maybe "None" identity statusTask
- threadStr = maybe "None" identity statusThreadId
+ threadStr = maybe "None" identity statusThread
+ meta =
+ "[Worker: "
+ <> statusWorker
+ <> "] Task: "
+ <> taskStr
+ <> " | Thread: "
+ <> threadStr
+ <> " | Files: "
+ <> tshow statusFiles
+ <> " | Credits: $"
+ <> tshow statusCredits
+ <> " | Time: "
+ <> statusTime
- -- Line 1: Worker + Time
ANSI.hSetCursorColumn IO.stderr 0
ANSI.hClearLine IO.stderr
- TIO.hPutStr IO.stderr <| "Worker: " <> statusWorker <> " | Time: " <> statusTime
+ TIO.hPutStr IO.stderr meta
- -- Line 2: Task
+ -- Line 2: Activity
+ -- [14:05:22] > Thinking...
ANSI.hCursorDown IO.stderr 1
ANSI.hSetCursorColumn IO.stderr 0
ANSI.hClearLine IO.stderr
- TIO.hPutStr IO.stderr <| "Task: " <> taskStr
+ TIO.hPutStr IO.stderr ("> " <> statusActivity)
- -- Line 3: Thread
- ANSI.hCursorDown IO.stderr 1
- ANSI.hSetCursorColumn IO.stderr 0
- ANSI.hClearLine IO.stderr
- TIO.hPutStr IO.stderr <| "Thread: " <> threadStr
+ -- Return cursor to line 1
+ ANSI.hCursorUp IO.stderr 1
+ IO.hFlush IO.stderr
- -- Line 4: Credits
- ANSI.hCursorDown IO.stderr 1
- ANSI.hSetCursorColumn IO.stderr 0
- ANSI.hClearLine IO.stderr
- TIO.hPutStr IO.stderr <| "Credits: $" <> str (printf "%.2f" statusCredits :: String)
+-- | Log Entry from JSON
+data LogEntry = LogEntry
+ { leMessage :: Text,
+ leThreadId :: Maybe Text,
+ leCredits :: Maybe Double,
+ leTotalCredits :: Maybe Double,
+ leTimestamp :: Maybe Text
+ }
+ deriving (Show, Eq)
- -- Line 5: Activity
- ANSI.hCursorDown IO.stderr 1
- ANSI.hSetCursorColumn IO.stderr 0
- ANSI.hClearLine IO.stderr
- TIO.hPutStr IO.stderr ("> " <> statusActivity)
+instance Aeson.FromJSON LogEntry where
+ parseJSON =
+ Aeson.withObject "LogEntry" <| \v ->
+ LogEntry
+ </ v
+ .: "message"
+ <*> v
+ .:? "threadId"
+ <*> v
+ .:? "credits"
+ <*> v
+ .:? "totalCredits"
+ <*> v
+ .:? "timestamp"
+
+-- | Parse a log line and update status
+processLogLine :: Text -> IO ()
+processLogLine line = do
+ let bs = BL.fromStrict <| TE.encodeUtf8 line
+ case Aeson.decode bs of
+ Just entry -> update (updateFromEntry entry)
+ Nothing -> pure () -- Ignore invalid JSON
+
+updateFromEntry :: LogEntry -> Status -> Status
+updateFromEntry LogEntry {..} s =
+ s
+ { statusThread = leThreadId <|> statusThread s,
+ statusCredits = fromMaybe (statusCredits s) (leTotalCredits <|> leCredits),
+ statusTime = maybe (statusTime s) formatTime leTimestamp
+ }
- -- Return cursor to Line 1
- ANSI.hCursorUp IO.stderr 4
- IO.hFlush IO.stderr
+formatTime :: Text -> Text
+formatTime ts =
+ -- "2025-11-22T21:24:02.512Z" -> "21:24"
+ case Text.splitOn "T" ts of
+ [_, time] -> case Text.splitOn ":" time of
+ (h : m : _) -> h <> ":" <> m
+ _ -> ts
+ _ -> ts