Files
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

133 lines
4.4 KiB
Go

package storesqlite
import (
"context"
"encoding/json"
"fmt"
"strings"
)
const (
workspaceGeneratedFileTurnCandidateLimit = 1000
sectionGeneratedFileTurnLimit = 100
)
type generatedFileChangesPayload struct {
Files []struct {
Path string `json:"path"`
Change string `json:"change"`
} `json:"files"`
}
func (s *Store) ListWorkspaceGeneratedFileTurns(
ctx context.Context,
input ListWorkspaceGeneratedFileTurnsInput,
) (GeneratedFileTurnList, bool, error) {
if s == nil || s.db == nil {
return GeneratedFileTurnList{}, false, fmt.Errorf("workspace database is not initialized")
}
workspaceID := strings.TrimSpace(input.WorkspaceID)
sectionKey := NormalizeRailSectionKey(strings.TrimSpace(input.SectionKey))
if workspaceID == "" || sectionKey == "" || sectionKey == PinnedSessionPageKey {
return GeneratedFileTurnList{}, false, nil
}
if err := s.ensureWorkspaceExists(ctx, workspaceID); err != nil {
return GeneratedFileTurnList{}, false, err
}
// Keep file_changes_json out of the 1000-row workspace candidate CTE. A
// canonical payload may contain large diffs, so fetch it only after section
// filtering has reduced the result to at most 100 turns.
// This projection intentionally includes retracted Turns: edit-retry changes
// model-visible history but does not compensate filesystem side effects.
rows, err := s.db.QueryContext(ctx, `
WITH recent_workspace_turns AS MATERIALIZED (
SELECT agent_session_id,
turn_id,
settled_at_unix_ms
FROM workspace_agent_turns INDEXED BY idx_workspace_agent_turns_workspace_settled_recent
WHERE workspace_id = ?
AND phase = 'settled'
AND settled_at_unix_ms IS NOT NULL
ORDER BY settled_at_unix_ms DESC,
agent_session_id DESC,
turn_id DESC
LIMIT ?
),
scoped_turns AS MATERIALIZED (
SELECT turns.agent_session_id,
COALESCE(sessions.agent_target_id, '') AS agent_target_id,
turns.turn_id,
sessions.cwd,
sessions.rail_section_kind,
sessions.rail_project_path,
turns.settled_at_unix_ms
FROM recent_workspace_turns AS turns
JOIN workspace_agent_sessions AS sessions
ON sessions.workspace_id = ?
AND sessions.agent_session_id = turns.agent_session_id
WHERE sessions.rail_section_key = ?
AND sessions.deleted_at_unix_ms = 0
ORDER BY turns.settled_at_unix_ms DESC,
turns.agent_session_id DESC,
turns.turn_id DESC
LIMIT ?
)
SELECT scoped.agent_session_id,
scoped.agent_target_id,
scoped.turn_id,
scoped.cwd,
scoped.rail_section_kind,
scoped.rail_project_path,
scoped.settled_at_unix_ms,
source.file_changes_json
FROM scoped_turns AS scoped
JOIN workspace_agent_turns AS source
ON source.workspace_id = ?
AND source.agent_session_id = scoped.agent_session_id
AND source.turn_id = scoped.turn_id
ORDER BY scoped.settled_at_unix_ms DESC,
scoped.agent_session_id DESC,
scoped.turn_id DESC
`, workspaceID, workspaceGeneratedFileTurnCandidateLimit, workspaceID, sectionKey, sectionGeneratedFileTurnLimit, workspaceID)
if err != nil {
return GeneratedFileTurnList{}, false, fmt.Errorf("list workspace agent generated file turn candidates: %w", err)
}
defer rows.Close()
turns := make([]GeneratedFileTurn, 0, sectionGeneratedFileTurnLimit)
for rows.Next() {
var turn GeneratedFileTurn
var fileChangesJSON *string
if err := rows.Scan(
&turn.AgentSessionID,
&turn.AgentTargetID,
&turn.TurnID,
&turn.CWD,
&turn.RailSectionKind,
&turn.RailProjectPath,
&turn.SettledAtUnixMS,
&fileChangesJSON,
); err != nil {
return GeneratedFileTurnList{}, false, fmt.Errorf("scan workspace agent generated file turn candidate: %w", err)
}
if fileChangesJSON != nil && strings.TrimSpace(*fileChangesJSON) != "" {
var payload generatedFileChangesPayload
if err := json.Unmarshal([]byte(*fileChangesJSON), &payload); err == nil {
turn.Changes = make([]GeneratedFileTurnChange, 0, len(payload.Files))
for _, file := range payload.Files {
turn.Changes = append(turn.Changes, GeneratedFileTurnChange{
Path: file.Path,
Change: file.Change,
})
}
}
}
turns = append(turns, turn)
}
if err := rows.Err(); err != nil {
return GeneratedFileTurnList{}, false, fmt.Errorf("iterate workspace agent generated file turn candidates: %w", err)
}
return GeneratedFileTurnList{WorkspaceID: workspaceID, Turns: turns}, true, nil
}