diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-27 10:59:38 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-27 10:59:38 -0500 |
| commit | 83ff4b622be49762491dac216ab8df374b24cd74 (patch) | |
| tree | 6f8d85b640716c43a309ae41e4bb61dd233bdfef /Omni/Task/Core.hs | |
| parent | 273208a8ffd714eb9cda51d557dbc62ff3009932 (diff) | |
Display worker metrics on task detail page
All tests pass. Let me summarize what was implemented:
- Extended `TaskActivity` type with new fields:
- `activityAmpThreadUrl` - Link to amp thread - `activityStartedAt` -
Work start timestamp - `activityCompletedAt` - Work completion
timestamp - `activityCostCents` - API cost in cents -
`activityTokensUsed` - Token usage count
- Updated `SQL.FromRow` and `SQL.ToRow` instances for the new fields -
Updated schema to include new columns in `task_activity` table - Added
`logActivityWithMetrics` function to log activities with all met -
Added `updateActivityMetrics` function to update metrics on existing
a - Added `getLatestRunningActivity` helper function
- Captures execution timing (start/end timestamps) - Retrieves amp
thread URL from `AgentLog.getStatus` - Converts credits to cents
and logs to activity record - Uses `logActivityWithMetrics` and
`updateActivityMetrics` for tracking
- Added `getStatus` function to retrieve current status (thread
URL, cre
- Added `TaskMetricsPartial` type for HTMX auto-refresh - Extended
`TaskDetailPage` to include `RetryContext` - Added Execution Details
section on task detail page showing:
- Amp Thread URL (clickable link) - Duration (formatted as "Xm Ys")
- Cost (formatted as "$X.XX") - Retry Attempt count (if applicable)
- Last Activity timestamp
- Added `/partials/task/:id/metrics` endpoint for HTMX auto-refresh
- Auto-refresh enabled while task is InProgress (every 5s) - Added
`renderExecutionDetails` helper function
- Added `executionDetailsStyles` for metric rows and execution section
- Added dark mode support for execution details section
Task-Id: t-148.4
Diffstat (limited to 'Omni/Task/Core.hs')
| -rw-r--r-- | Omni/Task/Core.hs | 74 |
1 files changed, 60 insertions, 14 deletions
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index 3a71900..ffecd60 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -93,7 +93,12 @@ data TaskActivity = TaskActivity activityTimestamp :: UTCTime, activityStage :: ActivityStage, activityMessage :: Maybe Text, - activityMetadata :: Maybe Text -- JSON for extra data + activityMetadata :: Maybe Text, -- JSON for extra data + activityAmpThreadUrl :: Maybe Text, -- Link to amp thread + activityStartedAt :: Maybe UTCTime, -- When work started + activityCompletedAt :: Maybe UTCTime, -- When work completed + activityCostCents :: Maybe Int, -- API cost in cents + activityTokensUsed :: Maybe Int -- Total tokens used } deriving (Show, Eq, Generic) @@ -241,6 +246,11 @@ instance SQL.FromRow TaskActivity where <*> SQL.field <*> SQL.field <*> SQL.field + <*> SQL.field + <*> SQL.field + <*> SQL.field + <*> SQL.field + <*> SQL.field instance SQL.ToRow TaskActivity where toRow a = @@ -249,7 +259,12 @@ instance SQL.ToRow TaskActivity where SQL.toField (activityTimestamp a), SQL.toField (activityStage a), SQL.toField (activityMessage a), - SQL.toField (activityMetadata a) + SQL.toField (activityMetadata a), + SQL.toField (activityAmpThreadUrl a), + SQL.toField (activityStartedAt a), + SQL.toField (activityCompletedAt a), + SQL.toField (activityCostCents a), + SQL.toField (activityTokensUsed a) ] -- | Case-insensitive ID comparison @@ -352,6 +367,11 @@ initTaskDb = do \ stage TEXT NOT NULL, \ \ message TEXT, \ \ metadata TEXT, \ + \ amp_thread_url TEXT, \ + \ started_at DATETIME, \ + \ completed_at DATETIME, \ + \ cost_cents INTEGER, \ + \ tokens_used INTEGER, \ \ FOREIGN KEY (task_id) REFERENCES tasks(id) \ \)" @@ -1011,21 +1031,47 @@ logActivity tid stage metadata = "INSERT INTO task_activity (task_id, stage, message, metadata) VALUES (?, ?, ?, ?)" (tid, show stage :: String, Nothing :: Maybe Text, metadata) +-- | Log activity with worker metrics (amp thread URL, timing, cost) +logActivityWithMetrics :: Text -> ActivityStage -> Maybe Text -> Maybe Text -> Maybe UTCTime -> Maybe UTCTime -> Maybe Int -> Maybe Int -> IO Int +logActivityWithMetrics tid stage metadata ampUrl startedAt completedAt costCents tokens = + withDb <| \conn -> do + SQL.execute + conn + "INSERT INTO task_activity (task_id, stage, message, metadata, amp_thread_url, started_at, completed_at, cost_cents, tokens_used) \ + \VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" + (tid, show stage :: String, Nothing :: Maybe Text, metadata, ampUrl, startedAt, completedAt, costCents, tokens) + [SQL.Only actId] <- SQL.query_ conn "SELECT last_insert_rowid()" :: IO [SQL.Only Int] + pure actId + +-- | Update an existing activity record with metrics +updateActivityMetrics :: Int -> Maybe Text -> Maybe UTCTime -> Maybe Int -> Maybe Int -> IO () +updateActivityMetrics actId ampUrl completedAt costCents tokens = + withDb <| \conn -> + SQL.execute + conn + "UPDATE task_activity SET amp_thread_url = COALESCE(?, amp_thread_url), \ + \completed_at = COALESCE(?, completed_at), \ + \cost_cents = COALESCE(?, cost_cents), \ + \tokens_used = COALESCE(?, tokens_used) \ + \WHERE id = ?" + (ampUrl, completedAt, costCents, tokens, actId) + -- | Get all activities for a task, ordered by timestamp descending getActivitiesForTask :: Text -> IO [TaskActivity] getActivitiesForTask tid = - withDb <| \conn -> do - rows <- - SQL.query - conn - "SELECT id, task_id, timestamp, stage, message, metadata \ - \FROM task_activity WHERE task_id = ? ORDER BY timestamp DESC" - (SQL.Only tid) :: - IO [(Int, Text, UTCTime, Text, Maybe Text, Maybe Text)] - pure [TaskActivity (Just i) taskId ts (readStage stg) msg meta | (i, taskId, ts, stg, msg, meta) <- rows] - where - readStage :: Text -> ActivityStage - readStage s = fromMaybe Claiming (readMaybe (T.unpack s)) + withDb <| \conn -> + SQL.query + conn + "SELECT id, task_id, timestamp, stage, message, metadata, \ + \amp_thread_url, started_at, completed_at, cost_cents, tokens_used \ + \FROM task_activity WHERE task_id = ? ORDER BY timestamp DESC" + (SQL.Only tid) + +-- | Get the most recent running activity for a task (for metrics display) +getLatestRunningActivity :: Text -> IO (Maybe TaskActivity) +getLatestRunningActivity tid = do + activities <- getActivitiesForTask tid + pure <| List.find (\a -> activityStage a == Running) activities -- | Get tasks with unmet blocking dependencies (not ready, not done) getBlockedTasks :: IO [Task] |
