Files
tutti/services/tuttid/data/workspace/sqlite_automation_rule_executions.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

100 lines
4.3 KiB
Go

package workspace
import (
"context"
"fmt"
"strings"
"time"
automationrulebiz "github.com/tutti-os/tutti/services/tuttid/biz/automationrule"
)
// RecordAutomationRuleExecution stores one durable launch attempt before the
// target session exists. The primary key over workspace/rule/source/trigger
// makes duplicate trigger deliveries fail loudly instead of double-launching.
func (s *SQLiteStore) RecordAutomationRuleExecution(ctx context.Context, execution automationrulebiz.Execution) error {
normalized, err := automationrulebiz.NormalizeExecution(execution)
if err != nil {
return err
}
createdAt := normalized.CreatedAt
if createdAt.IsZero() {
createdAt = time.Now().UTC()
}
updatedAt := normalized.UpdatedAt
if updatedAt.IsZero() {
updatedAt = createdAt
}
if _, err := s.writeDB.ExecContext(ctx, `
INSERT INTO automation_rule_executions (
workspace_id, rule_id, source_session_id, trigger_id, target_session_id,
status, failure_reason, total_tokens, created_at_unix_ms, updated_at_unix_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, normalized.WorkspaceID, normalized.RuleID, normalized.SourceSessionID, normalized.TriggerID,
normalized.TargetSessionID, string(normalized.Status), normalized.FailureReason,
normalized.TotalTokens, unixMs(createdAt), unixMs(updatedAt)); err != nil {
return fmt.Errorf("record automation rule execution: %w", err)
}
return nil
}
// MarkAutomationRuleExecutionLaunchFailed keeps the failed launch row for
// audit and dedup; a failed launch consumes the trigger rather than retrying.
func (s *SQLiteStore) MarkAutomationRuleExecutionLaunchFailed(ctx context.Context, workspaceID string, targetSessionID string, failureReason string) error {
if _, err := s.writeDB.ExecContext(ctx, `
UPDATE automation_rule_executions
SET status = ?, failure_reason = ?, updated_at_unix_ms = ?
WHERE workspace_id = ? AND target_session_id = ?
`, string(automationrulebiz.ExecutionLaunchFailed), strings.TrimSpace(failureReason),
unixMs(time.Now().UTC()), strings.TrimSpace(workspaceID), strings.TrimSpace(targetSessionID)); err != nil {
return fmt.Errorf("mark automation rule execution failed: %w", err)
}
return nil
}
func (s *SQLiteStore) AutomationRuleExecutionExists(ctx context.Context, workspaceID string, sourceSessionID string, ruleID string, triggerID string) (bool, error) {
row := s.readDB.QueryRowContext(ctx, `
SELECT COUNT(*) FROM automation_rule_executions
WHERE workspace_id = ? AND source_session_id = ? AND rule_id = ? AND trigger_id = ?
`, strings.TrimSpace(workspaceID), strings.TrimSpace(sourceSessionID), strings.TrimSpace(ruleID), strings.TrimSpace(triggerID))
var count int
if err := row.Scan(&count); err != nil {
return false, fmt.Errorf("automation rule execution lookup: %w", err)
}
return count > 0, nil
}
// AutomationRuleUsage counts every launch attempt (including failed ones)
// and sums the recorded target-session token totals for one rule and source
// session.
func (s *SQLiteStore) AutomationRuleUsage(ctx context.Context, workspaceID string, sourceSessionID string, ruleID string) (int, int64, error) {
row := s.readDB.QueryRowContext(ctx, `
SELECT COUNT(*), COALESCE(SUM(total_tokens), 0) FROM automation_rule_executions
WHERE workspace_id = ? AND source_session_id = ? AND rule_id = ?
`, strings.TrimSpace(workspaceID), strings.TrimSpace(sourceSessionID), strings.TrimSpace(ruleID))
var runs int
var totalTokens int64
if err := row.Scan(&runs, &totalTokens); err != nil {
return 0, 0, fmt.Errorf("automation rule usage lookup: %w", err)
}
return runs, totalTokens, nil
}
// RecordAutomationTargetUsage settles the launched session's recorded token
// total once; later turns in the same target session do not keep charging
// the rule budget, mirroring the retired CollaborationRun settle-once
// semantics.
func (s *SQLiteStore) RecordAutomationTargetUsage(ctx context.Context, workspaceID string, targetSessionID string, totalTokens int64) error {
if totalTokens < 0 {
return nil
}
if _, err := s.writeDB.ExecContext(ctx, `
UPDATE automation_rule_executions
SET total_tokens = ?, updated_at_unix_ms = ?
WHERE workspace_id = ? AND target_session_id = ? AND total_tokens = 0
`, totalTokens, unixMs(time.Now().UTC()), strings.TrimSpace(workspaceID), strings.TrimSpace(targetSessionID)); err != nil {
return fmt.Errorf("record automation target usage: %w", err)
}
return nil
}