diff options
| -rw-r--r-- | Omni/Agent/Worker.hs | 43 | ||||
| -rw-r--r-- | Omni/Jr/Web/Handlers.hs | 2 | ||||
| -rw-r--r-- | Omni/Jr/Web/Partials.hs | 33 | ||||
| -rw-r--r-- | Omni/Jr/Web/Types.hs | 2 |
4 files changed, 60 insertions, 20 deletions
diff --git a/Omni/Agent/Worker.hs b/Omni/Agent/Worker.hs index 8adb7c2..a8bbfc9 100644 --- a/Omni/Agent/Worker.hs +++ b/Omni/Agent/Worker.hs @@ -107,16 +107,20 @@ processTask worker task = do activityId <- TaskCore.logActivityWithMetrics tid TaskCore.Running Nothing Nothing (Just startTime) Nothing Nothing Nothing say "[worker] Starting engine..." - (exitCode, output, costCents) <- runWithEngine worker repo task + engineResult <- runWithEngine worker repo task endTime <- Data.Time.getCurrentTime - say ("[worker] Agent exited with: " <> tshow exitCode) -- Update the activity record with metrics (convert Double to Int by rounding) + let costCents = case engineResult of + EngineSuccess _ c -> c + EngineGuardrailViolation _ c -> c + EngineError _ c -> c TaskCore.updateActivityMetrics activityId Nothing (Just endTime) (Just (round costCents)) Nothing - case exitCode of - Exit.ExitSuccess -> do + case engineResult of + EngineSuccess output _ -> do + say "[worker] Agent completed successfully" TaskCore.logActivity tid TaskCore.Reviewing Nothing say "[worker] Running formatters..." _ <- runFormatters repo @@ -170,9 +174,18 @@ processTask worker task = do TaskCore.updateTaskStatusWithActor tid TaskCore.Review [] TaskCore.Junior say ("[worker] ✓ Task " <> tid <> " -> Review") unless quiet <| AgentLog.update (\s -> s {AgentLog.statusTask = Nothing}) - Exit.ExitFailure code -> do - say ("[worker] Engine failed with code " <> tshow code) - TaskCore.logActivity tid TaskCore.Failed (Just (toMetadata [("exit_code", tshow code)])) + EngineGuardrailViolation errMsg _ -> do + say ("[worker] Guardrail violation: " <> errMsg) + TaskCore.logActivity tid TaskCore.Failed (Just (toMetadata [("reason", "guardrail_violation")])) + -- Add comment with guardrail details + _ <- TaskCore.addComment tid errMsg TaskCore.Junior + -- Set to NeedsHelp so human can review + TaskCore.updateTaskStatusWithActor tid TaskCore.NeedsHelp [] TaskCore.Junior + say ("[worker] Task " <> tid <> " -> NeedsHelp (guardrail violation)") + unless quiet <| AgentLog.update (\s -> s {AgentLog.statusTask = Nothing}) + EngineError errMsg _ -> do + say ("[worker] Engine error: " <> errMsg) + TaskCore.logActivity tid TaskCore.Failed (Just (toMetadata [("reason", "engine_error")])) -- Don't set back to Open here - leave in InProgress for debugging say "[worker] Task left in InProgress (engine failure)" @@ -211,9 +224,14 @@ tryCommit repo msg = do Exit.ExitFailure _ -> pure <| CommitFailed (Text.pack commitErr) Exit.ExitFailure c -> pure <| CommitFailed ("git diff failed with code " <> tshow c) +data EngineResult + = EngineSuccess Text Double -- output, cost + | EngineGuardrailViolation Text Double -- error message, cost + | EngineError Text Double -- error message, cost + -- | Run task using native Engine --- Returns (ExitCode, output text, cost in cents) -runWithEngine :: Core.Worker -> FilePath -> TaskCore.Task -> IO (Exit.ExitCode, Text, Double) +-- Returns engine result with output/error and cost +runWithEngine :: Core.Worker -> FilePath -> TaskCore.Task -> IO EngineResult runWithEngine worker repo task = do -- Read API key from environment maybeApiKey <- Env.lookupEnv "OPENROUTER_API_KEY" @@ -338,10 +356,13 @@ runWithEngine worker repo task = do totalCost <- readIORef totalCostRef case result of - Left err -> pure (Exit.ExitFailure 1, "Engine error: " <> err, totalCost) + Left err -> + if "Guardrail: " `Text.isPrefixOf` err + then pure (EngineGuardrailViolation err totalCost) + else pure (EngineError ("Engine error: " <> err) totalCost) Right agentResult -> do let output = Engine.resultFinalMessage agentResult - pure (Exit.ExitSuccess, output, totalCost) + pure (EngineSuccess output totalCost) -- | Build the base prompt for the agent buildBasePrompt :: TaskCore.Task -> Text -> FilePath -> Text diff --git a/Omni/Jr/Web/Handlers.hs b/Omni/Jr/Web/Handlers.hs index 463c9f7..da8a2e6 100644 --- a/Omni/Jr/Web/Handlers.hs +++ b/Omni/Jr/Web/Handlers.hs @@ -435,7 +435,7 @@ server = let isInProgress = case TaskCore.findTask tid tasks of Nothing -> False Just task -> TaskCore.taskStatus task == TaskCore.InProgress - pure (AgentEventsPartial events isInProgress now) + pure (AgentEventsPartial tid events isInProgress now) taskEventsStreamHandler :: Text -> Servant.Handler (SourceIO ByteString) taskEventsStreamHandler tid = do diff --git a/Omni/Jr/Web/Partials.hs b/Omni/Jr/Web/Partials.hs index 79c997e..9c0e870 100644 --- a/Omni/Jr/Web/Partials.hs +++ b/Omni/Jr/Web/Partials.hs @@ -15,8 +15,15 @@ import qualified Lucid import qualified Lucid.Base as Lucid import Numeric (showFFloat) import Omni.Jr.Web.Components - ( priorityBadgeWithForm, + ( aggregateCostMetrics, + commentForm, + formatCostHeader, + formatTokensHeader, + metaSep, + priorityBadgeWithForm, + renderAutoscrollToggle, renderListGroupItem, + renderLiveToggle, renderMarkdown, renderRelativeTimestamp, renderTimelineEvent, @@ -235,13 +242,25 @@ instance Lucid.ToHtml DescriptionEditPartial where instance Lucid.ToHtml AgentEventsPartial where toHtmlRaw = Lucid.toHtml - toHtml (AgentEventsPartial events isInProgress now) = do + toHtml (AgentEventsPartial tid events isInProgress now) = do + let nonCostEvents = filter (\e -> TaskCore.storedEventType e /= "Cost") events + eventCount = length nonCostEvents + (totalCents, totalTokens) = aggregateCostMetrics events Lucid.h3_ <| do - Lucid.toHtml ("Timeline (" <> tshow (length events) <> ")") - when isInProgress <| Lucid.span_ [Lucid.class_ "timeline-live"] " LIVE" - if null events + Lucid.toHtml ("Timeline (" <> tshow eventCount <> ")") + when (totalCents > 0 || totalTokens > 0) <| do + Lucid.span_ [Lucid.class_ "timeline-cost-summary"] <| do + metaSep + when (totalCents > 0) <| Lucid.toHtml (formatCostHeader totalCents) + when (totalCents > 0 && totalTokens > 0) <| metaSep + when (totalTokens > 0) <| Lucid.toHtml (formatTokensHeader totalTokens <> " tokens") + when isInProgress <| do + renderLiveToggle + renderAutoscrollToggle + if null nonCostEvents then Lucid.p_ [Lucid.class_ "empty-msg"] "No activity yet." else do Lucid.div_ [Lucid.class_ "timeline-events"] <| do - traverse_ (renderTimelineEvent now) events - timelineScrollScript + traverse_ (renderTimelineEvent now) nonCostEvents + when isInProgress <| timelineScrollScript + commentForm tid diff --git a/Omni/Jr/Web/Types.hs b/Omni/Jr/Web/Types.hs index c463bfa..025f3a6 100644 --- a/Omni/Jr/Web/Types.hs +++ b/Omni/Jr/Web/Types.hs @@ -287,7 +287,7 @@ newtype TaskListPartial = TaskListPartial [TaskCore.Task] data TaskMetricsPartial = TaskMetricsPartial Text [TaskCore.TaskActivity] (Maybe TaskCore.RetryContext) UTCTime -data AgentEventsPartial = AgentEventsPartial [TaskCore.StoredEvent] Bool UTCTime +data AgentEventsPartial = AgentEventsPartial Text [TaskCore.StoredEvent] Bool UTCTime data DescriptionViewPartial = DescriptionViewPartial Text Text Bool |
