Phase 5: reply drafts — template/LLM draft, edit, SMTP send (AUD-71)
Implements Phase 5 of AGENTS.md §11 (FR-8, FR-15): - Message detail pane (GET /partials/message) with body, recommended action, reply identity, template picker, editable draft, and history. - internal/reply: OmniRoute (OpenAI-compatible) chat client and a template-based draft generator. All non-JEV models go through OmniRoute (§10). - internal/mailops: SMTP send over smtp.gmail.com:587 with STARTTLS and the app password, forcing IPv4 (§9); FR-15 reply identity (send as the arrived-at X-Pm-Original-To address, else "Sam Rolfe"); threading via In-Reply-To/References; -check-smtp safe verification; and a [Gmail]/Sent Mail thread check. - Migration v5 stores messages.message_id_header and drafts.sent_at; mailfetch parses Message-ID. - Send is user-triggered only: CSRF + UI confirm + confirm=yes guard, no auto-send, duplicate-send refused. A sent reply files the original under Replied-to/<address> and logs every step (FR-13). - Tests for reply, mailops SMTP/identity/thread check, store drafts and migration v5, and the server draft/edit/send guards. - docs/phase5-replies.md, README, .env.example, docker-compose updated. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
71
internal/reply/templates.go
Normal file
71
internal/reply/templates.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Reply templates for Phase 5 (FR-8).
|
||||
//
|
||||
// A template is a short instruction that shapes the LLM draft. It is not the
|
||||
// final text: the model fills it in from the message, the classification, and
|
||||
// any extra note Sam types. Templates are built in for v1; a template editor is
|
||||
// not in the Phase 5 requirements.
|
||||
package reply
|
||||
|
||||
import "strings"
|
||||
|
||||
// Template is one reply shape.
|
||||
type Template struct {
|
||||
ID string
|
||||
Name string
|
||||
Description string
|
||||
// Instruction is the template-specific part of the prompt.
|
||||
Instruction string
|
||||
}
|
||||
|
||||
// Templates is the built-in template list, in menu order.
|
||||
var Templates = []Template{
|
||||
{
|
||||
ID: "quick-ack",
|
||||
Name: "Quick acknowledgement",
|
||||
Description: "Confirm receipt in one or two short sentences.",
|
||||
Instruction: "Acknowledge that the message was received. If the sender asked for something, say when Sam will look at it. Do not promise anything Sam has not agreed to.",
|
||||
},
|
||||
{
|
||||
ID: "answer",
|
||||
Name: "Answer the question",
|
||||
Description: "Answer the sender's question directly.",
|
||||
Instruction: "Answer the sender's question directly and briefly. If the message does not contain a clear question, summarise what Sam understood and ask one clarifying question.",
|
||||
},
|
||||
{
|
||||
ID: "confirm",
|
||||
Name: "Confirm an arrangement",
|
||||
Description: "Confirm a booking, time, or arrangement.",
|
||||
Instruction: "Confirm the arrangement in the message: restate the key detail (date, time, or reference) and say it works for Sam.",
|
||||
},
|
||||
{
|
||||
ID: "decline",
|
||||
Name: "Politely decline",
|
||||
Description: "Decline or say no thanks, politely.",
|
||||
Instruction: "Politely decline the request or offer. Thank the sender and keep it warm but brief. Do not over-explain.",
|
||||
},
|
||||
}
|
||||
|
||||
// TemplateByID returns the template with the given id. ok is false when it is
|
||||
// unknown. The id match is case-insensitive.
|
||||
func TemplateByID(id string) (Template, bool) {
|
||||
id = strings.ToLower(strings.TrimSpace(id))
|
||||
for _, t := range Templates {
|
||||
if t.ID == id {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return Template{}, false
|
||||
}
|
||||
|
||||
// TemplateForAction picks a sensible default template from the classifier's
|
||||
// recommended action. An unknown action falls back to a quick acknowledgement.
|
||||
func TemplateForAction(action string) Template {
|
||||
switch strings.ToLower(strings.TrimSpace(action)) {
|
||||
case "reply":
|
||||
t, _ := TemplateByID("answer")
|
||||
return t
|
||||
default:
|
||||
t, _ := TemplateByID("quick-ack")
|
||||
return t
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user