tâches planifiées : prompts cron persistés, outil scheduler, conversations liées aux tâches, rattrapage des exécutions manquées au redémarrage (go-cron MissedRunOnce)

This commit is contained in:
fabien committed 2026-10-06 22:15:59 +02:00
1 parent bedb6c98e7
commit 058370830f
71 files changed
+1627 -100

No files matched your search

+4 -2
View File
@@ -47,6 +47,7 @@ func (s *Service) NewChat(
userID string,
teamID string,
agentID string,
taskID string,
) (*domain.Chat, error) {
team, err := s.repoUser.FindTeam(userID, teamID)
@@ -60,6 +61,7 @@ func (s *Service) NewChat(
UserID: userID,
AgentID: agentID,
TeamID: team.ID,
TaskID: taskID,
StartDatetime: &start,
Title: "Nouvelle conversation",
}
@@ -170,8 +172,8 @@ func (s *Service) AddChatMessage(
return message, nil
}
func (s *Service) ListChats(userID string, teamID string, page int, query string) ([]*domain.Chat, error) {
return s.repoChat.ListChats(userID, teamID, page, query)
func (s *Service) ListChats(userID string, teamID string, page int, query string, taskID string) ([]*domain.Chat, error) {
return s.repoChat.ListChats(userID, teamID, page, query, taskID)
}
func (s *Service) GetChat(userID string, teamID, chatID string) (*domain.Chat, error) {
+49 -22
View File
@@ -3,6 +3,7 @@ package chat
import (
"context"
_ "embed"
"fmt"
"log/slog"
"sync"
@@ -87,43 +88,69 @@ func (s *ChatSession) run(ctx context.Context) error {
for _, tc := range msg.ToolCalls {
toolImpl := s.toolService.GetToolImpl(tc.Function.Name)
slog.Info("Execute tool", "name", tc.Function.Name, "impl", toolImpl)
toolRecord, err := s.toolService.GetToolByName(s.userID, s.teamID, toolImpl.Name())
if err != nil {
slog.Info("Execute tool", "error", err)
toolResponse := &domain.Message{
ChatID: s.chatID,
TeamID: s.teamID,
ToolCallID: tc.ID,
Role: string(domain.RoleTool),
Content: "ERROR: " + err.Error(),
var toolRecord *domain.Tool
var executeErr error
if toolImpl == nil {
executeErr = fmt.Errorf("unknown tool %s", tc.Function.Name)
slog.Info("Execute tool", "error", executeErr)
} else {
slog.Info("Execute tool", "name", tc.Function.Name, "impl", toolImpl)
var err error
toolRecord, err = s.toolService.GetToolByName(s.userID, s.teamID, toolImpl.Name())
if err != nil {
// l'outil n'est pas encore enregistré pour l'équipe :
// auto-enregistrement (comme le fait la liste des outils)
toolRecord, err = s.toolService.CreateTool(s.userID, &domain.Tool{
TeamID: s.teamID,
Name: toolImpl.Name(),
Enabled: true,
})
}
if err != nil {
executeErr = err
slog.Info("Execute tool", "error", err)
}
s.repoChat.CreateChatMessage(s.userID, toolResponse)
}
output, err := toolImpl.Execute(ctx, []byte(tc.Function.Arguments), toolRecord.Configuration)
var toolResponse *domain.Message
if err != nil {
if executeErr != nil {
toolResponse = &domain.Message{
ChatID: s.chatID,
TeamID: s.teamID,
ToolCallID: tc.ID,
Role: string(domain.RoleTool),
Content: "ERROR: " + err.Error(),
Content: "ERROR: " + executeErr.Error(),
}
} else {
toolResponse = &domain.Message{
ChatID: s.chatID,
TeamID: s.teamID,
ToolCallID: tc.ID,
Role: string(domain.RoleTool),
Content: string(output),
var output []byte
output, executeErr = toolImpl.Execute(
domain.WithToolContext(ctx, s.userID, s.teamID, s.modelID),
[]byte(tc.Function.Arguments),
toolRecord.Configuration,
)
if executeErr != nil {
toolResponse = &domain.Message{
ChatID: s.chatID,
TeamID: s.teamID,
ToolCallID: tc.ID,
Role: string(domain.RoleTool),
Content: "ERROR: " + executeErr.Error(),
}
} else {
toolResponse = &domain.Message{
ChatID: s.chatID,
TeamID: s.teamID,
ToolCallID: tc.ID,
Role: string(domain.RoleTool),
Content: string(output),
}
}
}
toolResponse, err = s.repoChat.CreateChatMessage(s.userID, toolResponse)
persisted, err := s.repoChat.CreateChatMessage(s.userID, toolResponse)
if err != nil {
return err
}
s.messages = append(s.messages, toolResponse)
s.messages = append(s.messages, persisted)
}
} else {
@@ -25,3 +25,7 @@ Never present a partial result as complete. If, despite your efforts, you cannot
Use the available tools and skills whenever they can help you answer more accurately, verify a fact, or complete the task. Don't answer from memory when a tool can provide the answer. If several tools are relevant, combine them. When a task matches an available skill, consult it before starting.
You may skip them for purely conversational exchanges or questions whose answer depends on no information that needs verifying.
## Scheduled tasks
When the user asks to perform something later or on a schedule ("do this every day at 8am", "tomorrow morning give me...", "every Monday summarize..."), actually schedule it with the scheduler tool instead of just promising. Translate the request into a cron spec (5 fields: minute hour day-of-month month day-of-week; e.g. "0 8 * * *" is 8am every day). Set repeat=false for one-shot requests. The prompt you schedule must be fully self-contained: it will run in a fresh conversation with no memory of the current one.