feat(email): add email_update tool for sending progress emails without ending agent loop

linuztx committed Mar 15, 2026 at 23:54 UTC 4ebb5f472cdb0a22b6891685087c3b698a6cc349
6 files changed +79 -7
plugins/_email_integration/extensions/python/system_prompt/_20_email_context.py
+6 -2
@@ -17,5 +17,9 @@ class EmailContextPrompt(Extension):
17 return
18
19 if self.agent.context.data.get(CTX_EMAIL_HANDLER):
20 - prompt = self.agent.read_prompt("fw.email.system_context_reply.md")
21 - system_prompt.append(prompt)
20 + system_prompt.append(
21 + self.agent.read_prompt("fw.email.system_context_reply.md")
22 + )
23 + system_prompt.append(
24 + self.agent.read_prompt("fw.email.tool_email_update.md")
25 + )
plugins/_email_integration/prompts/fw.email.system_context_reply.md
+7 -5
@@ -1,15 +1,17 @@
1 ## Email conversation
2 -you are in an email conversation, user communicates via email
2 +you are in an email conversation user communicates via email
3 your response (via response tool) will be sent as email reply automatically
4 -be concise, professional, clear
4 +be concise professional clear
5 do not include email headers or signatures — they are added automatically
6 -if task requires extended processing, provide brief status then continue work
6 +
7 +### Progress updates
8 +for long tasks use email_update tool to send progress emails without ending your task
9 +use response tool only for the final answer
10
11 ### Sending file attachments
9 -to attach files to your email reply, add `attachments` list with absolute file paths to the response tool:
12 +to attach files to your email reply add attachments list with absolute file paths:
13 ~~~json
14 {
12 - ...
15 "tool_name": "response",
16 "tool_args": {
17 "text": "Here is the file you requested.",
plugins/_email_integration/prompts/fw.email.tool_email_update.md new
+28
@@ -0,0 +1,28 @@
1 +### email_update
2 +send progress or status email to user without ending your task
3 +use when working on long tasks keep user informed while continuing work
4 +does NOT end your agentic loop use "response" tool for final answer
5 +usage:
6 +~~~json
7 +{
8 + ...
9 + "tool_name": "email_update",
10 + "tool_args": {
11 + "text": "... Completed step 1 of 3..."
12 + }
13 +}
14 +~~~
15 +
16 +optional attach files with attachments
17 +~~~json
18 +{
19 + ...
20 + "tool_name": "email_update",
21 + "tool_args": {
22 + "text": "... preliminary results ...",
23 + "attachments": [
24 + "/path/report.csv"
25 + ]
26 + }
27 +}
28 +~~~
plugins/_email_integration/prompts/fw.email.update_error.md new
+1
@@ -0,0 +1 @@
1 +email update failed: {{error}}
plugins/_email_integration/prompts/fw.email.update_ok.md new
+1
@@ -0,0 +1 @@
1 +email update sent, continue working
plugins/_email_integration/tools/email_update.py new
+36
@@ -0,0 +1,36 @@
1 +"""Send progress email without ending the agent loop."""
2 +
3 +from helpers.tool import Tool, Response
4 +from plugins._email_integration.helpers.dispatcher import CTX_EMAIL_HANDLER
5 +
6 +
7 +class EmailUpdate(Tool):
8 +
9 + async def execute(self, **kwargs) -> Response:
10 + if not self.agent.context.data.get(CTX_EMAIL_HANDLER):
11 + return Response(
12 + message=self.agent.read_prompt(
13 + "fw.email.update_error.md",
14 + error="not in an email session"),
15 + break_loop=False,
16 + )
17 +
18 + text = self.args.get("text", "")
19 + if not text:
20 + return Response(
21 + message=self.agent.read_prompt(
22 + "fw.email.update_error.md",
23 + error="text is required",
24 + ),
25 + break_loop=False,
26 + )
27 +
28 + attachments = list(self.args.get("attachments", []))
29 +
30 + from plugins._email_integration.helpers.handler import send_email_reply
31 + await send_email_reply(self.agent.context, text, attachments or None)
32 +
33 + return Response(
34 + message=self.agent.read_prompt("fw.email.update_ok.md"),
35 + break_loop=False,
36 + )