commit f3779cd9a556defd5ce8c0bd0c92865437e9dba8
Author: Ben Sima <ben@bensima.com>
Date: Wed Dec 31 21:31:24 2025
Integrate Telegram Orchestrator with work_on_task tool
- Modified workOnTaskTool to take TaskToolContext with TelegramConfig, chatId, threadId
- Updated Telegram.hs to pass context to the tool
- Orchestrator now spawns as async thread and sends progress via Telegram
When work_on_task is called, it:
1. Validates the task exists and is workable
2. Spawns the orchestrator async (doesn't block chat)
3. Returns immediately with confirmation
4. Orchestrator posts status updates as it runs
Task-Id: t-298.2
diff --git a/Omni/Agent/Telegram.hs b/Omni/Agent/Telegram.hs
index 94533c8a..a08eb530 100644
--- a/Omni/Agent/Telegram.hs
+++ b/Omni/Agent/Telegram.hs
@@ -1322,7 +1322,14 @@ processEngagedMessage tgConfig provider engineCfg msg uid userName chatId userMe
<> [AvaLogs.searchChatHistoryTool]
taskTools =
if isBenAuthorized userName
- then [Tasks.workOnTaskTool, Tasks.listReadyTasksTool]
+ then
+ let taskCtx =
+ Tasks.TaskToolContext
+ { Tasks.ttcTelegramConfig = tgConfig,
+ Tasks.ttcChatId = chatId,
+ Tasks.ttcThreadId = Types.tmThreadId msg
+ }
+ in [Tasks.workOnTaskTool taskCtx, Tasks.listReadyTasksTool]
else []
tools = memoryTools <> searchTools <> webReaderTools <> pdfTools <> notesTools <> calendarTools <> todoTools <> messageTools <> hledgerTools <> emailTools <> pythonTools <> httpTools <> outreachTools <> feedbackTools <> fileTools <> skillsTools <> subagentToolList <> auditLogTools <> taskTools
diff --git a/Omni/Agent/Tools/Tasks.hs b/Omni/Agent/Tools/Tasks.hs
index c84a7e23..7faf3f15 100644
--- a/Omni/Agent/Tools/Tasks.hs
+++ b/Omni/Agent/Tools/Tasks.hs
@@ -5,16 +5,20 @@
-- | Task tools for the coding orchestrator.
--
-- Allows Ava to work on tasks from the task database by spawning
--- the pi-orchestrate coder/reviewer loop.
+-- the pi-orchestrate coder/reviewer loop with Telegram status updates.
--
-- : out omni-agent-tools-tasks
-- : dep aeson
+-- : dep async
-- : dep process
module Omni.Agent.Tools.Tasks
( -- * Tools
workOnTaskTool,
listReadyTasksTool,
+ -- * Context for tools
+ TaskToolContext (..),
+
-- * Testing
main,
test,
@@ -22,15 +26,22 @@ module Omni.Agent.Tools.Tasks
where
import Alpha
-import qualified Control.Concurrent as Concurrent
import Data.Aeson ((.:), (.=))
import qualified Data.Aeson as Aeson
import qualified Data.Text as Text
import qualified Omni.Agent.Engine as Engine
+import qualified Omni.Agent.Telegram.Orchestrator as Orchestrator
+import qualified Omni.Agent.Telegram.Types as TgTypes
import qualified Omni.Task.Core as Task
import qualified Omni.Test as Test
-import qualified System.Exit as Exit
-import qualified System.Process as Process
+
+-- | Context needed for task tools to send Telegram updates
+data TaskToolContext = TaskToolContext
+ { ttcTelegramConfig :: TgTypes.TelegramConfig,
+ ttcChatId :: Int,
+ ttcThreadId :: Maybe Int
+ }
+ deriving (Show)
-- | Arguments for work_on_task tool
newtype WorkOnTaskArgs = WorkOnTaskArgs
@@ -53,14 +64,16 @@ test =
[ Test.unit "placeholder" <| pure ()
]
--- | Tool to start working on a task
-workOnTaskTool :: Engine.Tool
-workOnTaskTool =
+-- | Tool to start working on a task.
+-- Takes a context so it can send Telegram status updates.
+workOnTaskTool :: TaskToolContext -> Engine.Tool
+workOnTaskTool ctx =
Engine.Tool
{ Engine.toolName = "work_on_task",
Engine.toolDescription =
"Start working on a coding task. This spawns the coder/reviewer loop "
<> "which will make changes, review them, and commit if approved. "
+ <> "Progress updates will be posted to the chat. "
<> "Use this when the user asks you to work on, implement, or fix a task. "
<> "The task ID should be in format 't-123' or 't-280.2.1'.",
Engine.toolJsonSchema =
@@ -76,11 +89,11 @@ workOnTaskTool =
],
"required" .= (["task_id"] :: [Text])
],
- Engine.toolExecute = executeWorkOnTask
+ Engine.toolExecute = executeWorkOnTask ctx
}
-executeWorkOnTask :: Aeson.Value -> IO Aeson.Value
-executeWorkOnTask v = do
+executeWorkOnTask :: TaskToolContext -> Aeson.Value -> IO Aeson.Value
+executeWorkOnTask ctx v = do
case Aeson.fromJSON v of
Aeson.Error e ->
pure <| Aeson.object ["error" .= Text.pack e]
@@ -101,55 +114,20 @@ executeWorkOnTask v = do
if status `notElem` [Task.Open, Task.InProgress, Task.Draft]
then pure <| Aeson.object ["error" .= ("Task " <> tid <> " is not in a workable state (status: " <> tshow status <> ")")]
else do
- -- Spawn orchestrator and check for early failures
- spawnResult <- spawnOrchestrator tid
- case spawnResult of
- Left err ->
- pure <| Aeson.object ["error" .= err]
- Right () ->
- pure
- <| Aeson.object
- [ "success" .= True,
- "task_id" .= tid,
- "title" .= Task.taskTitle task,
- "message" .= ("Started coder/reviewer loop for " <> tid <> ": " <> Task.taskTitle task)
- ]
-
--- | Spawn the orchestrator process and check for early failures.
--- Returns Left with error message if spawn fails or process exits immediately.
--- Returns Right () if process is still running after startup delay.
-spawnOrchestrator :: Text -> IO (Either Text ())
-spawnOrchestrator tid = do
- let scriptPath = "/home/ben/omni/ava/Omni/Ide/pi-orchestrate.sh"
- createProc =
- (Process.proc scriptPath [Text.unpack tid])
- { Process.std_out = Process.NoStream,
- Process.std_err = Process.NoStream,
- -- Detach from controlling terminal so it keeps running
- Process.new_session = True,
- Process.cwd = Just "/home/ben/omni/ava"
- }
-
- -- Try to create the process
- result <- try @SomeException (Process.createProcess createProc)
- case result of
- Left err ->
- pure <| Left ("Failed to spawn orchestrator: " <> tshow err)
- Right (_, _, _, ph) -> do
- -- Wait 500ms to catch immediate failures (missing script, permission errors, etc)
- Concurrent.threadDelay 500000
-
- -- Check if process exited early
- maybeExit <- Process.getProcessExitCode ph
- case maybeExit of
- Nothing ->
- -- Process still running - success!
- pure (Right ())
- Just Exit.ExitSuccess ->
- -- Exited immediately with success - suspicious but ok
- pure (Right ())
- Just (Exit.ExitFailure code) ->
- pure <| Left ("Orchestrator failed to start (exit code " <> tshow code <> ")")
+ -- Spawn orchestrator with Telegram updates
+ let orchConfig =
+ (Orchestrator.defaultConfig tid (ttcChatId ctx))
+ { Orchestrator.orchThreadId = ttcThreadId ctx
+ }
+ _ <- Orchestrator.spawnOrchestrator (ttcTelegramConfig ctx) orchConfig
+ -- Return immediately - orchestrator runs async
+ pure
+ <| Aeson.object
+ [ "success" .= True,
+ "task_id" .= tid,
+ "title" .= Task.taskTitle task,
+ "message" .= ("Started coder/reviewer loop for " <> tid <> ": " <> Task.taskTitle task <> ". Progress updates will appear in chat.")
+ ]
-- | Tool to list tasks ready for work
listReadyTasksTool :: Engine.Tool