源码快照 @ 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,189 @@
|
||||
package storesqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrInteractionTreeRootRequired reports that an interaction-tree query was
|
||||
// addressed to a child Session. Tree ownership always starts at a root Session.
|
||||
var ErrInteractionTreeRootRequired = errors.New("interaction tree query requires a root session")
|
||||
|
||||
// ErrInteractionTreeRootTurnNotFound reports that an explicitly selected root
|
||||
// Turn is missing, belongs to another Session, or is retracted.
|
||||
var ErrInteractionTreeRootTurnNotFound = errors.New("interaction tree root turn was not found")
|
||||
|
||||
const sessionInteractionTreeSnapshotSQL = `
|
||||
WITH descendant_sessions AS (
|
||||
SELECT workspace_id, agent_session_id
|
||||
FROM workspace_agent_sessions
|
||||
WHERE workspace_id = ?
|
||||
AND session_kind = 'child'
|
||||
AND root_agent_session_id = ?
|
||||
AND root_turn_id = ?
|
||||
AND deleted_at_unix_ms = 0
|
||||
),
|
||||
ranked_descendant_turns AS (
|
||||
SELECT turn.workspace_id, turn.agent_session_id, turn.turn_id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY turn.workspace_id, turn.agent_session_id
|
||||
ORDER BY turn.updated_at_unix_ms DESC, turn.created_at_unix_ms DESC,
|
||||
turn.started_at_unix_ms DESC, turn.turn_id DESC
|
||||
) AS rank
|
||||
FROM workspace_agent_turns turn
|
||||
JOIN descendant_sessions session
|
||||
ON session.workspace_id = turn.workspace_id
|
||||
AND session.agent_session_id = turn.agent_session_id
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM workspace_agent_turn_history history
|
||||
WHERE history.workspace_id = turn.workspace_id
|
||||
AND history.agent_session_id = turn.agent_session_id
|
||||
AND history.turn_id = turn.turn_id
|
||||
AND history.history_state = 'retracted'
|
||||
)
|
||||
),
|
||||
selected_turns AS (
|
||||
SELECT ? AS workspace_id, ? AS agent_session_id, ? AS turn_id
|
||||
UNION ALL
|
||||
SELECT workspace_id, agent_session_id, turn_id
|
||||
FROM ranked_descendant_turns
|
||||
WHERE rank = 1
|
||||
)
|
||||
SELECT interaction.workspace_id,
|
||||
interaction.agent_session_id,
|
||||
interaction.request_id,
|
||||
interaction.turn_id,
|
||||
interaction.kind,
|
||||
interaction.status,
|
||||
interaction.tool_name,
|
||||
interaction.input_json,
|
||||
interaction.output_json,
|
||||
interaction.metadata_json,
|
||||
interaction.created_at_unix_ms,
|
||||
interaction.updated_at_unix_ms
|
||||
FROM workspace_agent_interactions interaction
|
||||
JOIN selected_turns selected
|
||||
ON selected.workspace_id = interaction.workspace_id
|
||||
AND selected.agent_session_id = interaction.agent_session_id
|
||||
AND selected.turn_id = interaction.turn_id
|
||||
ORDER BY interaction.agent_session_id ASC,
|
||||
interaction.turn_id ASC,
|
||||
interaction.request_id ASC
|
||||
`
|
||||
|
||||
// GetSessionInteractionTreeSnapshot reads a root Turn and all descendant
|
||||
// Sessions' latest-Turn interactions from one SQLite snapshot. Descendants are
|
||||
// selected by their immutable root identity, so traversal depth does not
|
||||
// affect the query cost.
|
||||
func (s *Store) GetSessionInteractionTreeSnapshot(
|
||||
ctx context.Context,
|
||||
query SessionInteractionTreeQuery,
|
||||
) (SessionInteractionTreeSnapshot, bool, error) {
|
||||
if s == nil || s.db == nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, errors.New("workspace database is not initialized")
|
||||
}
|
||||
query.WorkspaceID = strings.TrimSpace(query.WorkspaceID)
|
||||
query.RootAgentSessionID = strings.TrimSpace(query.RootAgentSessionID)
|
||||
query.RootTurnID = strings.TrimSpace(query.RootTurnID)
|
||||
if query.WorkspaceID == "" || query.RootAgentSessionID == "" {
|
||||
return SessionInteractionTreeSnapshot{}, false, nil
|
||||
}
|
||||
|
||||
tx, err := s.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
||||
if err != nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, fmt.Errorf("begin interaction tree read: %w", err)
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
var kind string
|
||||
err = tx.QueryRowContext(ctx, `
|
||||
SELECT session_kind
|
||||
FROM workspace_agent_sessions
|
||||
WHERE workspace_id = ? AND agent_session_id = ? AND deleted_at_unix_ms = 0
|
||||
`, query.WorkspaceID, query.RootAgentSessionID).Scan(&kind)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return SessionInteractionTreeSnapshot{}, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, fmt.Errorf("read interaction tree root session: %w", err)
|
||||
}
|
||||
if kind != SessionKindRoot {
|
||||
return SessionInteractionTreeSnapshot{}, false, ErrInteractionTreeRootRequired
|
||||
}
|
||||
|
||||
rootTurnID := query.RootTurnID
|
||||
if rootTurnID != "" {
|
||||
var validatedRootTurnID string
|
||||
err = tx.QueryRowContext(ctx, `
|
||||
SELECT turn.turn_id
|
||||
FROM workspace_agent_turns turn
|
||||
WHERE turn.workspace_id = ? AND turn.agent_session_id = ? AND turn.turn_id = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM workspace_agent_turn_history history
|
||||
WHERE history.workspace_id = turn.workspace_id
|
||||
AND history.agent_session_id = turn.agent_session_id
|
||||
AND history.turn_id = turn.turn_id
|
||||
AND history.history_state = 'retracted'
|
||||
)
|
||||
`, query.WorkspaceID, query.RootAgentSessionID, rootTurnID).Scan(&validatedRootTurnID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return SessionInteractionTreeSnapshot{}, false, ErrInteractionTreeRootTurnNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, fmt.Errorf("validate interaction tree root turn: %w", err)
|
||||
}
|
||||
rootTurnID = validatedRootTurnID
|
||||
} else {
|
||||
err = tx.QueryRowContext(ctx, `
|
||||
SELECT turn_id
|
||||
FROM workspace_agent_turns turn
|
||||
WHERE workspace_id = ? AND agent_session_id = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM workspace_agent_turn_history history
|
||||
WHERE history.workspace_id = turn.workspace_id
|
||||
AND history.agent_session_id = turn.agent_session_id
|
||||
AND history.turn_id = turn.turn_id
|
||||
AND history.history_state = 'retracted'
|
||||
)
|
||||
ORDER BY updated_at_unix_ms DESC, created_at_unix_ms DESC,
|
||||
started_at_unix_ms DESC, turn_id DESC
|
||||
LIMIT 1
|
||||
`, query.WorkspaceID, query.RootAgentSessionID).Scan(&rootTurnID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return SessionInteractionTreeSnapshot{}, true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, fmt.Errorf("resolve interaction tree root turn: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, sessionInteractionTreeSnapshotSQL,
|
||||
query.WorkspaceID, query.RootAgentSessionID, rootTurnID,
|
||||
query.WorkspaceID, query.RootAgentSessionID, rootTurnID)
|
||||
if err != nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, fmt.Errorf("read interaction tree interactions: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
snapshot := SessionInteractionTreeSnapshot{RootTurnID: rootTurnID}
|
||||
for rows.Next() {
|
||||
interaction, scanErr := scanAgentInteraction(rows)
|
||||
if scanErr != nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, scanErr
|
||||
}
|
||||
snapshot.Interactions = append(snapshot.Interactions, interaction)
|
||||
if interaction.Status == InteractionStatusPending {
|
||||
snapshot.PendingInteractions = append(snapshot.PendingInteractions, interaction)
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, fmt.Errorf("iterate interaction tree interactions: %w", err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return SessionInteractionTreeSnapshot{}, false, fmt.Errorf("finish interaction tree read: %w", err)
|
||||
}
|
||||
return snapshot, true, nil
|
||||
}
|
||||
Reference in New Issue
Block a user