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
100 lines
4.3 KiB
Go
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
|
|
}
|