源码快照 @ 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,186 @@
|
||||
package storesqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
agentactivityprojection "github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
|
||||
)
|
||||
|
||||
func prepareSessionRelation(
|
||||
input *SessionStateReport,
|
||||
existing agentactivityprojection.SessionSnapshot,
|
||||
hasExisting bool,
|
||||
) error {
|
||||
input.Kind = strings.TrimSpace(input.Kind)
|
||||
input.RootAgentSessionID = strings.TrimSpace(input.RootAgentSessionID)
|
||||
input.RootTurnID = strings.TrimSpace(input.RootTurnID)
|
||||
input.ParentAgentSessionID = strings.TrimSpace(input.ParentAgentSessionID)
|
||||
input.ParentTurnID = strings.TrimSpace(input.ParentTurnID)
|
||||
input.ParentToolCallID = strings.TrimSpace(input.ParentToolCallID)
|
||||
|
||||
if hasExisting {
|
||||
existingKind := strings.TrimSpace(existing.Kind)
|
||||
if existingKind == "" {
|
||||
existingKind = SessionKindRoot
|
||||
}
|
||||
if input.Kind == "" {
|
||||
input.Kind = existingKind
|
||||
} else if input.Kind != existingKind {
|
||||
return errors.New("workspace agent session kind is immutable")
|
||||
}
|
||||
if err := retainImmutableSessionField(&input.RootAgentSessionID, existing.RootAgentSessionID, "root agent session id"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := retainImmutableSessionField(&input.RootTurnID, existing.RootTurnID, "root turn id"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := retainImmutableSessionField(&input.ParentAgentSessionID, existing.ParentAgentSessionID, "parent agent session id"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := retainImmutableSessionField(&input.ParentTurnID, existing.ParentTurnID, "parent turn id"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := retainImmutableSessionField(&input.ParentToolCallID, existing.ParentToolCallID, "parent tool call id"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if input.Kind == "" {
|
||||
input.Kind = SessionKindRoot
|
||||
}
|
||||
|
||||
switch input.Kind {
|
||||
case SessionKindRoot:
|
||||
if input.RootAgentSessionID != "" || input.RootTurnID != "" ||
|
||||
input.ParentAgentSessionID != "" || input.ParentTurnID != "" || input.ParentToolCallID != "" {
|
||||
return errors.New("root workspace agent session cannot have root or parent fields")
|
||||
}
|
||||
case SessionKindChild:
|
||||
if input.RootAgentSessionID == "" || input.RootTurnID == "" ||
|
||||
input.ParentAgentSessionID == "" || input.ParentTurnID == "" || input.ParentToolCallID == "" {
|
||||
return errors.New("child workspace agent session requires root and parent fields")
|
||||
}
|
||||
if input.RootAgentSessionID == input.AgentSessionID || input.ParentAgentSessionID == input.AgentSessionID {
|
||||
return errors.New("child workspace agent session cannot own or parent itself")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported workspace agent session kind %q", input.Kind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func retainImmutableSessionField(target *string, existing string, label string) error {
|
||||
existing = strings.TrimSpace(existing)
|
||||
if *target == "" {
|
||||
*target = existing
|
||||
return nil
|
||||
}
|
||||
if *target != existing {
|
||||
return fmt.Errorf("workspace agent session %s is immutable", label)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChildSessionParentsTx(
|
||||
ctx context.Context,
|
||||
tx *sql.Tx,
|
||||
input SessionStateReport,
|
||||
) error {
|
||||
var rootKind 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
|
||||
`, input.WorkspaceID, input.RootAgentSessionID).Scan(&rootKind)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return errors.New("child workspace agent session root session does not exist")
|
||||
}
|
||||
return fmt.Errorf("read child workspace agent root session: %w", err)
|
||||
}
|
||||
if rootKind != SessionKindRoot {
|
||||
return errors.New("child workspace agent session root must have root kind")
|
||||
}
|
||||
|
||||
var rootPhase string
|
||||
err = tx.QueryRowContext(ctx, `
|
||||
SELECT phase
|
||||
FROM workspace_agent_turns
|
||||
WHERE workspace_id = ? AND agent_session_id = ? AND turn_id = ?
|
||||
`, input.WorkspaceID, input.RootAgentSessionID, input.RootTurnID).Scan(&rootPhase)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return errors.New("child workspace agent session root turn does not exist")
|
||||
}
|
||||
return fmt.Errorf("read child workspace agent root turn: %w", err)
|
||||
}
|
||||
if rootPhase == TurnPhaseSettled {
|
||||
return errors.New("child workspace agent session cannot be created after its root turn settled")
|
||||
}
|
||||
|
||||
// Preparing a root cancel operation durably freezes the delegation tree
|
||||
// before provider I/O begins. A provider may reveal another child after the
|
||||
// exact cancel target snapshot was recorded; accepting that child here
|
||||
// would let it escape the atomic target settlement and leave active durable
|
||||
// state below a canceled root.
|
||||
var rootCancelPending int
|
||||
err = tx.QueryRowContext(ctx, `
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM workspace_agent_runtime_operations
|
||||
WHERE workspace_id = ? AND agent_session_id = ? AND turn_id = ?
|
||||
AND kind = ? AND status IN (?, ?)
|
||||
)
|
||||
`, input.WorkspaceID, input.RootAgentSessionID, input.RootTurnID,
|
||||
RuntimeOperationKindCancelTurn, RuntimeOperationStatusPrepared, RuntimeOperationStatusLeased,
|
||||
).Scan(&rootCancelPending)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read child workspace agent root cancel boundary: %w", err)
|
||||
}
|
||||
if rootCancelPending != 0 {
|
||||
return errors.New("child workspace agent session cannot be created after its root turn cancellation started")
|
||||
}
|
||||
|
||||
var parentKind string
|
||||
var parentRootAgentSessionID, parentRootTurnID sql.NullString
|
||||
err = tx.QueryRowContext(ctx, `
|
||||
SELECT session_kind, root_agent_session_id, root_turn_id
|
||||
FROM workspace_agent_sessions
|
||||
WHERE workspace_id = ? AND agent_session_id = ? AND deleted_at_unix_ms = 0
|
||||
`, input.WorkspaceID, input.ParentAgentSessionID).Scan(
|
||||
&parentKind,
|
||||
&parentRootAgentSessionID,
|
||||
&parentRootTurnID,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return errors.New("child workspace agent session parent session does not exist")
|
||||
}
|
||||
return fmt.Errorf("read child workspace agent parent session: %w", err)
|
||||
}
|
||||
if parentKind == SessionKindRoot {
|
||||
if input.ParentAgentSessionID != input.RootAgentSessionID || input.ParentTurnID != input.RootTurnID {
|
||||
return errors.New("child workspace agent session root parent must use the root session and turn")
|
||||
}
|
||||
} else if parentKind != SessionKindChild ||
|
||||
strings.TrimSpace(parentRootAgentSessionID.String) != input.RootAgentSessionID ||
|
||||
strings.TrimSpace(parentRootTurnID.String) != input.RootTurnID {
|
||||
return errors.New("child workspace agent session parent belongs to another root turn")
|
||||
}
|
||||
|
||||
var parentTurnExists int
|
||||
err = tx.QueryRowContext(ctx, `
|
||||
SELECT 1
|
||||
FROM workspace_agent_turns
|
||||
WHERE workspace_id = ? AND agent_session_id = ? AND turn_id = ?
|
||||
`, input.WorkspaceID, input.ParentAgentSessionID, input.ParentTurnID).Scan(&parentTurnExists)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return errors.New("child workspace agent session parent turn does not exist")
|
||||
}
|
||||
return fmt.Errorf("read child workspace agent parent turn: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user