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
76 lines
2.6 KiB
Go
76 lines
2.6 KiB
Go
package workspace
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
workspaceissues "github.com/tutti-os/tutti/packages/workspace/issues"
|
|
)
|
|
|
|
// SearchRunOutputs matches output files by display name across one workspace,
|
|
// joining the owning issue for its title (and for topic scoping). Results are
|
|
// recency-ordered and deduplicated by path, mirroring GetIssueDetail outputs.
|
|
func (s *SQLiteStore) SearchRunOutputs(ctx context.Context, params workspaceissues.RunOutputSearchParams) ([]workspaceissues.RunOutputSearchHit, error) {
|
|
if err := s.ensureIssueDatabase(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.ensureIssueWorkspace(ctx, params.WorkspaceID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// query 为空时 LIKE '%%' 匹配全部 —— 即「仅按类型筛选」的 list-all(由下方 filters 收窄)。
|
|
where := []string{"o.workspace_id = ?", "LOWER(o.display_name) LIKE ?"}
|
|
args := []any{params.WorkspaceID, "%" + strings.ToLower(params.Query) + "%"}
|
|
if params.IssueID != "" {
|
|
where = append(where, "o.issue_id = ?")
|
|
args = append(args, params.IssueID)
|
|
} else if params.TopicID != "" {
|
|
where = append(where, "i.topic_id = ?")
|
|
args = append(args, params.TopicID)
|
|
}
|
|
// 文件类型筛选(全局统一口径):按 display_name 扩展名收窄,见 reference_filter_categories.go。
|
|
if clause, clauseArgs := referenceFilterDisplayNameClause("o.display_name", params.Filters); clause != "" {
|
|
where = append(where, clause)
|
|
args = append(args, clauseArgs...)
|
|
}
|
|
|
|
query := fmt.Sprintf(`
|
|
SELECT o.id, o.output_id, o.run_id, o.task_id, o.issue_id, o.workspace_id,
|
|
o.path, o.display_name, o.media_type, o.size_bytes, o.created_at_unix_ms,
|
|
i.title
|
|
FROM workspace_issue_run_outputs o
|
|
JOIN workspace_issues i
|
|
ON i.workspace_id = o.workspace_id AND i.issue_id = o.issue_id
|
|
WHERE %s
|
|
ORDER BY o.created_at_unix_ms DESC, o.id DESC
|
|
LIMIT ?
|
|
`, strings.Join(where, " AND "))
|
|
args = append(args, params.Limit)
|
|
|
|
rows, err := s.readDB.QueryContext(ctx, query, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("search workspace issue run outputs: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
hits := make([]workspaceissues.RunOutputSearchHit, 0)
|
|
seenPaths := map[string]struct{}{}
|
|
for rows.Next() {
|
|
hit, err := scanWorkspaceIssueRunOutputSearchHit(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dedupKey := strings.TrimSpace(hit.Output.Path)
|
|
if _, exists := seenPaths[dedupKey]; exists {
|
|
continue
|
|
}
|
|
seenPaths[dedupKey] = struct{}{}
|
|
hits = append(hits, hit)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate workspace issue run output search: %w", err)
|
|
}
|
|
return hits, nil
|
|
}
|