From 11ee0b44397ff5f58a11a105883c07a39d49bfa3 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Mon, 1 Dec 2025 18:59:04 -0500 Subject: Fix timeline partial to include cost/token metrics and controls The HTMX-refreshed AgentEventsPartial was missing: - Cost/token summary in header - Live toggle button - Autoscroll toggle button - Comment form Now matches the full page renderUnifiedTimeline output. --- Omni/Agent/Worker.hs | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) (limited to 'Omni/Agent') 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 -- cgit v1.2.3