diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-26 08:53:53 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-26 08:53:53 -0500 |
| commit | c119ac2fa14d99a7ac5cd4cfe809e862d4d892db (patch) | |
| tree | 47e46a712c43504d2c34bebdee2865b157c6a9ac /Omni/Jr.hs | |
| parent | b8df9ce47e36b0c643701f07fd24b1e75c8389c5 (diff) | |
Improve jr loop logging and fix review race condition
- Reorder loop to check pending reviews before starting new work -
Loop no longer exits on missing commit (skips instead) - Add [loop],
[review], [worker] prefixes to all log messages - Worker leaves task
in InProgress on amp failure (avoids retry loop)
Diffstat (limited to 'Omni/Jr.hs')
| -rw-r--r-- | Omni/Jr.hs | 180 |
1 files changed, 111 insertions, 69 deletions
@@ -119,47 +119,116 @@ move args -- | Run the autonomous loop: work -> review -> repeat runLoop :: Int -> IO () runLoop delaySec = do - putText "Starting autonomous jr loop..." - putText ("Delay between iterations: " <> tshow delaySec <> "s") + putText "[loop] Starting autonomous jr loop..." + putText ("[loop] Delay between iterations: " <> tshow delaySec <> "s") go where go = do - -- Check for ready work - readyTasks <- TaskCore.getReadyTasks - case readyTasks of - [] -> do - putText "\nNo ready tasks. Checking for tasks to review..." - reviewPending - (task : _) -> do - putText ("\n=== Working on: " <> TaskCore.taskId task <> " ===") - -- Run worker - absPath <- Directory.getCurrentDirectory - let name = Text.pack (takeFileName absPath) - let worker = - AgentCore.Worker - { AgentCore.workerName = name, - AgentCore.workerPid = Nothing, - AgentCore.workerStatus = AgentCore.Idle, - AgentCore.workerPath = "." - } - AgentWorker.start worker (Just (TaskCore.taskId task)) - -- After work, check for review - reviewPending - - -- Delay and loop - putText ("\nSleeping " <> tshow delaySec <> "s...") - threadDelay (delaySec * 1000000) - go - + -- First check for tasks to review (prioritize finishing work) + reviewResult <- reviewPending + if reviewResult + then do + -- Reviewed something, continue loop immediately + threadDelay (delaySec * 1000000) + go + else do + -- No reviews, check for ready work + readyTasks <- TaskCore.getReadyTasks + case readyTasks of + [] -> do + putText "[loop] No ready tasks, no pending reviews." + (task : _) -> do + putText "" + putText ("[loop] === Working on: " <> TaskCore.taskId task <> " ===") + -- Run worker (this blocks until amp completes) + absPath <- Directory.getCurrentDirectory + let name = Text.pack (takeFileName absPath) + let worker = + AgentCore.Worker + { AgentCore.workerName = name, + AgentCore.workerPid = Nothing, + AgentCore.workerStatus = AgentCore.Idle, + AgentCore.workerPath = "." + } + putText "[loop] Starting worker..." + AgentWorker.start worker (Just (TaskCore.taskId task)) + putText "[loop] Worker finished." + + -- Delay and loop + putText ("[loop] Sleeping " <> tshow delaySec <> "s...") + threadDelay (delaySec * 1000000) + go + + -- Returns True if a task was reviewed, False otherwise + reviewPending :: IO Bool reviewPending = do tasks <- TaskCore.loadTasks let reviewTasks = filter (\t -> TaskCore.taskStatus t == TaskCore.Review) tasks case reviewTasks of - [] -> putText "No tasks pending review." + [] -> pure False (t : _) -> do - putText ("\n=== Auto-reviewing: " <> TaskCore.taskId t <> " ===") - reviewTask (TaskCore.taskId t) True + putText "" + putText ("[loop] === Reviewing: " <> TaskCore.taskId t <> " ===") + tryAutoReview (TaskCore.taskId t) + pure True + -- Auto-review that doesn't exit on missing commit + tryAutoReview :: Text -> IO () + tryAutoReview tid = do + tasks <- TaskCore.loadTasks + case TaskCore.findTask tid tasks of + Nothing -> do + putText ("[review] Task " <> tid <> " not found.") + Just task -> do + let grepArg = "--grep=" <> Text.unpack tid + (code, shaOut, _) <- + Process.readProcessWithExitCode + "git" + ["log", "--pretty=format:%H", "-n", "1", grepArg] + "" + + if code /= Exit.ExitSuccess || null shaOut + then do + putText "[review] No commit found for this task yet." + putText "[review] Worker may still be running or commit failed. Skipping." + else do + let commitSha = case List.lines shaOut of + (x : _) -> x + [] -> "" + + -- Check for merge conflicts + conflictResult <- checkMergeConflict commitSha + case conflictResult of + Just conflictFiles -> do + putText "[review] MERGE CONFLICT DETECTED" + traverse_ (\f -> putText (" - " <> f)) conflictFiles + handleConflict tid conflictFiles commitSha + Nothing -> do + autoReview tid task commitSha + +-- | Handle merge conflict during review +handleConflict :: Text -> [Text] -> String -> IO () +handleConflict tid conflictFiles commitSha = do + maybeCtx <- TaskCore.getRetryContext tid + let attempt = maybe 1 (\c -> TaskCore.retryAttempt c + 1) maybeCtx + + if attempt > 3 + then do + putText "[review] Task has failed 3 times. Needs human intervention." + TaskCore.updateTaskStatus tid TaskCore.Open [] + else do + TaskCore.setRetryContext + TaskCore.RetryContext + { TaskCore.retryTaskId = tid, + TaskCore.retryOriginalCommit = Text.pack commitSha, + TaskCore.retryConflictFiles = conflictFiles, + TaskCore.retryAttempt = attempt, + TaskCore.retryReason = "merge_conflict" + } + TaskCore.updateTaskStatus tid TaskCore.Open [] + putText ("[review] Task " <> tid <> " returned to queue (attempt " <> tshow attempt <> "/3).") + +-- | Interactive review command (jr review <task-id>) reviewTask :: Text -> Bool -> IO () reviewTask tid autoMode = do tasks <- TaskCore.loadTasks @@ -191,34 +260,8 @@ reviewTask tid autoMode = do case conflictResult of Just conflictFiles -> do putText "\n=== MERGE CONFLICT DETECTED ===" - putText "This commit cannot be cleanly applied to live." - putText "Conflicting files:" traverse_ (\f -> putText (" - " <> f)) conflictFiles - putText "" - putText "Kicking back to coder with context..." - - -- Get current retry count - maybeCtx <- TaskCore.getRetryContext tid - let attempt = maybe 1 (\c -> TaskCore.retryAttempt c + 1) maybeCtx - - if attempt > 3 - then do - putText "\nTask has failed 3 times. Marking as NeedsHuman." - -- For now, just mark as Open with a note (no NeedsHuman status yet) - TaskCore.updateTaskStatus tid TaskCore.Open [] - putText ("Task " <> tid <> " needs human intervention (3 failed attempts).") - else do - -- Save retry context - TaskCore.setRetryContext - TaskCore.RetryContext - { TaskCore.retryTaskId = tid, - TaskCore.retryOriginalCommit = Text.pack commitSha, - TaskCore.retryConflictFiles = conflictFiles, - TaskCore.retryAttempt = attempt, - TaskCore.retryReason = "merge_conflict" - } - TaskCore.updateTaskStatus tid TaskCore.Open [] - putText ("Task " <> tid <> " returned to queue (attempt " <> tshow attempt <> "/3).") + handleConflict tid conflictFiles commitSha Nothing -> do if autoMode then autoReview tid task commitSha @@ -227,14 +270,14 @@ reviewTask tid autoMode = do -- | Auto-review: run tests on namespace, accept if pass, reject if fail autoReview :: Text -> TaskCore.Task -> String -> IO () autoReview tid task commitSha = do - putText "Running automated review..." + putText "[review] Running automated review..." + putText ("[review] Commit: " <> Text.pack (take 8 commitSha)) -- Determine what to test based on namespace - -- Keep .hs suffix since bild expects it let namespace = fromMaybe "." (TaskCore.taskNamespace task) let testTarget = Text.unpack namespace - putText ("Testing: " <> Text.pack testTarget) + putText ("[review] Testing: " <> Text.pack testTarget) -- Run bild --test on the namespace (testCode, testOut, testErr) <- @@ -245,12 +288,12 @@ autoReview tid task commitSha = do case testCode of Exit.ExitSuccess -> do - putText "✓ Tests passed. Accepting task." + putText "[review] ✓ Tests passed." TaskCore.clearRetryContext tid TaskCore.updateTaskStatus tid TaskCore.Done [] - putText ("Task " <> tid <> " marked as Done.") - Exit.ExitFailure _ -> do - putText "✗ Tests failed. Rejecting task." + putText ("[review] Task " <> tid <> " -> Done") + Exit.ExitFailure code -> do + putText ("[review] ✗ Tests failed (exit " <> tshow code <> ")") let reason = "Test failure:\n" <> Text.pack testOut <> Text.pack testErr maybeCtx <- TaskCore.getRetryContext tid @@ -258,9 +301,8 @@ autoReview tid task commitSha = do if attempt > 3 then do - putText "\nTask has failed 3 times. Marking as NeedsHuman." + putText "[review] Task has failed 3 times. Needs human intervention." TaskCore.updateTaskStatus tid TaskCore.Open [] - putText ("Task " <> tid <> " needs human intervention (3 failed attempts).") else do TaskCore.setRetryContext TaskCore.RetryContext @@ -271,7 +313,7 @@ autoReview tid task commitSha = do TaskCore.retryReason = reason } TaskCore.updateTaskStatus tid TaskCore.Open [] - putText ("Task " <> tid <> " reopened (attempt " <> tshow attempt <> "/3).") + putText ("[review] Task " <> tid <> " reopened (attempt " <> tshow attempt <> "/3).") -- | Interactive review with user prompts interactiveReview :: Text -> String -> IO () |
