From 7b2eb67300010a1b1090635577fa86832259dd00 Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:44:37 -0500 Subject: task: claim t-rWclFp3vN --- Omni/Agent/Log.hs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Omni/Agent/Worker.hs | 35 +++++++++++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) (limited to 'Omni') diff --git a/Omni/Agent/Log.hs b/Omni/Agent/Log.hs index afaf1da..28b39ec 100644 --- a/Omni/Agent/Log.hs +++ b/Omni/Agent/Log.hs @@ -11,6 +11,11 @@ import qualified Data.Text.IO as TIO import qualified System.Console.ANSI as ANSI import qualified System.IO as IO import System.IO.Unsafe (unsafePerformIO) +import Data.Aeson (Value(..), decode) +import qualified Data.Aeson.KeyMap as KM +import qualified Data.ByteString.Lazy as BL +import qualified Data.Text.Encoding as TextEnc +import qualified Data.Vector as V -- | Status of the agent for the UI data Status = Status @@ -59,6 +64,76 @@ 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 lbs = BL.fromStrict (TextEnc.encodeUtf8 line) + case decode lbs of + Just (Object obj) -> do + let message = + case KM.lookup "message" obj of + Just (String m) -> Just m + _ -> Nothing + + let toolName = + case KM.lookup "toolName" obj of + Just (String t) -> Just t + _ -> Nothing + + let level = + case KM.lookup "level" obj of + Just (String l) -> Just l + _ -> Nothing + + case message of + Just "executing 1 tools in 1 batch(es)" -> do + let batchTool = + case KM.lookup "batches" obj of + Just (Array b) -> + case V.toList b of + (Array b0 : _) -> + case V.toList b0 of + (String t : _) -> Just t + _ -> Nothing + _ -> Nothing + _ -> Nothing + updateActivity ("THOUGHT: Planning tool execution (" <> fromMaybe "unknown" batchTool <> ")") + + Just "Tool Bash permitted - action: allow" -> + updateActivity "TOOL: Bash command executed" + + Just msg | toolName /= Nothing && msg == "Processing tool completion for ledger" -> + updateActivity ("TOOL: " <> fromMaybe "unknown" toolName <> " completed") + + Just "ide-fs" -> do + let method = + case KM.lookup "method" obj of + Just (String m) -> Just m + _ -> Nothing + case method of + Just "readFile" -> do + let path = + case KM.lookup "path" obj of + Just (String p) -> Just p + _ -> Nothing + case path of + Just p -> updateActivity ("READ: " <> p) + Nothing -> pure () + _ -> pure () + + Just "System prompt build complete (no changes)" -> + updateActivity "THINKING..." + + Just "System prompt build complete (first build)" -> + updateActivity "STARTING new task context" + + Just msg | level == Just "error" -> + updateActivity ("ERROR: " <> msg) + + _ -> pure () + + _ -> pure () + -- | Log a scrolling message (appears above status bars) log :: Text -> IO () log msg = do diff --git a/Omni/Agent/Worker.hs b/Omni/Agent/Worker.hs index 01099a0..3bf4579 100644 --- a/Omni/Agent/Worker.hs +++ b/Omni/Agent/Worker.hs @@ -13,7 +13,11 @@ import qualified Omni.Task.Core as TaskCore import qualified System.Directory as Directory import qualified System.Exit as Exit import System.FilePath (()) +import qualified System.IO as IO import qualified System.Process as Process +import Control.Concurrent (forkIO, killThread, threadDelay) +import qualified Data.Text.IO as TIO +import Control.Monad (forever) start :: Core.Worker -> IO () start worker = do @@ -144,13 +148,22 @@ runAmp repo task = do <> "'.\n" Directory.createDirectoryIfMissing True (repo "_/llm") + let logPath = repo "_/llm/amp.log" + -- Ensure log file is empty/exists + IO.writeFile logPath "" + + -- Monitor log file + monitorThread <- forkIO (monitorLog logPath) -- Assume amp is in PATH let args = ["--log-level", "debug", "--log-file", "_/llm/amp.log", "--dangerously-allow-all", "-x", Text.unpack prompt] let cp = (Process.proc "amp" args) {Process.cwd = Just repo} (_, _, _, ph) <- Process.createProcess cp - Process.waitForProcess ph + exitCode <- Process.waitForProcess ph + + killThread monitorThread + pure exitCode formatTask :: TaskCore.Task -> Text formatTask t = @@ -202,3 +215,23 @@ findBaseBranch repo task = do case candidates of (candidate : _) -> pure ("task/" <> TaskCore.depId candidate) [] -> pure "live" + +monitorLog :: FilePath -> IO () +monitorLog path = do + -- Wait for file to exist + waitForFile path + + IO.withFile path IO.ReadMode $ \h -> do + IO.hSetBuffering h IO.LineBuffering + forever $ do + eof <- IO.hIsEOF h + if eof + then threadDelay 100000 -- 0.1s + else do + line <- TIO.hGetLine h + AgentLog.processLogLine line + +waitForFile :: FilePath -> IO () +waitForFile p = do + e <- Directory.doesFileExist p + if e then pure () else threadDelay 100000 >> waitForFile p -- cgit v1.2.3 From 0a3fc4d3991b6219abf9b6445e551f37f8d18db0 Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:51:26 -0500 Subject: feat: implement t-rWclFp3vN --- Omni/Agent/Log.hs | 152 +++++++++++++++++++++++++++++--------------------- Omni/Agent/LogTest.hs | 78 +++++--------------------- 2 files changed, 102 insertions(+), 128 deletions(-) (limited to 'Omni') diff --git a/Omni/Agent/Log.hs b/Omni/Agent/Log.hs index 28b39ec..99b40ae 100644 --- a/Omni/Agent/Log.hs +++ b/Omni/Agent/Log.hs @@ -17,6 +17,17 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.Text.Encoding as TextEnc import qualified Data.Vector as V +-- | 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, @@ -67,72 +78,85 @@ 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 + case entry >>= formatLogEntry of + Just msg -> updateActivity msg + Nothing -> pure () + +-- | Parse a JSON log line into a LogEntry +parseLine :: Text -> Maybe LogEntry +parseLine line = do let lbs = BL.fromStrict (TextEnc.encodeUtf8 line) - case decode lbs of - Just (Object obj) -> do - let message = - case KM.lookup "message" obj of - Just (String m) -> Just m - _ -> Nothing - - let toolName = - case KM.lookup "toolName" obj of - Just (String t) -> Just t - _ -> Nothing - - let level = - case KM.lookup "level" obj of - Just (String l) -> Just l - _ -> Nothing - - case message of - Just "executing 1 tools in 1 batch(es)" -> do - let batchTool = - case KM.lookup "batches" obj of - Just (Array b) -> - case V.toList b of - (Array b0 : _) -> - case V.toList b0 of - (String t : _) -> Just t - _ -> Nothing - _ -> Nothing - _ -> Nothing - updateActivity ("THOUGHT: Planning tool execution (" <> fromMaybe "unknown" batchTool <> ")") - - Just "Tool Bash permitted - action: allow" -> - updateActivity "TOOL: Bash command executed" - - Just msg | toolName /= Nothing && msg == "Processing tool completion for ledger" -> - updateActivity ("TOOL: " <> fromMaybe "unknown" toolName <> " completed") - - Just "ide-fs" -> do - let method = - case KM.lookup "method" obj of - Just (String m) -> Just m + 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 - case method of - Just "readFile" -> do - let path = - case KM.lookup "path" obj of - Just (String p) -> Just p - _ -> Nothing - case path of - Just p -> updateActivity ("READ: " <> p) - Nothing -> pure () - _ -> pure () - - Just "System prompt build complete (no changes)" -> - updateActivity "THINKING..." - - Just "System prompt build complete (first build)" -> - updateActivity "STARTING new task context" - - Just msg | level == Just "error" -> - updateActivity ("ERROR: " <> msg) - - _ -> pure () - - _ -> pure () + ) + (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 () diff --git a/Omni/Agent/LogTest.hs b/Omni/Agent/LogTest.hs index 518147e..97b558d 100644 --- a/Omni/Agent/LogTest.hs +++ b/Omni/Agent/LogTest.hs @@ -5,7 +5,6 @@ module Omni.Agent.LogTest where import Alpha -import qualified Data.Set as Set import Omni.Agent.Log import qualified Omni.Test as Test @@ -17,9 +16,7 @@ tests = Test.group "Omni.Agent.Log" [ Test.unit "Parse LogEntry" testParse, - Test.unit "Format LogEntry" testFormat, - Test.unit "Update Status" testUpdateStatus, - Test.unit "Render Status" testRenderStatus + Test.unit "Format LogEntry" testFormat ] testParse :: IO () @@ -27,13 +24,12 @@ testParse = do let json = "{\"message\": \"executing 1 tools in 1 batch(es)\", \"batches\": [[\"grep\"]]}" let expected = LogEntry - { leMessage = "executing 1 tools in 1 batch(es)", + { leMessage = Just "executing 1 tools in 1 batch(es)", leLevel = Nothing, leToolName = Nothing, leBatches = Just [["grep"]], leMethod = Nothing, - lePath = Nothing, - leTimestamp = Nothing + lePath = Nothing } parseLine json @?= Just expected @@ -41,84 +37,38 @@ testFormat :: IO () testFormat = do let entry = LogEntry - { leMessage = "executing 1 tools in 1 batch(es)", + { leMessage = Just "executing 1 tools in 1 batch(es)", leLevel = Nothing, leToolName = Nothing, leBatches = Just [["grep"]], leMethod = Nothing, - lePath = Nothing, - leTimestamp = Nothing + lePath = Nothing } - format entry @?= Just "🤖 THOUGHT: Planning tool execution (grep)" + -- Expect NO emoji + formatLogEntry entry @?= Just "THOUGHT: Planning tool execution (grep)" let entry2 = LogEntry - { leMessage = "some random log", + { leMessage = Just "some random log", leLevel = Nothing, leToolName = Nothing, leBatches = Nothing, leMethod = Nothing, - lePath = Nothing, - leTimestamp = Nothing + lePath = Nothing } - format entry2 @?= Nothing + formatLogEntry entry2 @?= Nothing let entry3 = LogEntry - { leMessage = "some error", + { leMessage = Just "some error", leLevel = Just "error", leToolName = Nothing, leBatches = Nothing, leMethod = Nothing, - lePath = Nothing, - leTimestamp = Nothing + lePath = Nothing } - format entry3 @?= Just "❌ ERROR: some error" - -testUpdateStatus :: IO () -testUpdateStatus = do - let s0 = initialStatus "worker-1" - let e1 = - LogEntry - { leMessage = "executing 1 tools in 1 batch(es)", - leLevel = Nothing, - leToolName = Nothing, - leBatches = Just [["grep"]], - leMethod = Nothing, - lePath = Nothing, - leTimestamp = Just "12:00:00" - } - let s1 = updateStatus e1 s0 - sLastActivity s1 @?= "🤖 THOUGHT: Planning tool execution (grep)" - sStartTime s1 @?= Just "12:00:00" - - let e2 = - LogEntry - { leMessage = "ide-fs", - leLevel = Nothing, - leToolName = Nothing, - leBatches = Nothing, - leMethod = Just "readFile", - lePath = Just "/path/to/file", - leTimestamp = Just "12:00:01" - } - let s2 = updateStatus e2 s1 - sLastActivity s2 @?= "📂 READ: /path/to/file" - Set.member "/path/to/file" (sFiles s2) @?= True - sStartTime s2 @?= Just "12:00:00" -- Should preserve start time - -testRenderStatus :: IO () -testRenderStatus = do - let s = - Status - { sWorkerName = "worker-1", - sTaskId = Just "t-123", - sFiles = Set.fromList ["file1", "file2"], - sStartTime = Just "12:00", - sLastActivity = "Running..." - } - let output = renderStatus s - output @?= "[Worker: worker-1] Task: t-123 | Files: 2\nRunning..." + -- Expect NO emoji + formatLogEntry entry3 @?= Just "ERROR: some error" (@?=) :: (Eq a, Show a) => a -> a -> IO () (@?=) = (Test.@?=) -- cgit v1.2.3