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

1538 lines
62 KiB
Go

package storesqlite
import (
"context"
"database/sql"
"errors"
"fmt"
"path/filepath"
"strings"
"testing"
_ "modernc.org/sqlite"
)
var errTestWorkspaceNotFound = errors.New("workspace not found")
const (
testTargetIDCodex = "local:codex"
testTargetIDClaude = "local:claude-code"
)
type staticProjectPaths struct {
paths []string
}
func (s *staticProjectPaths) ProjectPaths(context.Context, Querier) ([]string, error) {
return append([]string(nil), s.paths...), nil
}
func testSeedTargets(now int64) []Target {
return []Target{
{
ID: testTargetIDCodex,
Provider: "codex",
LaunchRefJSON: `{"type":"local_cli","provider":"codex"}`,
Name: "Codex",
IconKey: "codex",
IconURL: "tutti-asset://agent/codex.png",
MaskIconURL: "tutti-asset://agent/codex-mask.svg",
Enabled: true,
Source: "system",
SortOrder: 10,
CreatedAtUnixMS: now,
UpdatedAtUnixMS: now,
},
{
ID: testTargetIDClaude,
Provider: "claude-code",
LaunchRefJSON: `{"type":"local_cli","provider":"claude-code"}`,
Name: "Claude Code",
IconKey: "claude-code",
IconURL: "tutti-asset://agent/claudecode.png",
MaskIconURL: "tutti-asset://agent/claudecode-mask.svg",
Enabled: true,
Source: "system",
SortOrder: 20,
CreatedAtUnixMS: now,
UpdatedAtUnixMS: now,
},
}
}
func testOptions(projects *staticProjectPaths) Options {
return Options{
ProjectPaths: projects,
SeedSystemTargets: testSeedTargets,
LegacySystemTargetIDRenames: map[string]string{
"local-codex": testTargetIDCodex,
"local-claude-code": testTargetIDClaude,
},
TargetIDBackfillByProvider: map[string]string{
"codex": testTargetIDCodex,
"claude-code": testTargetIDClaude,
},
}
}
func openTestDB(t testing.TB) *sql.DB {
t.Helper()
dbPath := filepath.Join(t.TempDir(), "agent-store.db")
db, err := sql.Open("sqlite", dbPath)
if err != nil {
t.Fatalf("open sqlite database: %v", err)
}
t.Cleanup(func() {
_ = db.Close()
})
db.SetMaxOpenConns(1)
for _, pragma := range []string{
"PRAGMA busy_timeout = 5000",
"PRAGMA foreign_keys = ON",
"PRAGMA journal_mode = WAL",
} {
if _, err := db.Exec(pragma); err != nil {
t.Fatalf("%s: %v", pragma, err)
}
}
return db
}
func openTestStore(t testing.TB, opts Options) *Store {
t.Helper()
store := New(openTestDB(t), opts)
if err := store.Migrate(context.Background()); err != nil {
t.Fatalf("Migrate() error = %v", err)
}
return store
}
func TestStoreFreshMigrateCreatesSessionTurnReferenceWithoutHostForeignKey(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
rows, err := store.db.QueryContext(ctx, `PRAGMA foreign_key_list(workspace_agent_sessions)`)
if err != nil {
t.Fatalf("foreign_key_list error = %v", err)
}
defer rows.Close()
hasTurnsFK := false
hasHostFK := false
for rows.Next() {
var id, seq int
var table, from, to, onUpdate, onDelete, match string
if err := rows.Scan(&id, &seq, &table, &from, &to, &onUpdate, &onDelete, &match); err != nil {
t.Fatalf("scan foreign_key_list: %v", err)
}
hasTurnsFK = hasTurnsFK || table == "workspace_agent_turns"
hasHostFK = hasHostFK || table == "workspaces"
}
if err := rows.Err(); err != nil {
t.Fatalf("iterate foreign_key_list: %v", err)
}
if !hasTurnsFK || hasHostFK {
t.Fatalf("session foreign keys turns=%v host=%v, want exact turn reference without host coupling", hasTurnsFK, hasHostFK)
}
targets, err := store.ListAgentTargets(ctx)
if err != nil {
t.Fatalf("ListAgentTargets() error = %v", err)
}
if len(targets) != 2 || targets[0].ID != testTargetIDCodex || targets[1].ID != testTargetIDClaude {
t.Fatalf("seeded targets = %#v, want codex and claude-code system targets", targets)
}
}
func TestStoreMigrateRefreshesOnlySystemSeedTargets(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.db.ExecContext(ctx, `
UPDATE agent_targets
SET provider = 'stale-provider', launch_ref_json = '{}', name = 'Stale Codex',
icon_key = 'stale', icon_url = 'stale.png', mask_icon_url = 'stale-mask.svg',
hero_image_url = 'stale-hero.jpg', enabled = 0, sort_order = 999,
created_at_ms = 7, updated_at_ms = 8
WHERE id = ?;
`, testTargetIDCodex); err != nil {
t.Fatalf("seed stale system target: %v", err)
}
if _, err := store.db.ExecContext(ctx, `
UPDATE agent_targets
SET provider = 'custom-provider', launch_ref_json = '{"custom":true}', name = 'Custom',
icon_key = 'custom', enabled = 0, source = 'user', sort_order = 777,
created_at_ms = 9, updated_at_ms = 10
WHERE id = ?;
`, testTargetIDClaude); err != nil {
t.Fatalf("seed custom target: %v", err)
}
if err := store.Migrate(ctx); err != nil {
t.Fatalf("Migrate() error = %v", err)
}
var codex Target
if err := store.db.QueryRowContext(ctx, `
SELECT id, provider, launch_ref_json, name, icon_key, icon_url, mask_icon_url, hero_image_url,
enabled, source, sort_order, created_at_ms, updated_at_ms
FROM agent_targets WHERE id = ?
`, testTargetIDCodex).Scan(&codex.ID, &codex.Provider, &codex.LaunchRefJSON, &codex.Name, &codex.IconKey, &codex.IconURL, &codex.MaskIconURL, &codex.HeroImageURL, &codex.Enabled, &codex.Source, &codex.SortOrder, &codex.CreatedAtUnixMS, &codex.UpdatedAtUnixMS); err != nil {
t.Fatalf("query refreshed codex target: %v", err)
}
if codex.Provider != "codex" || codex.LaunchRefJSON != `{"type":"local_cli","provider":"codex"}` ||
codex.Name != "Codex" || codex.IconKey != "codex" ||
codex.IconURL != "tutti-asset://agent/codex.png" ||
codex.MaskIconURL != "tutti-asset://agent/codex-mask.svg" ||
codex.HeroImageURL != "" || codex.Enabled || codex.SortOrder != 10 {
t.Fatalf("refreshed system target = %#v", codex)
}
if codex.CreatedAtUnixMS != 7 || codex.Source != systemTargetSource || codex.UpdatedAtUnixMS == 8 {
t.Fatalf("system target preserved fields/timestamp = %#v", codex)
}
refreshedAt := codex.UpdatedAtUnixMS
if err := store.seedSystemAgentTargets(ctx, refreshedAt+1000); err != nil {
t.Fatalf("seed unchanged system targets: %v", err)
}
if err := store.db.QueryRowContext(ctx, `SELECT updated_at_ms FROM agent_targets WHERE id = ?`, testTargetIDCodex).Scan(&codex.UpdatedAtUnixMS); err != nil {
t.Fatalf("query unchanged codex target: %v", err)
}
if codex.UpdatedAtUnixMS != refreshedAt {
t.Fatalf("unchanged system target updated_at_ms = %d, want %d", codex.UpdatedAtUnixMS, refreshedAt)
}
var custom Target
if err := store.db.QueryRowContext(ctx, `
SELECT id, provider, launch_ref_json, name, icon_key, enabled, source, sort_order, created_at_ms, updated_at_ms
FROM agent_targets WHERE id = ?
`, testTargetIDClaude).Scan(&custom.ID, &custom.Provider, &custom.LaunchRefJSON, &custom.Name, &custom.IconKey, &custom.Enabled, &custom.Source, &custom.SortOrder, &custom.CreatedAtUnixMS, &custom.UpdatedAtUnixMS); err != nil {
t.Fatalf("query custom target: %v", err)
}
if custom.Provider != "custom-provider" || custom.LaunchRefJSON != `{"custom":true}` || custom.Name != "Custom" ||
custom.IconKey != "custom" || custom.Enabled || custom.Source != "user" || custom.SortOrder != 777 ||
custom.CreatedAtUnixMS != 9 || custom.UpdatedAtUnixMS != 10 {
t.Fatalf("custom target was overwritten = %#v", custom)
}
}
func TestStoreMigratePreservesEnabledSystemTargetWithDisabledDefault(t *testing.T) {
t.Parallel()
opts := testOptions(&staticProjectPaths{})
opts.SeedSystemTargets = func(now int64) []Target {
targets := testSeedTargets(now)
targets[0].Enabled = false
return targets
}
store := openTestStore(t, opts)
ctx := context.Background()
target, err := store.GetAgentTarget(ctx, testTargetIDCodex)
if err != nil {
t.Fatalf("GetAgentTarget() error = %v", err)
}
if target.Enabled {
t.Fatalf("fresh system target enabled = true, want descriptor default false")
}
target.Enabled = true
target, err = store.PutAgentTarget(ctx, target)
if err != nil {
t.Fatalf("PutAgentTarget() error = %v", err)
}
enabledAt := target.UpdatedAtUnixMS
if err := store.Migrate(ctx); err != nil {
t.Fatalf("Migrate() error = %v", err)
}
target, err = store.GetAgentTarget(ctx, testTargetIDCodex)
if err != nil {
t.Fatalf("GetAgentTarget() after Migrate error = %v", err)
}
if !target.Enabled {
t.Fatalf("system target enabled = false after Migrate, want persisted user value true")
}
if target.UpdatedAtUnixMS != enabledAt {
t.Fatalf("system target updated_at_ms = %d after enabled-only descriptor mismatch, want %d", target.UpdatedAtUnixMS, enabledAt)
}
}
func TestStoreReportAndListSessionLifecycle(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
state, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
Origin: "runtime",
UserID: "user-1",
Provider: "codex",
ProviderSessionID: "provider-1",
Cwd: "/workspace",
Title: "@renderer.js",
Status: "running",
OccurredAtUnixMS: 100,
})
if err != nil {
t.Fatalf("ReportSessionState() error = %v", err)
}
if !state.Accepted || state.LastEventUnixMS != 100 {
t.Fatalf("state result = %#v", state)
}
if state.Session.UserID != "user-1" {
t.Fatalf("state session user id = %q", state.Session.UserID)
}
if state.Session.Title != "@renderer.js" {
t.Fatalf("state session title = %q, want canonical title", state.Session.Title)
}
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{
WorkspaceID: "ws-1", AgentSessionID: "session-1", TurnID: "turn-1",
Phase: TurnPhaseRunning, OccurredAtUnixMS: 105,
}); err != nil || !accepted {
t.Fatalf("RecordTurnTransition accepted=%v error=%v", accepted, err)
}
first, err := store.ReportSessionMessages(ctx, SessionMessageReport{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
Origin: "runtime",
Messages: []MessageUpdate{{
MessageID: "message-1",
TurnID: "turn-1",
Role: "assistant",
Kind: "text",
Status: "running",
Payload: map[string]any{"text": "hel"},
OccurredAtUnixMS: 110,
}},
})
if err != nil {
t.Fatalf("ReportSessionMessages(first) error = %v", err)
}
if first.AcceptedCount != 1 || first.LatestVersion != 1 {
t.Fatalf("first result = %#v", first)
}
second, err := store.ReportSessionMessages(ctx, SessionMessageReport{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
Origin: "runtime",
Messages: []MessageUpdate{{
MessageID: "message-1",
Status: "completed",
ContentDelta: "lo",
CompletedAtUnixMS: 120,
}},
})
if err != nil {
t.Fatalf("ReportSessionMessages(second) error = %v", err)
}
if second.AcceptedCount != 1 || second.LatestVersion != 2 {
t.Fatalf("second result = %#v", second)
}
page, ok, err := store.ListSessionMessages(ctx, ListSessionMessagesInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
Limit: 10,
})
if err != nil || !ok {
t.Fatalf("ListSessionMessages() ok=%v error=%v", ok, err)
}
if len(page.Messages) != 1 || page.LatestVersion != 2 || page.Messages[0].Payload["text"] != "hello" {
t.Fatalf("page = %#v, want merged message payload", page)
}
session, ok, err := store.GetSession(ctx, "ws-1", "session-1")
if err != nil || !ok || session.UserID != "user-1" {
t.Fatalf("GetSession() = %#v ok=%v error=%v, want user id", session, ok, err)
}
pinned, ok, err := store.UpdateSessionPinned(ctx, "ws-1", "session-1", true)
if err != nil || !ok || pinned.PinnedAtUnixMS <= 0 {
t.Fatalf("UpdateSessionPinned() = %#v ok=%v error=%v", pinned, ok, err)
}
renamed, ok, err := store.UpdateSessionTitle(ctx, "ws-1", "session-1", " final ")
if err != nil || !ok || renamed.Title != "final" || renamed.UpdatedAtUnixMS < pinned.UpdatedAtUnixMS {
t.Fatalf("UpdateSessionTitle() = %#v ok=%v error=%v", renamed, ok, err)
}
if _, err := store.db.ExecContext(ctx, `
UPDATE workspace_agent_sessions
SET updated_at_unix_ms = 9999999999999
WHERE workspace_id = 'ws-1' AND agent_session_id = 'session-1'
`); err != nil {
t.Fatalf("force session updated_at_unix_ms error = %v", err)
}
monotonicRenamed, ok, err := store.UpdateSessionTitle(ctx, "ws-1", "session-1", " monotonic ")
if err != nil || !ok || monotonicRenamed.Title != "monotonic" || monotonicRenamed.UpdatedAtUnixMS != 10000000000000 {
t.Fatalf("UpdateSessionTitle(monotonic) = %#v ok=%v error=%v", monotonicRenamed, ok, err)
}
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-1", AgentSessionID: "session-1",
Provider: "codex", Title: "runtime title", OccurredAtUnixMS: monotonicRenamed.UpdatedAtUnixMS + 1,
}); err != nil {
t.Fatalf("ReportSessionState(runtime title) error = %v", err)
}
sessionAfterRuntimeTitle, ok, err := store.GetSession(ctx, "ws-1", "session-1")
if err != nil || !ok || sessionAfterRuntimeTitle.Title != "monotonic" {
t.Fatalf("GetSession() after runtime title = %#v ok=%v error=%v, want explicit title", sessionAfterRuntimeTitle, ok, err)
}
updatedSettings, ok, err := store.UpdateSessionSettings(ctx, "ws-1", "session-1", "gpt-5.4", map[string]any{
"model": "gpt-5.4",
"permissionModeId": "full-access",
})
if err != nil || !ok || updatedSettings.Model != "gpt-5.4" ||
updatedSettings.Settings["permissionModeId"] != "full-access" ||
updatedSettings.UpdatedAtUnixMS < monotonicRenamed.UpdatedAtUnixMS {
t.Fatalf("UpdateSessionSettings() = %#v ok=%v error=%v", updatedSettings, ok, err)
}
blankRenamed, ok, err := store.UpdateSessionTitle(ctx, "ws-1", "session-1", " ")
if err != nil || !ok || blankRenamed.Title != "" || blankRenamed.InternalRuntimeContext["tuttiInitialTitleEstablished"] != true {
t.Fatalf("UpdateSessionTitle(blank) = %#v ok=%v error=%v, want cleared canonical title", blankRenamed, ok, err)
}
sessionAfterBlankTitle, ok, err := store.GetSession(ctx, "ws-1", "session-1")
if err != nil || !ok || sessionAfterBlankTitle.Title != "" {
t.Fatalf("GetSession() after blank title = %#v ok=%v error=%v", sessionAfterBlankTitle, ok, err)
}
expectedRuntimeContext := cloneJSONMap(sessionAfterBlankTitle.InternalRuntimeContext)
replacementRuntimeContext := cloneJSONMap(expectedRuntimeContext)
replacementRuntimeContext["sessionRuntimeSnapshot"] = map[string]any{"revision": float64(2)}
rebound, updated, err := store.CompareAndSwapSessionRuntimeContext(
ctx, "ws-1", "session-1", expectedRuntimeContext, replacementRuntimeContext,
)
if err != nil || !updated || rebound.InternalRuntimeContext["sessionRuntimeSnapshot"] == nil {
t.Fatalf("CompareAndSwapSessionRuntimeContext() = %#v updated=%v error=%v", rebound, updated, err)
}
if _, updated, err := store.CompareAndSwapSessionRuntimeContext(
ctx, "ws-1", "session-1", expectedRuntimeContext, map[string]any{"stale": true},
); err != nil || updated {
t.Fatalf("stale CompareAndSwapSessionRuntimeContext() updated=%v error=%v", updated, err)
}
removed, err := store.DeleteSession(ctx, "ws-1", "session-1")
if err != nil || !removed {
t.Fatalf("DeleteSession() removed=%v error=%v", removed, err)
}
if _, ok, err := store.GetSession(ctx, "ws-1", "session-1"); err != nil || ok {
t.Fatalf("GetSession() after delete ok=%v error=%v", ok, err)
}
result, err := store.ClearSessions(ctx, "ws-1")
if err != nil {
t.Fatalf("ClearSessions() error = %v", err)
}
if result.RemovedSessions != 1 || result.RemovedMessages != 1 {
t.Fatalf("ClearSessions() = %#v, want tombstoned rows hard-deleted", result)
}
}
func TestStoreMessageVersionsAreSnapshotCursorsAndMayHaveGaps(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-1", AgentSessionID: "session-1", Origin: "runtime",
Provider: "codex", ProviderSessionID: "provider-1", Status: "running", OccurredAtUnixMS: 100,
}); err != nil {
t.Fatal(err)
}
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{
WorkspaceID: "ws-1", AgentSessionID: "session-1", TurnID: "turn-1",
Phase: TurnPhaseSubmitted, OccurredAtUnixMS: 101,
}); err != nil || !accepted {
t.Fatalf("RecordTurnTransition() accepted=%v error=%v", accepted, err)
}
for _, report := range []SessionMessageReport{
{WorkspaceID: "ws-1", AgentSessionID: "session-1", Messages: []MessageUpdate{{
MessageID: "message-a", TurnID: "turn-1", Role: "user", Kind: "text", Status: "completed", Payload: map[string]any{"text": "a"},
}}},
{WorkspaceID: "ws-1", AgentSessionID: "session-1", Messages: []MessageUpdate{{
MessageID: "message-b", TurnID: "turn-1", Role: "assistant", Kind: "text", Status: "running", Payload: map[string]any{"text": "b"},
}}},
{WorkspaceID: "ws-1", AgentSessionID: "session-1", Messages: []MessageUpdate{{
MessageID: "message-b", Status: "completed", ContentDelta: " done",
}}},
} {
if result, err := store.ReportSessionMessages(ctx, report); err != nil || result.AcceptedCount != 1 {
t.Fatalf("ReportSessionMessages() result=%#v error=%v", result, err)
}
}
page, ok, err := store.ListSessionMessages(ctx, ListSessionMessagesInput{WorkspaceID: "ws-1", AgentSessionID: "session-1", Limit: 10})
if err != nil || !ok || page.LatestVersion != 3 || len(page.Messages) != 2 {
t.Fatalf("ListSessionMessages() page=%#v ok=%v error=%v", page, ok, err)
}
if page.Messages[0].MessageID != "message-a" || page.Messages[0].Version != 1 || page.Messages[1].MessageID != "message-b" || page.Messages[1].Version != 3 {
t.Fatalf("current message snapshot versions=%#v, want message-a@1 and message-b@3", page.Messages)
}
incremental, ok, err := store.ListSessionMessages(ctx, ListSessionMessagesInput{WorkspaceID: "ws-1", AgentSessionID: "session-1", AfterVersion: 1, Limit: 10})
if err != nil || !ok || len(incremental.Messages) != 1 || incremental.Messages[0].MessageID != "message-b" || incremental.LatestVersion != 3 {
t.Fatalf("incremental page=%#v ok=%v error=%v", incremental, ok, err)
}
rejected, err := store.ReportSessionMessages(ctx, SessionMessageReport{WorkspaceID: "ws-1", AgentSessionID: "session-1", Messages: []MessageUpdate{{
MessageID: "message-a", TurnID: "turn-2", Status: "completed",
}}})
if err != nil || rejected.AcceptedCount != 0 {
t.Fatalf("rejected update result=%#v error=%v", rejected, err)
}
session, ok, err := store.GetSession(ctx, "ws-1", "session-1")
if err != nil || !ok || session.MessageVersion != 3 {
t.Fatalf("session after rejected update=%#v ok=%v error=%v", session, ok, err)
}
}
func TestSessionAuditIsTurnlessAndDoesNotChangeActiveTurn(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-audit", AgentSessionID: "session-audit", Origin: "runtime", Provider: "codex",
Status: "running", CurrentPhase: "working", OccurredAtUnixMS: 100,
}); err != nil {
t.Fatal(err)
}
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{
WorkspaceID: "ws-audit", AgentSessionID: "session-audit", TurnID: "turn-active",
Phase: TurnPhaseRunning, Origin: TurnOriginUserPrompt, OccurredAtUnixMS: 101,
}); err != nil || !accepted {
t.Fatalf("RecordTurnTransition() accepted=%v error=%v", accepted, err)
}
result, err := store.ReportSessionMessages(ctx, SessionMessageReport{
WorkspaceID: "ws-audit", AgentSessionID: "session-audit", Origin: "runtime", Provider: "codex",
Messages: []MessageUpdate{{MessageID: "goal-control:op-1", Role: "user", Kind: "session_audit", Status: "completed", Payload: map[string]any{"text": "/goal clear"}, OccurredAtUnixMS: 102}},
})
if err != nil || result.AcceptedCount != 1 || result.Messages[0].TurnID != "" {
t.Fatalf("audit result=%#v error=%v", result, err)
}
turn, ok, err := store.GetTurn(ctx, "ws-audit", "session-audit", "turn-active")
if err != nil || !ok || turn.Phase != TurnPhaseRunning {
t.Fatalf("active turn=%#v ok=%v error=%v", turn, ok, err)
}
for _, invalid := range []MessageUpdate{
{MessageID: "ordinary-empty", Role: "assistant", Kind: "text", OccurredAtUnixMS: 103},
{MessageID: "audit-with-turn", TurnID: "turn-active", Role: "user", Kind: "session_audit", OccurredAtUnixMS: 104},
} {
if _, err := store.ReportSessionMessages(ctx, SessionMessageReport{WorkspaceID: "ws-audit", AgentSessionID: "session-audit", Origin: "runtime", Messages: []MessageUpdate{invalid}}); err == nil {
t.Fatalf("ReportSessionMessages(%s) error=nil", invalid.MessageID)
}
}
}
func TestCollaborationMessageIsTurnlessAndDoesNotChangeActiveTurn(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-collaboration", AgentSessionID: "session-collaboration", Origin: "runtime", Provider: "codex",
Status: "running", CurrentPhase: "working", OccurredAtUnixMS: 100,
}); err != nil {
t.Fatal(err)
}
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{
WorkspaceID: "ws-collaboration", AgentSessionID: "session-collaboration", TurnID: "turn-active",
Phase: TurnPhaseRunning, Origin: TurnOriginUserPrompt, OccurredAtUnixMS: 101,
}); err != nil || !accepted {
t.Fatalf("RecordTurnTransition() accepted=%v error=%v", accepted, err)
}
result, err := store.ReportSessionMessages(ctx, SessionMessageReport{
WorkspaceID: "ws-collaboration", AgentSessionID: "session-collaboration", Origin: "runtime", Provider: "codex",
Messages: []MessageUpdate{{
MessageID: "collab:run-1", Role: "assistant", Kind: "collaboration", Status: "completed",
Payload: map[string]any{"runId": "run-1", "adoption": "pending"}, OccurredAtUnixMS: 102,
}},
})
if err != nil || result.AcceptedCount != 1 || result.Messages[0].TurnID != "" {
t.Fatalf("collaboration result=%#v error=%v", result, err)
}
turn, ok, err := store.GetTurn(ctx, "ws-collaboration", "session-collaboration", "turn-active")
if err != nil || !ok || turn.Phase != TurnPhaseRunning {
t.Fatalf("active turn=%#v ok=%v error=%v", turn, ok, err)
}
if _, err := store.ReportSessionMessages(ctx, SessionMessageReport{
WorkspaceID: "ws-collaboration", AgentSessionID: "session-collaboration", Origin: "runtime",
Messages: []MessageUpdate{{
MessageID: "collab:run-with-turn", TurnID: "turn-active", Role: "assistant", Kind: "collaboration", OccurredAtUnixMS: 103,
}},
}); err == nil {
t.Fatal("turn-scoped collaboration message was accepted")
}
}
func TestHistoricalImportCompatibilityCannotBeForgedByOrigin(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
input := SessionMessageReport{
WorkspaceID: "ws-import-boundary", AgentSessionID: "session-import", Origin: "WORKSPACE_AGENT_SESSION_ORIGIN_IMPORTED",
Messages: []MessageUpdate{{MessageID: "legacy", Role: "assistant", Kind: "text", OccurredAtUnixMS: 10}},
}
if _, err := store.ReportSessionMessages(ctx, input); err == nil {
t.Fatal("import origin alone bypassed Turn invariant")
}
input.HistoricalImport = true
result, err := store.ReportSessionMessages(ctx, input)
if err != nil || result.AcceptedCount != 1 || result.Messages[0].TurnID != "" {
t.Fatalf("historical import result=%#v error=%v", result, err)
}
}
func TestHistoricalImportPersistsTrustworthyTurnBoundaries(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
result, err := store.ReportSessionMessages(ctx, SessionMessageReport{
WorkspaceID: "ws-import-turns",
AgentSessionID: "session-import",
Origin: "WORKSPACE_AGENT_SESSION_ORIGIN_IMPORTED",
HistoricalImport: true,
Messages: []MessageUpdate{
{MessageID: "user-1", TurnID: "imported-turn-1", Role: "user", Kind: "text", Status: "completed", OccurredAtUnixMS: 10, StartedAtUnixMS: 10, CompletedAtUnixMS: 10},
{MessageID: "tool-1", TurnID: "imported-turn-1", Role: "assistant", Kind: "tool_call", Status: "completed", OccurredAtUnixMS: 20, StartedAtUnixMS: 20, CompletedAtUnixMS: 30},
{MessageID: "assistant-1", TurnID: "imported-turn-1", Role: "assistant", Kind: "text", Status: "completed", OccurredAtUnixMS: 40, StartedAtUnixMS: 40, CompletedAtUnixMS: 40},
{MessageID: "user-2", TurnID: "imported-turn-2", Role: "user", Kind: "text", Status: "completed", OccurredAtUnixMS: 50, StartedAtUnixMS: 50, CompletedAtUnixMS: 50},
{MessageID: "assistant-2", TurnID: "imported-turn-2", Role: "assistant", Kind: "text", Status: "completed", OccurredAtUnixMS: 60, StartedAtUnixMS: 60, CompletedAtUnixMS: 60},
},
})
if err != nil || result.AcceptedCount != 5 {
t.Fatalf("historical import result=%#v error=%v", result, err)
}
for _, expected := range []struct {
turnID string
startedAtUnixMS int64
settledAtUnixMS int64
finalAssistantMessage string
}{
{turnID: "imported-turn-1", startedAtUnixMS: 10, settledAtUnixMS: 40, finalAssistantMessage: "assistant-1"},
{turnID: "imported-turn-2", startedAtUnixMS: 50, settledAtUnixMS: 60, finalAssistantMessage: "assistant-2"},
} {
turn, ok, err := store.GetTurn(ctx, "ws-import-turns", "session-import", expected.turnID)
if err != nil || !ok {
t.Fatalf("GetTurn(%s) ok=%v error=%v", expected.turnID, ok, err)
}
if turn.Phase != TurnPhaseSettled || turn.Outcome != TurnOutcomeCompleted ||
!turn.Backfilled || turn.Origin != TurnOriginUserPrompt {
t.Fatalf("turn %s = %#v, want settled completed user-prompt backfill", expected.turnID, turn)
}
if turn.StartedAtUnixMS != expected.startedAtUnixMS || turn.SettledAtUnixMS != expected.settledAtUnixMS {
t.Fatalf("turn %s timestamps = %d..%d, want %d..%d", expected.turnID,
turn.StartedAtUnixMS, turn.SettledAtUnixMS, expected.startedAtUnixMS, expected.settledAtUnixMS)
}
if !turn.FinalAssistantMessageResolved || turn.FinalAssistantMessageID != expected.finalAssistantMessage {
t.Fatalf("turn %s final assistant = resolved:%v id:%q, want %q",
expected.turnID, turn.FinalAssistantMessageResolved, turn.FinalAssistantMessageID, expected.finalAssistantMessage)
}
}
}
func TestStoreMessageSemanticsRoundTrip(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{WorkspaceID: "ws-semantics", AgentSessionID: "session-semantics", Provider: "codex", OccurredAtUnixMS: 1}); err != nil {
t.Fatal(err)
}
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{WorkspaceID: "ws-semantics", AgentSessionID: "session-semantics", TurnID: "turn-1", Phase: TurnPhaseRunning, OccurredAtUnixMS: 2}); err != nil || !accepted {
t.Fatalf("turn accepted=%v err=%v", accepted, err)
}
semantics := &MessageSemantics{UserVisibleAssistantResponse: true, TurnSettling: true, NoticeCommand: "compact", NoticeCommandStatus: "running"}
if _, err := store.ReportSessionMessages(ctx, SessionMessageReport{WorkspaceID: "ws-semantics", AgentSessionID: "session-semantics", Messages: []MessageUpdate{{MessageID: "message-1", TurnID: "turn-1", Role: "assistant", Kind: "text", Semantics: semantics, OccurredAtUnixMS: 3}}}); err != nil {
t.Fatal(err)
}
page, ok, err := store.ListSessionMessages(ctx, ListSessionMessagesInput{WorkspaceID: "ws-semantics", AgentSessionID: "session-semantics", Limit: 10})
if err != nil || !ok || len(page.Messages) != 1 || page.Messages[0].Semantics == nil || !page.Messages[0].Semantics.UserVisibleAssistantResponse || page.Messages[0].Semantics.NoticeCommand != "compact" {
t.Fatalf("message semantics page=%#v ok=%v err=%v", page, ok, err)
}
}
func TestReportActivityStateRejectsTurnForDeletedSession(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{WorkspaceID: "ws-deleted-activity", AgentSessionID: "session-deleted-activity", Provider: "codex", OccurredAtUnixMS: 1}); err != nil {
t.Fatal(err)
}
if removed, err := store.DeleteSession(ctx, "ws-deleted-activity", "session-deleted-activity"); err != nil || !removed {
t.Fatalf("DeleteSession removed=%v err=%v", removed, err)
}
result, err := store.ReportActivityState(ctx, ActivityStateReport{
Session: SessionStateReport{WorkspaceID: "ws-deleted-activity", AgentSessionID: "session-deleted-activity", Provider: "codex", OccurredAtUnixMS: 2},
Turn: &TurnTransition{WorkspaceID: "ws-deleted-activity", AgentSessionID: "session-deleted-activity", TurnID: "late-turn", Phase: TurnPhaseSettled, Outcome: TurnOutcomeCompleted, OccurredAtUnixMS: 2},
})
if err != nil || result.State.Accepted || result.TurnAccepted {
t.Fatalf("late activity result=%#v err=%v", result, err)
}
if _, found, err := store.GetTurn(ctx, "ws-deleted-activity", "session-deleted-activity", "late-turn"); err != nil || found {
t.Fatalf("late turn found=%v err=%v", found, err)
}
}
func TestStoreRejectsMessageReferencingUnknownTurn(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-1", AgentSessionID: "session-1", Origin: "runtime",
Provider: "codex", ProviderSessionID: "provider-1", Status: "running", OccurredAtUnixMS: 100,
}); err != nil {
t.Fatal(err)
}
_, err := store.ReportSessionMessages(ctx, SessionMessageReport{
WorkspaceID: "ws-1", AgentSessionID: "session-1", Messages: []MessageUpdate{{
MessageID: "message-1", TurnID: "missing-turn", Role: "user", Kind: "text",
Status: "completed", Payload: map[string]any{"text": "/goal clear"}, OccurredAtUnixMS: 110,
}},
})
if err == nil || !strings.Contains(err.Error(), `references unknown turn "missing-turn"`) {
t.Fatalf("ReportSessionMessages() error=%v, want unknown-turn rejection", err)
}
if _, ok, err := store.GetTurn(ctx, "ws-1", "session-1", "missing-turn"); err != nil || ok {
t.Fatalf("GetTurn(missing-turn) ok=%v error=%v, want no manufactured turn", ok, err)
}
}
func TestStoreClearSessionsTxJoinsCallerTransaction(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-tx",
AgentSessionID: "session-1",
Origin: "runtime",
Provider: "codex",
Status: "completed",
OccurredAtUnixMS: 100,
}); err != nil {
t.Fatalf("ReportSessionState() error = %v", err)
}
// A rollback of the caller's transaction must undo the clear.
tx, err := store.db.BeginTx(ctx, nil)
if err != nil {
t.Fatalf("BeginTx() error = %v", err)
}
result, err := store.ClearSessionsTx(ctx, tx, "ws-tx")
if err != nil {
t.Fatalf("ClearSessionsTx() error = %v", err)
}
if result.RemovedSessions != 1 {
t.Fatalf("ClearSessionsTx() = %#v, want one removed session", result)
}
if err := tx.Rollback(); err != nil {
t.Fatalf("Rollback() error = %v", err)
}
if _, ok, err := store.GetSession(ctx, "ws-tx", "session-1"); err != nil || !ok {
t.Fatalf("GetSession() after rollback ok=%v error=%v, want session restored", ok, err)
}
// A committed transaction applies it.
tx, err = store.db.BeginTx(ctx, nil)
if err != nil {
t.Fatalf("BeginTx(second) error = %v", err)
}
if _, err := store.ClearSessionsTx(ctx, tx, "ws-tx"); err != nil {
t.Fatalf("ClearSessionsTx(second) error = %v", err)
}
if err := tx.Commit(); err != nil {
t.Fatalf("Commit() error = %v", err)
}
if _, ok, err := store.GetSession(ctx, "ws-tx", "session-1"); err != nil || ok {
t.Fatalf("GetSession() after commit ok=%v error=%v, want cleared", ok, err)
}
}
func TestStoreClearSessionsDeletesGoalSagaWithForeignKeysDisabledAndSessionIDReuseStartsClean(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-clear-goal", AgentSessionID: "session-reused", Provider: "codex", OccurredAtUnixMS: 10,
}); err != nil {
t.Fatal(err)
}
if _, state, created, err := store.PrepareGoalControlOperation(ctx, GoalControlOperationPrepare{
OperationID: "old-goal-op", WorkspaceID: "ws-clear-goal", AgentSessionID: "session-reused",
Action: "set", Objective: "old objective", OccurredAtUnixMS: 20,
}); err != nil || !created || state.Revision != 1 {
t.Fatalf("prepare old goal state=%#v created=%v error=%v", state, created, err)
}
if _, created, err := store.PrepareGoalGenerationFence(ctx, GoalGenerationFencePrepare{
FenceID: "old-goal-fence", WorkspaceID: "ws-clear-goal", AgentSessionID: "session-reused",
TargetOperationID: "old-goal-op", ClientSubmitID: "old-binding-revoke", OccurredAtUnixMS: 21,
}); err != nil || !created {
t.Fatalf("prepare old goal fence created=%v error=%v", created, err)
}
if _, err := store.db.ExecContext(ctx, `PRAGMA foreign_keys = OFF`); err != nil {
t.Fatalf("disable foreign keys: %v", err)
}
seedRuntimeDeletionSaga(t, store, "ws-clear-goal", "session-reused")
result, err := store.ClearSessions(ctx, "ws-clear-goal")
if err != nil || result.RemovedSessions != 1 {
t.Fatalf("ClearSessions() result=%#v error=%v", result, err)
}
for _, table := range []string{"workspace_agent_runtime_operation_events", "workspace_agent_runtime_operations", "workspace_agent_goal_generation_fences", "workspace_agent_goal_control_operations", "workspace_agent_session_goals"} {
var count int
if err := store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM `+table+` WHERE workspace_id = ?`, "ws-clear-goal").Scan(&count); err != nil || count != 0 {
t.Fatalf("%s count=%d error=%v, want empty", table, count, err)
}
}
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-clear-goal", AgentSessionID: "session-reused", Provider: "codex", OccurredAtUnixMS: 30,
}); err != nil {
t.Fatalf("recreate session: %v", err)
}
if state, found, err := store.GetSessionGoalState(ctx, "ws-clear-goal", "session-reused"); err != nil || found {
t.Fatalf("recreated goal state=%#v found=%v error=%v, want absent", state, found, err)
}
if _, state, created, err := store.PrepareGoalControlOperation(ctx, GoalControlOperationPrepare{
OperationID: "new-goal-op", WorkspaceID: "ws-clear-goal", AgentSessionID: "session-reused",
Action: "set", Objective: "new objective", OccurredAtUnixMS: 40,
}); err != nil || !created || state.Revision != 1 {
t.Fatalf("prepare recreated goal state=%#v created=%v error=%v, want revision 1", state, created, err)
}
seedInteractionTurn(t, store, "ws-clear-goal", "session-reused", "turn-prepared", 50)
if _, result, err := store.UpsertInteraction(ctx, InteractionUpsert{
WorkspaceID: "ws-clear-goal", AgentSessionID: "session-reused", TurnID: "turn-prepared",
RequestID: "request-reused", Kind: InteractionKindQuestion, Status: InteractionStatusPending, OccurredAtUnixMS: 51,
}); err != nil || result != InteractionTransitionApplied {
t.Fatalf("recreate interaction result=%v error=%v", result, err)
}
if _, created, err := store.PrepareRuntimeOperation(ctx, RuntimeOperationPrepare{
OperationID: "new-runtime-op", WorkspaceID: "ws-clear-goal", AgentSessionID: "session-reused",
Kind: RuntimeOperationKindInteractiveResponse, TurnID: "turn-prepared", RequestID: "request-reused", OccurredAtMS: 52,
}); err != nil || !created {
t.Fatalf("prepare recreated runtime operation created=%v error=%v", created, err)
}
claimable, err := store.ListClaimableRuntimeOperations(ctx, ListClaimableRuntimeOperationsInput{WorkspaceID: "ws-clear-goal", NowUnixMS: 100, Limit: 10})
if err != nil || len(claimable) != 1 || claimable[0].OperationID != "new-runtime-op" {
t.Fatalf("claimable after recreation=%#v error=%v", claimable, err)
}
}
func TestStoreSessionDeleteVariantsPreserveAndTerminalizeSagaWithForeignKeysDisabled(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
remove func(context.Context, *Store) error
}{
{name: "single", remove: func(ctx context.Context, store *Store) error {
_, err := store.DeleteSession(ctx, "ws-delete-goal", "session-1")
return err
}},
{name: "batch", remove: func(ctx context.Context, store *Store) error {
_, err := store.DeleteSessionsBatch(ctx, DeleteSessionsBatchInput{WorkspaceID: "ws-delete-goal", SessionIDs: []string{"session-1"}})
return err
}},
} {
t.Run(tc.name, func(t *testing.T) {
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-delete-goal", AgentSessionID: "session-1", Provider: "codex", OccurredAtUnixMS: 10,
}); err != nil {
t.Fatal(err)
}
goalOperationID := "goal-op-" + tc.name
if _, _, _, err := store.PrepareGoalControlOperation(ctx, GoalControlOperationPrepare{
OperationID: goalOperationID, WorkspaceID: "ws-delete-goal", AgentSessionID: "session-1",
Action: "set", Objective: "objective", OccurredAtUnixMS: 20,
}); err != nil {
t.Fatal(err)
}
if _, created, err := store.PrepareGoalGenerationFence(ctx, GoalGenerationFencePrepare{
FenceID: "goal-fence-" + tc.name, WorkspaceID: "ws-delete-goal", AgentSessionID: "session-1",
TargetOperationID: goalOperationID, ClientSubmitID: "binding-revoke-" + tc.name, OccurredAtUnixMS: 21,
}); err != nil || !created {
t.Fatalf("prepare goal fence created=%v error=%v", created, err)
}
if _, err := store.db.ExecContext(ctx, `PRAGMA foreign_keys = OFF`); err != nil {
t.Fatal(err)
}
seedRuntimeDeletionSaga(t, store, "ws-delete-goal", "session-1")
if err := tc.remove(ctx, store); err != nil {
t.Fatal(err)
}
for _, table := range []string{"workspace_agent_runtime_operation_events", "workspace_agent_runtime_operations", "workspace_agent_goal_generation_fences", "workspace_agent_goal_control_operations", "workspace_agent_session_goals"} {
var count int
if err := store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM `+table+` WHERE workspace_id = ?`, "ws-delete-goal").Scan(&count); err != nil || count == 0 {
t.Fatalf("%s count=%d error=%v, want preserved rows", table, count, err)
}
}
var runtimePending, goalPending, fencePending int
if err := store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM workspace_agent_runtime_operations WHERE workspace_id=? AND status IN ('prepared','leased')`, "ws-delete-goal").Scan(&runtimePending); err != nil {
t.Fatal(err)
}
if err := store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM workspace_agent_goal_control_operations WHERE workspace_id=? AND status IN ('prepared','dispatched')`, "ws-delete-goal").Scan(&goalPending); err != nil {
t.Fatal(err)
}
if err := store.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM workspace_agent_goal_generation_fences WHERE workspace_id=? AND status IN ('pending','processing')`, "ws-delete-goal").Scan(&fencePending); err != nil {
t.Fatal(err)
}
if runtimePending != 0 || goalPending != 0 || fencePending != 0 {
t.Fatalf("executable saga state remains: runtime=%d goal=%d fence=%d", runtimePending, goalPending, fencePending)
}
})
}
}
func seedRuntimeDeletionSaga(t *testing.T, store *Store, workspaceID string, sessionID string) {
t.Helper()
for _, row := range []struct {
operationID string
status string
turnID string
result any
leaseOwner any
leaseExpiry any
nextAttempt any
completedAt any
}{
{operationID: "old-runtime-prepared", status: RuntimeOperationStatusPrepared, turnID: "turn-prepared", nextAttempt: int64(10)},
{operationID: "old-runtime-leased", status: RuntimeOperationStatusLeased, turnID: "turn-leased", leaseOwner: "worker", leaseExpiry: int64(1000)},
{operationID: "old-runtime-completed", status: RuntimeOperationStatusCompleted, turnID: "turn-completed", result: RuntimeOperationResultCanceled, completedAt: int64(30)},
} {
query := `INSERT INTO workspace_agent_runtime_operations (
operation_id, workspace_id, agent_session_id, kind, status, result, turn_id,
payload_json, lease_owner, lease_expires_at_unix_ms, next_attempt_at_unix_ms,
created_at_unix_ms, updated_at_unix_ms, completed_at_unix_ms
) VALUES (?, ?, ?, 'cancel_turn', ?, ?, ?, '{}', ?, ?, ?, 20, 20, ?)`
if _, err := store.db.Exec(query, row.operationID, workspaceID, sessionID, row.status, row.result, row.turnID,
row.leaseOwner, row.leaseExpiry, row.nextAttempt, row.completedAt); err != nil {
t.Fatalf("seed runtime operation %s: %v", row.status, err)
}
}
if _, err := store.db.Exec(`INSERT INTO workspace_agent_runtime_operation_events (
operation_id, workspace_id, agent_session_id, kind, payload_json, created_at_unix_ms
) VALUES ('old-runtime-completed', ?, ?, 'turn_canceled', '{}', 30)`, workspaceID, sessionID); err != nil {
t.Fatalf("seed runtime operation event: %v", err)
}
if _, err := store.db.Exec(`INSERT INTO workspace_agent_runtime_operations (
operation_id, workspace_id, agent_session_id, kind, status, result, turn_id,
request_id, payload_json, created_at_unix_ms, updated_at_unix_ms, completed_at_unix_ms
) VALUES ('old-runtime-interactive', ?, ?, 'interactive_response', 'completed', 'answered',
'turn-prepared', 'request-reused', '{}', 31, 31, 31)`, workspaceID, sessionID); err != nil {
t.Fatalf("seed reused interactive runtime operation: %v", err)
}
if _, err := store.db.Exec(`INSERT INTO workspace_agent_runtime_operation_events (
operation_id, workspace_id, agent_session_id, kind, payload_json, created_at_unix_ms
) VALUES ('old-runtime-interactive', ?, ?, 'interactive_completed', '{}', 31)`, workspaceID, sessionID); err != nil {
t.Fatalf("seed reused interactive runtime operation event: %v", err)
}
}
func TestStoreWorkspaceExistsCallbackGatesWrites(t *testing.T) {
t.Parallel()
opts := testOptions(&staticProjectPaths{})
opts.WorkspaceExists = func(_ context.Context, workspaceID string) error {
if workspaceID != "ws-known" {
return errTestWorkspaceNotFound
}
return nil
}
store := openTestStore(t, opts)
ctx := context.Background()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-unknown",
AgentSessionID: "session-1",
Status: "running",
}); !errors.Is(err, errTestWorkspaceNotFound) {
t.Fatalf("ReportSessionState(unknown) error = %v, want workspace not found", err)
}
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-known",
AgentSessionID: "session-1",
Status: "running",
}); err != nil {
t.Fatalf("ReportSessionState(known) error = %v", err)
}
if _, _, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
WorkspaceID: "ws-unknown", SectionKey: RailSectionKeyConversations,
}); !errors.Is(err, errTestWorkspaceNotFound) {
t.Fatalf("ListWorkspaceGeneratedFileTurns(unknown) error = %v, want workspace not found", err)
}
}
func TestStoreClassifiesRailSectionsWithInjectedProjectPaths(t *testing.T) {
t.Parallel()
projects := &staticProjectPaths{}
store := openTestStore(t, testOptions(projects))
ctx := context.Background()
root := t.TempDir()
repo := filepath.Join(root, "repo")
repoSubdir := filepath.Join(repo, "pkg")
otherDir := filepath.Join(root, "other")
for _, path := range []string{repoSubdir, otherDir} {
if err := mkdirAll(path); err != nil {
t.Fatalf("mkdir %q error = %v", path, err)
}
}
projects.paths = []string{repo}
repoCanonical := NormalizeProjectPath(repo)
projectResult, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-rail",
AgentSessionID: "session-project",
Origin: "runtime",
Provider: "codex",
Cwd: repoSubdir,
Status: "completed",
OccurredAtUnixMS: 100,
})
if err != nil {
t.Fatalf("ReportSessionState(project) error = %v", err)
}
wantProjectKey := RailSectionKeyForProject(repoCanonical)
if projectResult.Session.RailSectionKey != wantProjectKey {
t.Fatalf("ReportSessionState(project) rail key = %q, want %q", projectResult.Session.RailSectionKey, wantProjectKey)
}
projects.paths = []string{otherDir}
updatedProjectResult, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-rail",
AgentSessionID: "session-project",
Origin: "runtime",
Provider: "codex",
Cwd: otherDir,
Status: "completed",
OccurredAtUnixMS: 200,
})
if err != nil {
t.Fatalf("ReportSessionState(project cwd changed) error = %v", err)
}
if updatedProjectResult.Session.RailSectionKey != wantProjectKey {
t.Fatalf(
"ReportSessionState(project cwd changed) rail key = %q, want immutable %q",
updatedProjectResult.Session.RailSectionKey,
wantProjectKey,
)
}
projects.paths = []string{repo}
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-rail",
AgentSessionID: "session-other",
Origin: "runtime",
Provider: "codex",
Cwd: otherDir,
Status: "completed",
OccurredAtUnixMS: 100,
}); err != nil {
t.Fatalf("ReportSessionState(other) error = %v", err)
}
projectPage, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-rail",
SectionKey: wantProjectKey,
Limit: 10,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(project) ok=%v error=%v", ok, err)
}
if len(projectPage.Sessions) != 1 || projectPage.Sessions[0].ID != "session-project" || projectPage.Sessions[0].RailSectionKey != wantProjectKey {
t.Fatalf("project page = %#v, want session-project", projectPage.Sessions)
}
projectSession, ok, err := store.GetSession(ctx, "ws-rail", "session-project")
if err != nil || !ok || projectSession.RailSectionKey != wantProjectKey {
t.Fatalf("GetSession(project) = %#v ok=%v error=%v, want rail key %q", projectSession, ok, err, wantProjectKey)
}
conversationsPage, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-rail",
SectionKey: RailSectionKeyConversations,
Limit: 10,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(conversations) ok=%v error=%v", ok, err)
}
if len(conversationsPage.Sessions) != 1 || conversationsPage.Sessions[0].ID != "session-other" || conversationsPage.Sessions[0].RailSectionKey != RailSectionKeyConversations {
t.Fatalf("conversations page = %#v, want session-other", conversationsPage.Sessions)
}
}
func TestStoreImportedRailUsesSelectedNestedProjectAndRepairsAncestor(t *testing.T) {
t.Parallel()
root := t.TempDir()
parent := filepath.Join(root, "dev")
nested := filepath.Join(parent, "kage")
if err := mkdirAll(nested); err != nil {
t.Fatalf("mkdir nested project error = %v", err)
}
parent = NormalizeProjectPath(parent)
nested = NormalizeProjectPath(nested)
projects := &staticProjectPaths{paths: []string{parent}}
store := openTestStore(t, testOptions(projects))
ctx := context.Background()
importedContext := map[string]any{"imported": true}
initial, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-import-rail",
AgentSessionID: "selected-nested",
Origin: "WORKSPACE_AGENT_SESSION_ORIGIN_IMPORTED",
Provider: "codex",
RuntimeContext: importedContext,
Cwd: nested,
ImportProjectPath: nested,
Status: "completed",
OccurredAtUnixMS: 100,
})
if err != nil {
t.Fatalf("ReportSessionState(selected nested) error = %v", err)
}
wantNestedKey := RailSectionKeyForProject(nested)
if initial.Session.RailSectionKey != wantNestedKey {
t.Fatalf("selected nested rail key = %q, want %q", initial.Session.RailSectionKey, wantNestedKey)
}
legacy, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-import-rail",
AgentSessionID: "legacy-parent",
Origin: "WORKSPACE_AGENT_SESSION_ORIGIN_IMPORTED",
Provider: "codex",
RuntimeContext: importedContext,
Cwd: nested,
Status: "completed",
OccurredAtUnixMS: 100,
})
if err != nil {
t.Fatalf("ReportSessionState(legacy parent) error = %v", err)
}
if legacy.Session.RailSectionKey != RailSectionKeyForProject(parent) {
t.Fatalf("legacy rail key = %q, want parent before repair", legacy.Session.RailSectionKey)
}
repaired, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-import-rail",
AgentSessionID: "legacy-parent",
Origin: "WORKSPACE_AGENT_SESSION_ORIGIN_IMPORTED",
Provider: "codex",
RuntimeContext: importedContext,
Cwd: nested,
ImportProjectPath: nested,
Status: "completed",
OccurredAtUnixMS: 50,
})
if err != nil {
t.Fatalf("ReportSessionState(repair nested) error = %v", err)
}
if repaired.Session.RailSectionKey != wantNestedKey {
t.Fatalf("repaired rail key = %q, want %q", repaired.Session.RailSectionKey, wantNestedKey)
}
preserved, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-import-rail",
AgentSessionID: "legacy-parent",
Origin: "WORKSPACE_AGENT_SESSION_ORIGIN_IMPORTED",
Provider: "codex",
RuntimeContext: importedContext,
Cwd: nested,
ImportProjectPath: parent,
Status: "completed",
OccurredAtUnixMS: 300,
})
if err != nil {
t.Fatalf("ReportSessionState(preserve nested) error = %v", err)
}
if preserved.Session.RailSectionKey != wantNestedKey {
t.Fatalf("ancestor reimport rail key = %q, want existing nested %q", preserved.Session.RailSectionKey, wantNestedKey)
}
ordinary, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-import-rail",
AgentSessionID: "ordinary-runtime",
Origin: "runtime",
Provider: "codex",
RuntimeContext: map[string]any{"imported": false},
Cwd: nested,
ImportProjectPath: nested,
Status: "completed",
OccurredAtUnixMS: 100,
})
if err != nil {
t.Fatalf("ReportSessionState(ordinary runtime) error = %v", err)
}
wantParentKey := RailSectionKeyForProject(parent)
if ordinary.Session.RailSectionKey != wantParentKey {
t.Fatalf("ordinary runtime rail key = %q, want injected parent %q", ordinary.Session.RailSectionKey, wantParentKey)
}
noProject, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: "ws-import-rail",
AgentSessionID: "no-project-import",
Origin: "WORKSPACE_AGENT_SESSION_ORIGIN_IMPORTED",
Provider: "codex",
RuntimeContext: map[string]any{"externalImportNoProject": true, "imported": true},
Cwd: nested,
ImportProjectPath: nested,
Status: "completed",
OccurredAtUnixMS: 100,
})
if err != nil {
t.Fatalf("ReportSessionState(no-project import) error = %v", err)
}
if noProject.Session.RailSectionKey != RailSectionKeyConversations {
t.Fatalf("no-project import rail key = %q, want conversations", noProject.Session.RailSectionKey)
}
}
func TestStoreListSessionSectionFiltersHiddenSessionsBeforePagination(t *testing.T) {
t.Parallel()
projects := &staticProjectPaths{paths: []string{"/workspace/app"}}
store := openTestStore(t, testOptions(projects))
ctx := context.Background()
for _, input := range []SessionStateReport{
{
WorkspaceID: "ws-rail-visible",
AgentSessionID: "bbb-visible-newer",
Origin: "runtime",
Provider: "codex",
Cwd: "/workspace/app",
Title: "visible newer",
Status: "completed",
OccurredAtUnixMS: 100,
},
{
WorkspaceID: "ws-rail-visible",
AgentSessionID: "ccc-visible-older",
Origin: "runtime",
Provider: "codex",
Cwd: "/workspace/app",
Title: "visible older",
Status: "completed",
RuntimeContext: map[string]any{"visible": true},
OccurredAtUnixMS: 100,
},
{
WorkspaceID: "ws-rail-visible",
AgentSessionID: "aaa-hidden",
Origin: "runtime",
Provider: "claude-code",
Cwd: "/workspace/app",
Title: "hidden",
Status: "completed",
RuntimeContext: map[string]any{"visible": false},
OccurredAtUnixMS: 100,
},
} {
if _, err := store.ReportSessionState(ctx, input); err != nil {
t.Fatalf("ReportSessionState(%s) error = %v", input.AgentSessionID, err)
}
}
if _, err := store.db.ExecContext(ctx, `
UPDATE workspace_agent_sessions
SET created_at_unix_ms = 1000, updated_at_unix_ms = 1000
WHERE workspace_id = ?`, "ws-rail-visible"); err != nil {
t.Fatalf("normalize session timestamps error = %v", err)
}
page, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-rail-visible",
SectionKey: RailSectionKeyForProject("/workspace/app"),
Limit: 1,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(first) ok=%v error=%v", ok, err)
}
if len(page.Sessions) != 1 || page.Sessions[0].ID != "bbb-visible-newer" {
t.Fatalf("first page sessions = %#v, want bbb-visible-newer", page.Sessions)
}
if !page.HasMore || !strings.HasSuffix(page.NextCursor, "|bbb-visible-newer") {
t.Fatalf("first page state = hasMore %v cursor %q, want visible cursor with more", page.HasMore, page.NextCursor)
}
if page.TotalCount != 2 {
t.Fatalf("first page total count = %d, want 2 visible sessions", page.TotalCount)
}
next, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-rail-visible",
SectionKey: RailSectionKeyForProject("/workspace/app"),
CursorSortTimeUnixMS: page.Sessions[0].CreatedAtUnixMS,
CursorSessionID: page.Sessions[0].ID,
Limit: 1,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(next) ok=%v error=%v", ok, err)
}
if len(next.Sessions) != 1 || next.Sessions[0].ID != "ccc-visible-older" {
t.Fatalf("next page sessions = %#v, want ccc-visible-older", next.Sessions)
}
if next.HasMore || next.NextCursor != "" {
t.Fatalf("next page state = hasMore %v cursor %q, want exhausted", next.HasMore, next.NextCursor)
}
if next.TotalCount != 2 {
t.Fatalf("next page total count = %d, want stable total 2", next.TotalCount)
}
}
func TestStoreListSessionSectionOrdersAndPagesByLatestTurnStart(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{"/workspace/app"}}))
ctx := context.Background()
for _, report := range []ActivityStateReport{
{
Session: SessionStateReport{
WorkspaceID: "ws-turn-order",
AgentSessionID: "older-start-newer-update",
Origin: "runtime",
Provider: "codex",
Cwd: "/workspace/app",
Status: "working",
OccurredAtUnixMS: 9_000,
},
Turn: &TurnTransition{
WorkspaceID: "ws-turn-order",
AgentSessionID: "older-start-newer-update",
TurnID: "turn-older",
Phase: TurnPhaseRunning,
StartedAtUnixMS: 2_000,
OccurredAtUnixMS: 9_000,
},
},
{
Session: SessionStateReport{
WorkspaceID: "ws-turn-order",
AgentSessionID: "newer-start-older-update",
Origin: "runtime",
Provider: "codex",
Cwd: "/workspace/app",
Status: "working",
OccurredAtUnixMS: 4_000,
},
Turn: &TurnTransition{
WorkspaceID: "ws-turn-order",
AgentSessionID: "newer-start-older-update",
TurnID: "turn-newer",
Phase: TurnPhaseRunning,
StartedAtUnixMS: 3_000,
OccurredAtUnixMS: 4_000,
},
},
} {
if _, err := store.ReportActivityState(ctx, report); err != nil {
t.Fatalf("ReportActivityState(%s) error = %v", report.Session.AgentSessionID, err)
}
}
page, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-turn-order",
SectionKey: RailSectionKeyForProject("/workspace/app"),
Limit: 1,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(first) ok=%v error=%v", ok, err)
}
if len(page.Sessions) != 1 || page.Sessions[0].ID != "newer-start-older-update" {
t.Fatalf("first page sessions = %#v, want latest turn start", page.Sessions)
}
if !page.HasMore || page.NextCursor != "3000|newer-start-older-update" {
t.Fatalf("first page hasMore=%v cursor=%q", page.HasMore, page.NextCursor)
}
if page.TotalCount != 2 {
t.Fatalf("first page total count = %d, want 2", page.TotalCount)
}
next, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-turn-order",
SectionKey: RailSectionKeyForProject("/workspace/app"),
CursorSortTimeUnixMS: 3_000,
CursorSessionID: "newer-start-older-update",
Limit: 1,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(next) ok=%v error=%v", ok, err)
}
if len(next.Sessions) != 1 || next.Sessions[0].ID != "older-start-newer-update" {
t.Fatalf("next page sessions = %#v, want older turn start", next.Sessions)
}
if next.HasMore || next.NextCursor != "" {
t.Fatalf("next page hasMore=%v cursor=%q, want exhausted", next.HasMore, next.NextCursor)
}
if next.TotalCount != 2 {
t.Fatalf("next page total count = %d, want stable total 2", next.TotalCount)
}
}
func TestStoreListPinnedSessionPageOrdersByPinnedTime(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
ctx := context.Background()
for _, input := range []SessionStateReport{
{
WorkspaceID: "ws-pinned-page",
AgentSessionID: "newer-pinned",
Origin: "runtime",
Provider: "codex",
Status: "completed",
OccurredAtUnixMS: 100,
},
{
WorkspaceID: "ws-pinned-page",
AgentSessionID: "older-pinned",
Origin: "runtime",
Provider: "codex",
Status: "completed",
OccurredAtUnixMS: 100,
},
{
WorkspaceID: "ws-pinned-page",
AgentSessionID: "unpinned",
Origin: "runtime",
Provider: "codex",
Status: "completed",
OccurredAtUnixMS: 100,
},
} {
if _, err := store.ReportSessionState(ctx, input); err != nil {
t.Fatalf("ReportSessionState(%s) error = %v", input.AgentSessionID, err)
}
}
for sessionID, pinnedAt := range map[string]int64{
"newer-pinned": 2000,
"older-pinned": 1000,
} {
if _, err := store.db.ExecContext(ctx, `
UPDATE workspace_agent_sessions
SET pinned_at_unix_ms = ?
WHERE workspace_id = ? AND agent_session_id = ?`, pinnedAt, "ws-pinned-page", sessionID); err != nil {
t.Fatalf("set pinned_at_unix_ms(%s) error = %v", sessionID, err)
}
}
conversations, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-pinned-page",
SectionKey: RailSectionKeyConversations,
Limit: 10,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(conversations) ok=%v error=%v", ok, err)
}
if len(conversations.Sessions) != 1 || conversations.Sessions[0].ID != "unpinned" {
t.Fatalf("ordinary conversations = %#v, want only unpinned", conversations.Sessions)
}
if conversations.TotalCount != 1 {
t.Fatalf("ordinary conversations total count = %d, want 1", conversations.TotalCount)
}
page, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-pinned-page",
SectionKey: PinnedSessionPageKey,
Limit: 1,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(pinned first) ok=%v error=%v", ok, err)
}
if len(page.Sessions) != 1 || page.Sessions[0].ID != "newer-pinned" {
t.Fatalf("pinned first sessions = %#v, want newer-pinned", page.Sessions)
}
if !page.HasMore || page.NextCursor != "2000|newer-pinned" {
t.Fatalf("pinned first page state = hasMore %v cursor %q", page.HasMore, page.NextCursor)
}
if page.TotalCount != 2 {
t.Fatalf("pinned first page total count = %d, want 2", page.TotalCount)
}
next, ok, err := store.ListSessionSection(ctx, ListSessionSectionInput{
WorkspaceID: "ws-pinned-page",
SectionKey: PinnedSessionPageKey,
CursorSortTimeUnixMS: page.Sessions[0].PinnedAtUnixMS,
CursorSessionID: page.Sessions[0].ID,
Limit: 2,
})
if err != nil || !ok {
t.Fatalf("ListSessionSection(pinned next) ok=%v error=%v", ok, err)
}
if len(next.Sessions) != 1 || next.Sessions[0].ID != "older-pinned" {
t.Fatalf("pinned next sessions = %#v, want older-pinned", next.Sessions)
}
if next.HasMore || next.NextCursor != "" {
t.Fatalf("pinned next page state = hasMore %v cursor %q, want exhausted", next.HasMore, next.NextCursor)
}
if next.TotalCount != 2 {
t.Fatalf("pinned next page total count = %d, want stable total 2", next.TotalCount)
}
}
func TestStoreTargetNormalizationAndSkippableRows(t *testing.T) {
t.Parallel()
errInvalidTestTarget := errors.New("invalid test target")
opts := testOptions(&staticProjectPaths{})
opts.NormalizeTarget = func(target Target) (Target, error) {
target.Name = strings.TrimSpace(target.Name)
if strings.Contains(target.Name, "broken") {
return Target{}, fmt.Errorf("%w: %s", errInvalidTestTarget, target.Name)
}
return target, nil
}
opts.IsSkippableTargetError = func(err error) bool {
return errors.Is(err, errInvalidTestTarget)
}
store := openTestStore(t, opts)
ctx := context.Background()
if _, err := store.PutAgentTarget(ctx, Target{
ID: "custom",
Provider: "codex",
LaunchRefJSON: `{"type":"local_cli","provider":"codex"}`,
Name: " Custom ",
MaskIconURL: " data:image/svg+xml;base64,mask ",
Enabled: true,
Source: "user",
}); err != nil {
t.Fatalf("PutAgentTarget() error = %v", err)
}
target, err := store.GetAgentTarget(ctx, "custom")
if err != nil {
t.Fatalf("GetAgentTarget() error = %v", err)
}
if target.Name != "Custom" {
t.Fatalf("target name = %q, want normalized Custom", target.Name)
}
if target.MaskIconURL != " data:image/svg+xml;base64,mask " {
t.Fatalf("target mask icon URL = %q", target.MaskIconURL)
}
if _, err := store.PutAgentTarget(ctx, Target{ID: "bad", Name: "broken row"}); !errors.Is(err, errInvalidTestTarget) {
t.Fatalf("PutAgentTarget(invalid) error = %v, want invalid test target", err)
}
now := int64(1700000000000)
if _, err := store.db.ExecContext(ctx, `
INSERT INTO agent_targets (id, provider, launch_ref_json, name, icon_key, enabled, source, sort_order, created_at_ms, updated_at_ms)
VALUES ('broken-row', 'codex', '{}', 'broken stored row', NULL, 1, 'user', 5, ?, ?)
`, now, now); err != nil {
t.Fatalf("insert broken target fixture: %v", err)
}
targets, err := store.ListAgentTargets(ctx)
if err != nil {
t.Fatalf("ListAgentTargets() error = %v", err)
}
for _, listed := range targets {
if listed.ID == "broken-row" {
t.Fatalf("ListAgentTargets() returned skippable row: %#v", targets)
}
}
if err := store.DeleteAgentTarget(ctx, "custom"); err != nil {
t.Fatalf("DeleteAgentTarget() error = %v", err)
}
if _, err := store.GetAgentTarget(ctx, "custom"); !errors.Is(err, ErrAgentTargetNotFound) {
t.Fatalf("GetAgentTarget(deleted) error = %v, want ErrAgentTargetNotFound", err)
}
}