源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
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
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
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
automationrulebiz "github.com/tutti-os/tutti/services/tuttid/biz/automationrule"
|
||||
)
|
||||
|
||||
var ErrAutomationRuleNotFound = errors.New("automation rule not found")
|
||||
var ErrAutomationRuleAlreadyExists = errors.New("automation rule already exists")
|
||||
|
||||
func (s *SQLiteStore) ListAutomationRules(ctx context.Context, workspaceID string) ([]automationrulebiz.Rule, error) {
|
||||
rows, err := s.readDB.QueryContext(ctx, automationRuleSelect+`
|
||||
WHERE workspace_id = ?
|
||||
ORDER BY created_at_unix_ms ASC, rule_id ASC
|
||||
`, workspaceID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list automation rules: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var rules []automationrulebiz.Rule
|
||||
for rows.Next() {
|
||||
rule, err := scanAutomationRule(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate automation rules: %w", err)
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStore) GetAutomationRule(ctx context.Context, workspaceID string, ruleID string) (automationrulebiz.Rule, error) {
|
||||
rule, err := scanAutomationRule(s.readDB.QueryRowContext(ctx, automationRuleSelect+`
|
||||
WHERE workspace_id = ? AND rule_id = ?
|
||||
`, workspaceID, ruleID))
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return automationrulebiz.Rule{}, ErrAutomationRuleNotFound
|
||||
}
|
||||
return rule, err
|
||||
}
|
||||
|
||||
func (s *SQLiteStore) CreateAutomationRule(ctx context.Context, rule automationrulebiz.Rule) error {
|
||||
normalized, err := automationrulebiz.Normalize(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requiredCapabilities, err := json.Marshal(normalized.Target.RequiredCapabilities)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode automation rule capabilities: %w", err)
|
||||
}
|
||||
allowedTools, err := json.Marshal(normalized.Permissions.AllowedTools)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode automation rule tools: %w", err)
|
||||
}
|
||||
// The action column is retired: automation has exactly one launch
|
||||
// behavior, so rows no longer carry an action discriminator.
|
||||
_, err = s.writeDB.ExecContext(ctx, `
|
||||
INSERT INTO automation_rules (
|
||||
workspace_id, rule_id, name, enabled, trigger, action,
|
||||
source_workspace_agent_id, target_kind, target_workspace_agent_id,
|
||||
model_plan_id, model, required_capabilities_json, permission_mode_id,
|
||||
allowed_tools_json, max_runs_per_session, max_total_tokens_per_session,
|
||||
prompt, legacy_policy_id, created_at_unix_ms, updated_at_unix_ms
|
||||
) VALUES (?, ?, ?, ?, ?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', ?, ?)
|
||||
`, normalized.WorkspaceID, normalized.ID, normalized.Name, boolInt(normalized.Enabled),
|
||||
string(normalized.Trigger), normalized.SourceWorkspaceAgentID,
|
||||
string(normalized.Target.Kind), normalized.Target.WorkspaceAgentID,
|
||||
normalized.Target.ModelPlanID, normalized.Target.Model, string(requiredCapabilities),
|
||||
normalized.Permissions.PermissionModeID, string(allowedTools),
|
||||
normalized.Budget.MaxRunsPerSession, normalized.Budget.MaxTotalTokensPerSession,
|
||||
normalized.Prompt, unixMs(normalized.CreatedAt), unixMs(normalized.UpdatedAt))
|
||||
if err != nil {
|
||||
if isSQLiteUniqueConstraintError(err) {
|
||||
return ErrAutomationRuleAlreadyExists
|
||||
}
|
||||
return fmt.Errorf("create automation rule: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStore) UpdateAutomationRule(ctx context.Context, rule automationrulebiz.Rule) (automationrulebiz.Rule, error) {
|
||||
normalized, err := automationrulebiz.Normalize(rule)
|
||||
if err != nil {
|
||||
return automationrulebiz.Rule{}, err
|
||||
}
|
||||
requiredCapabilities, err := json.Marshal(normalized.Target.RequiredCapabilities)
|
||||
if err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("encode automation rule capabilities: %w", err)
|
||||
}
|
||||
allowedTools, err := json.Marshal(normalized.Permissions.AllowedTools)
|
||||
if err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("encode automation rule tools: %w", err)
|
||||
}
|
||||
tx, err := s.writeDB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("begin automation rule update: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
UPDATE automation_rules
|
||||
SET name = ?, enabled = ?, trigger = ?, action = '',
|
||||
source_workspace_agent_id = ?, target_kind = ?, target_workspace_agent_id = ?,
|
||||
model_plan_id = ?, model = ?, required_capabilities_json = ?,
|
||||
permission_mode_id = ?, allowed_tools_json = ?, max_runs_per_session = ?,
|
||||
max_total_tokens_per_session = ?, prompt = ?, legacy_policy_id = '',
|
||||
updated_at_unix_ms = ?
|
||||
WHERE workspace_id = ? AND rule_id = ?
|
||||
`, normalized.Name, boolInt(normalized.Enabled), string(normalized.Trigger),
|
||||
normalized.SourceWorkspaceAgentID, string(normalized.Target.Kind), normalized.Target.WorkspaceAgentID,
|
||||
normalized.Target.ModelPlanID, normalized.Target.Model, string(requiredCapabilities),
|
||||
normalized.Permissions.PermissionModeID, string(allowedTools), normalized.Budget.MaxRunsPerSession,
|
||||
normalized.Budget.MaxTotalTokensPerSession, normalized.Prompt, unixMs(normalized.UpdatedAt),
|
||||
normalized.WorkspaceID, normalized.ID)
|
||||
if err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("update automation rule: %w", err)
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("update automation rule result: %w", err)
|
||||
}
|
||||
if affected == 0 {
|
||||
return automationrulebiz.Rule{}, ErrAutomationRuleNotFound
|
||||
}
|
||||
updated, err := scanAutomationRule(tx.QueryRowContext(ctx, automationRuleSelect+`
|
||||
WHERE workspace_id = ? AND rule_id = ?
|
||||
`, normalized.WorkspaceID, normalized.ID))
|
||||
if err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("read updated automation rule: %w", err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("commit automation rule update: %w", err)
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStore) DeleteAutomationRule(ctx context.Context, workspaceID string, ruleID string) error {
|
||||
result, err := s.writeDB.ExecContext(ctx, `DELETE FROM automation_rules WHERE workspace_id = ? AND rule_id = ?`, workspaceID, ruleID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete automation rule: %w", err)
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete automation rule result: %w", err)
|
||||
}
|
||||
if affected == 0 {
|
||||
return ErrAutomationRuleNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStore) ListAutomationRulesByPlan(ctx context.Context, workspaceID string, planID string) ([]automationrulebiz.Rule, error) {
|
||||
rows, err := s.readDB.QueryContext(ctx, automationRuleSelect+`
|
||||
WHERE workspace_id = ? AND model_plan_id = ?
|
||||
ORDER BY rule_id ASC
|
||||
`, workspaceID, planID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list automation rules by plan: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var rules []automationrulebiz.Rule
|
||||
for rows.Next() {
|
||||
rule, err := scanAutomationRule(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate automation rules by plan: %w", err)
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStore) GetAutomationRuleSessionOverride(ctx context.Context, workspaceID string, sessionID string) (automationrulebiz.SessionOverride, bool, error) {
|
||||
row := s.readDB.QueryRowContext(ctx, `
|
||||
SELECT workspace_id, agent_session_id, disabled, rule_ids_json, updated_at_unix_ms
|
||||
FROM automation_rule_session_overrides
|
||||
WHERE workspace_id = ? AND agent_session_id = ?
|
||||
`, workspaceID, sessionID)
|
||||
var override automationrulebiz.SessionOverride
|
||||
var disabled int
|
||||
var ruleIDsJSON string
|
||||
var updatedAtMS int64
|
||||
if err := row.Scan(&override.WorkspaceID, &override.AgentSessionID, &disabled, &ruleIDsJSON, &updatedAtMS); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return automationrulebiz.SessionOverride{}, false, nil
|
||||
}
|
||||
return automationrulebiz.SessionOverride{}, false, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(ruleIDsJSON), &override.RuleIDs); err != nil {
|
||||
return automationrulebiz.SessionOverride{}, false, fmt.Errorf("decode automation rule override ids: %w", err)
|
||||
}
|
||||
override.Disabled = disabled != 0
|
||||
override.UpdatedAt = time.UnixMilli(updatedAtMS).UTC()
|
||||
return override, true, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStore) PutAutomationRuleSessionOverride(ctx context.Context, override automationrulebiz.SessionOverride) error {
|
||||
normalized, err := automationrulebiz.NormalizeSessionOverride(override)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleIDs, err := json.Marshal(normalized.RuleIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode automation rule override ids: %w", err)
|
||||
}
|
||||
_, err = s.writeDB.ExecContext(ctx, `
|
||||
INSERT INTO automation_rule_session_overrides (
|
||||
workspace_id, agent_session_id, disabled, rule_ids_json, updated_at_unix_ms
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(workspace_id, agent_session_id) DO UPDATE SET
|
||||
disabled = excluded.disabled,
|
||||
rule_ids_json = excluded.rule_ids_json,
|
||||
updated_at_unix_ms = excluded.updated_at_unix_ms
|
||||
`, normalized.WorkspaceID, normalized.AgentSessionID, boolInt(normalized.Disabled), string(ruleIDs), unixMs(normalized.UpdatedAt))
|
||||
if err != nil {
|
||||
return fmt.Errorf("put automation rule session override: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const automationRuleSelect = `
|
||||
SELECT workspace_id, rule_id, name, enabled, trigger,
|
||||
source_workspace_agent_id, target_kind, target_workspace_agent_id,
|
||||
model_plan_id, model, required_capabilities_json, permission_mode_id,
|
||||
allowed_tools_json, max_runs_per_session, max_total_tokens_per_session,
|
||||
prompt, created_at_unix_ms, updated_at_unix_ms
|
||||
FROM automation_rules
|
||||
`
|
||||
|
||||
func scanAutomationRule(row managedProviderScanner) (automationrulebiz.Rule, error) {
|
||||
var rule automationrulebiz.Rule
|
||||
var enabled int
|
||||
var trigger, targetKind string
|
||||
var requiredCapabilitiesJSON, allowedToolsJSON string
|
||||
var createdAtMS, updatedAtMS int64
|
||||
if err := row.Scan(&rule.WorkspaceID, &rule.ID, &rule.Name, &enabled, &trigger,
|
||||
&rule.SourceWorkspaceAgentID, &targetKind, &rule.Target.WorkspaceAgentID,
|
||||
&rule.Target.ModelPlanID, &rule.Target.Model, &requiredCapabilitiesJSON,
|
||||
&rule.Permissions.PermissionModeID, &allowedToolsJSON,
|
||||
&rule.Budget.MaxRunsPerSession, &rule.Budget.MaxTotalTokensPerSession,
|
||||
&rule.Prompt, &createdAtMS, &updatedAtMS); err != nil {
|
||||
return automationrulebiz.Rule{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(requiredCapabilitiesJSON), &rule.Target.RequiredCapabilities); err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("decode automation rule capabilities: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(allowedToolsJSON), &rule.Permissions.AllowedTools); err != nil {
|
||||
return automationrulebiz.Rule{}, fmt.Errorf("decode automation rule tools: %w", err)
|
||||
}
|
||||
rule.Enabled = enabled != 0
|
||||
rule.Trigger = automationrulebiz.Trigger(trigger)
|
||||
rule.Target.Kind = automationrulebiz.TargetKind(targetKind)
|
||||
rule.CreatedAt = time.UnixMilli(createdAtMS).UTC()
|
||||
rule.UpdatedAt = time.UnixMilli(updatedAtMS).UTC()
|
||||
return automationrulebiz.Normalize(rule)
|
||||
}
|
||||
Reference in New Issue
Block a user