diff options
Diffstat (limited to 'Omni/Agent/Worker.hs')
| -rw-r--r-- | Omni/Agent/Worker.hs | 96 |
1 files changed, 28 insertions, 68 deletions
diff --git a/Omni/Agent/Worker.hs b/Omni/Agent/Worker.hs index a861173..1cc0b8d 100644 --- a/Omni/Agent/Worker.hs +++ b/Omni/Agent/Worker.hs @@ -5,12 +5,8 @@ module Omni.Agent.Worker where import Alpha -import qualified Data.Aeson as Aeson -import qualified Data.Aeson.KeyMap as KM -import qualified Data.ByteString.Lazy as BL -import qualified Data.Scientific as Scientific import qualified Data.Text as Text -import qualified Data.Time as Time +import qualified Data.Text.IO as TIO import qualified Omni.Agent.Core as Core import qualified Omni.Agent.Git as Git import qualified Omni.Agent.Log as AgentLog @@ -93,7 +89,7 @@ processTask worker task = do -- Run Amp AgentLog.updateActivity "Running Amp agent..." - exitCode <- runAmp repo task + (exitCode, output) <- runAmp repo task case exitCode of Exit.ExitSuccess -> do @@ -103,8 +99,10 @@ processTask worker task = do TaskCore.updateTaskStatus tid TaskCore.Review [] -- Commit changes - -- We should check if there are changes, but 'git add .' is safe. - Git.commit repo ("feat: implement " <> tid) + -- We use the agent's output as the extended commit description + let summary = Text.strip output + let commitMsg = "feat: implement " <> tid <> "\n\n" <> summary + Git.commit repo commitMsg -- Submit for review AgentLog.updateActivity "Submitting for review..." @@ -127,7 +125,7 @@ processTask worker task = do AgentLog.updateActivity "Agent failed, retrying..." threadDelay (10 * 1000000) -- Sleep 10s -runAmp :: FilePath -> TaskCore.Task -> IO Exit.ExitCode +runAmp :: FilePath -> TaskCore.Task -> IO (Exit.ExitCode, Text) runAmp repo task = do let prompt = "You are a Worker Agent.\n" @@ -139,7 +137,8 @@ runAmp repo task = do <> "3. Run tests to verify your work (e.g., 'bild --test Omni/Namespace').\n" <> "4. Fix any errors found during testing.\n" <> "5. Do NOT update the task status or manage git branches (the system handles that).\n" - <> "6. When finished and tested, exit.\n\n" + <> "6. Do NOT run 'git commit'. The system will commit your changes automatically.\n" + <> "7. When finished and tested, exit.\n\n" <> "Context:\n" <> "- You are working in '" <> Text.pack repo @@ -149,7 +148,10 @@ runAmp repo task = do <> "'.\n" Directory.createDirectoryIfMissing True (repo </> "_/llm") - let logFile = repo </> "_/llm/amp.log" + let logPath = repo </> "_/llm/amp.log" + + -- Ensure log file is empty/exists + IO.writeFile logPath "" -- Read AGENTS.md agentsMd <- @@ -164,26 +166,19 @@ runAmp repo task = do <> "\n\nREPOSITORY GUIDELINES (AGENTS.md):\n" <> agentsMd - -- Clean up previous log - exists <- Directory.doesFileExist logFile - when exists (Directory.removeFile logFile) - - -- Start background monitors - tidTime <- forkIO timeTicker - tidLog <- forkIO (monitorLog logFile) + -- Monitor log file + tidLog <- forkIO (monitorLog logPath) -- Assume amp is in PATH let args = ["--log-level", "debug", "--log-file", "_/llm/amp.log", "--dangerously-allow-all", "-x", Text.unpack fullPrompt] let cp = (Process.proc "amp" args) {Process.cwd = Just repo} - (_, _, _, ph) <- Process.createProcess cp - exitCode <- Process.waitForProcess ph + (exitCode, out, _err) <- Process.readCreateProcessWithExitCode cp "" -- Cleanup - killThread tidTime killThread tidLog - pure exitCode + pure (exitCode, Text.pack out) formatTask :: TaskCore.Task -> Text formatTask t = @@ -237,56 +232,21 @@ findBaseBranch repo task = do [] -> pure "live" monitorLog :: FilePath -> IO () -monitorLog logPath = do - waitForFile logPath - IO.withFile logPath IO.ReadMode <| \h -> do - -- Start from beginning of file (don't seek to end) +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 <- IO.hGetLine h - parseAndUpdate (Text.pack line) + line <- TIO.hGetLine h + AgentLog.processLogLine line waitForFile :: FilePath -> IO () -waitForFile path = do - exists <- Directory.doesFileExist path - if exists - then pure () - else do - threadDelay 100000 - waitForFile path - -parseAndUpdate :: Text -> IO () -parseAndUpdate line = do - let maybeObj = Aeson.decode (BL.fromStrict (encodeUtf8 line)) :: Maybe Aeson.Object - case maybeObj of - Nothing -> pure () - Just obj -> do - -- Extract message (was msg) - case KM.lookup "message" obj of - Just (Aeson.String m) -> unless (Text.null m) (AgentLog.updateActivity m) - _ -> pure () - - -- Extract threadId - case KM.lookup "threadId" obj of - Just (Aeson.String tid) -> AgentLog.update (\s -> s {AgentLog.statusThreadId = Just tid}) - _ -> pure () - - -- Extract cost from usage-ledger:event - -- Pattern: {"totalCredits": 154.0, "message": "usage-ledger:event", ...} - -- The credits are in cents, so we divide by 100 to get dollars. - case KM.lookup "totalCredits" obj of - Just (Aeson.Number n) -> - let total = Scientific.toRealFloat n / 100.0 - in AgentLog.update (\s -> s {AgentLog.statusCredits = total}) - _ -> pure () - -timeTicker :: IO () -timeTicker = - forever <| do - time <- Time.getCurrentTime - let timeStr = Time.formatTime Time.defaultTimeLocale "%H:%M" time - AgentLog.update (\s -> s {AgentLog.statusTime = Text.pack timeStr}) - threadDelay 1000000 -- 1s +waitForFile p = do + e <- Directory.doesFileExist p + if e then pure () else threadDelay 100000 >> waitForFile p |
