Files
wehub-resource-sync f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

1670 lines
55 KiB
Go

package parser
import (
"context"
"errors"
"os"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/testjsonl"
)
func TestCodexProviderSourceMethods(t *testing.T) {
base := t.TempDir()
root := filepath.Join(base, "sessions")
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e1"
sourcePath := writeCodexProviderSession(t, root, uuid, "Rename me")
indexPath := filepath.Join(base, CodexSessionIndexFilename)
require.NoError(t, os.WriteFile(indexPath, []byte(
`{"id":"`+uuid+`","thread_name":"Renamed title","updated_at":"2026-06-11T17:34:20Z"}`+"\n",
), 0o644))
newer := time.Now().Add(time.Hour)
require.NoError(t, os.Chtimes(indexPath, newer, newer))
provider, ok := NewProvider(AgentCodex, ProviderConfig{
Roots: []string{root},
Machine: "devbox",
})
require.True(t, ok)
plan, err := provider.WatchPlan(context.Background())
require.NoError(t, err)
require.Len(t, plan.Roots, 2)
assert.Equal(t, root, plan.Roots[0].Path)
assert.True(t, plan.Roots[0].Recursive)
assert.Equal(t, []string{"*.jsonl"}, plan.Roots[0].IncludeGlobs)
assert.Equal(t, base, plan.Roots[1].Path)
assert.False(t, plan.Roots[1].Recursive)
assert.Equal(t, []string{CodexSessionIndexFilename}, plan.Roots[1].IncludeGlobs)
discovered, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, discovered, 1)
source := discovered[0]
assert.Equal(t, AgentCodex, source.Provider)
assert.Equal(t, sourcePath, source.DisplayPath)
assert.Equal(t, sourcePath, source.FingerprintKey)
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
FullSessionID: "host~codex:" + uuid,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, sourcePath, found.DisplayPath)
for _, path := range []string{sourcePath, indexPath} {
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: path, EventKind: "write"},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, sourcePath, changed[0].DisplayPath)
}
info, err := os.Stat(sourcePath)
require.NoError(t, err)
fingerprint, err := provider.Fingerprint(context.Background(), found)
require.NoError(t, err)
assert.Equal(t, sourcePath, fingerprint.Key)
assert.Equal(t, info.Size(), fingerprint.Size)
assert.Equal(t, newer.UnixNano(), fingerprint.MTimeNS)
wantInode, wantDevice := sourceFileIdentity(info)
assert.Equal(t, wantInode, fingerprint.Inode)
assert.Equal(t, wantDevice, fingerprint.Device)
assert.NotEmpty(t, fingerprint.Hash)
outcome, err := provider.Parse(context.Background(), ParseRequest{
Source: found,
Fingerprint: fingerprint,
})
require.NoError(t, err)
require.True(t, outcome.ResultSetComplete)
require.Len(t, outcome.Results, 1)
result := outcome.Results[0]
assert.Equal(t, DataVersionCurrent, result.DataVersion)
assert.Equal(t, "codex:"+uuid, result.Result.Session.ID)
assert.Equal(t, AgentCodex, result.Result.Session.Agent)
assert.Equal(t, "devbox", result.Result.Session.Machine)
assert.Equal(t, "api", result.Result.Session.Project)
assert.Equal(t, "Renamed title", result.Result.Session.SessionName)
assert.Equal(t, fingerprint.Hash, result.Result.Session.File.Hash)
assert.Len(t, result.Result.Messages, 1)
}
func TestCodexProviderForceParseReloadsSameStatSessionIndex(t *testing.T) {
base := t.TempDir()
root := filepath.Join(base, "sessions")
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229f3"
writeCodexProviderSession(t, root, uuid, "keep this prompt")
indexPath := filepath.Join(base, CodexSessionIndexFilename)
original := `{"id":"` + uuid + `","thread_name":"Alpha title"}` + "\n"
rewritten := `{"id":"` + uuid + `","thread_name":"Bravo title"}` + "\n"
require.Len(t, rewritten, len(original), "index fixtures must have equal length")
require.NoError(t, os.WriteFile(indexPath, []byte(original), 0o644))
stableTime := time.Unix(1_800_000_000, 123_000_000)
require.NoError(t, os.Chtimes(indexPath, stableTime, stableTime))
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
fingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
first, err := provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: fingerprint,
})
require.NoError(t, err)
require.Len(t, first.Results, 1)
assert.Equal(t, "Alpha title", first.Results[0].Result.Session.SessionName)
before, err := os.Stat(indexPath)
require.NoError(t, err)
require.NoError(t, os.WriteFile(indexPath, []byte(rewritten), 0o644))
require.NoError(t, os.Chtimes(indexPath, before.ModTime(), before.ModTime()))
after, err := os.Stat(indexPath)
require.NoError(t, err)
require.Equal(t, before.Size(), after.Size(), "index size must stay unchanged")
require.Equal(t, before.ModTime(), after.ModTime(), "index mtime must stay unchanged")
forced, err := provider.Parse(context.Background(), ParseRequest{
Source: source,
Fingerprint: fingerprint,
ForceParse: true,
})
require.NoError(t, err)
require.Len(t, forced.Results, 1)
assert.Equal(t, "Bravo title", forced.Results[0].Result.Session.SessionName)
}
func TestCodexProviderAdvertisesIncrementalAppend(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e2"
writeCodexProviderSession(t, root, uuid, "hello")
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
assert.Equal(t,
CapabilitySupported,
provider.Capabilities().Source.IncrementalAppend,
)
}
func TestCodexProviderFactoryScopesCursorCache(t *testing.T) {
def, ok := AgentByType(AgentCodex)
require.True(t, ok)
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e3"
path := writeCodexProviderSession(t, root, uuid, "seed shared cache")
sharedFactory := newCodexProviderFactory(def)
seedingProvider, ok := sharedFactory.NewProvider(ProviderConfig{
Roots: []string{root},
}).(*codexProvider)
require.True(t, ok)
siblingProvider, ok := sharedFactory.NewProvider(ProviderConfig{
Roots: []string{root},
}).(*codexProvider)
require.True(t, ok)
isolatedFactory := newCodexProviderFactory(def)
isolatedProvider, ok := isolatedFactory.NewProvider(ProviderConfig{
Roots: []string{root},
}).(*codexProvider)
require.True(t, ok)
sess, _, err := seedingProvider.parseSession(path, "local", false)
require.NoError(t, err)
require.NotNil(t, sess)
info, err := os.Stat(path)
require.NoError(t, err)
inode, device := sourceFileIdentity(info)
_, siblingHit := siblingProvider.cursorCache.Get(
path, info.Size(), inode, device,
)
_, isolatedHit := isolatedProvider.cursorCache.Get(
path, info.Size(), inode, device,
)
assert.True(t, siblingHit)
assert.False(t, isolatedHit)
}
func TestCodexProviderFullParseSnapshotExcludesLaterGrowth(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229ec"
initial := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/home/user/code/api", "codex_cli_rs", tsEarly,
),
testjsonl.CodexTurnContextJSON("gpt-5.4", tsEarlyS1),
testjsonl.CodexMsgJSON("user", "captured request", "2024-01-01T10:00:02Z"),
codexEventMsgJSON("task_complete", "2024-01-01T10:00:03Z"),
)
path := writeCodexProviderSessionContent(t, root, uuid, initial)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
snapshot, err := os.Open(path)
require.NoError(t, err)
defer snapshot.Close()
capturedInfo, err := snapshot.Stat()
require.NoError(t, err)
tail := testjsonl.JoinJSONL(
testjsonl.CodexTurnContextJSON("gpt-5.5", "2024-01-01T10:00:04Z"),
testjsonl.CodexMsgJSON("assistant", "later answer", "2024-01-01T10:00:05Z"),
codexEventMsgJSON("task_started", "2024-01-01T10:00:06Z"),
)
appendCodexProviderContent(t, path, tail)
sess, messages, err := concrete.parseSessionSnapshot(
path, "local", false, snapshot, capturedInfo,
)
require.NoError(t, err)
require.NotNil(t, sess)
assert.Equal(t, int64(len(initial)), sess.File.Size)
require.Len(t, messages, 1)
assert.Equal(t, RoleUser, messages[0].Role)
assert.Equal(t, "captured request", messages[0].Content)
inode, device := sourceFileIdentity(capturedInfo)
seed, seeded := concrete.cursorCache.Get(
path, int64(len(initial)), inode, device,
)
require.True(t, seeded)
assert.Equal(t, "gpt-5.4", seed.model)
assert.Equal(t, "task_complete", seed.lastTaskEvent)
currentFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: currentFingerprint,
SessionID: "codex:" + uuid,
Offset: int64(len(initial)),
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
require.Len(t, outcome.Messages, 1)
assert.Equal(t, RoleAssistant, outcome.Messages[0].Role)
assert.Equal(t, "later answer", outcome.Messages[0].Content)
assert.Equal(t, 1, outcome.Messages[0].Ordinal)
assert.Equal(t, "gpt-5.5", outcome.Messages[0].Model)
assert.Equal(t, int64(len(tail)), outcome.ConsumedBytes)
}
func TestCodexProviderFullParseSnapshotKeepsDescriptorIdentityAfterReplacement(
t *testing.T,
) {
if runtime.GOOS == "windows" {
t.Skip("Windows cannot atomically replace an open file")
}
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229f4"
original := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/workspace/original", "codex_cli_rs", tsEarly,
),
testjsonl.CodexMsgJSON("user", "old snapshot", tsEarlyS1),
)
replacement := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/workspace/replaced", "codex_cli_rs", tsEarly,
),
testjsonl.CodexMsgJSON("user", "new pathname", tsEarlyS1),
)
path := writeCodexProviderSessionContent(t, root, uuid, original)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
snapshot, err := os.Open(path)
require.NoError(t, err)
defer snapshot.Close()
snapshotInfo, err := snapshot.Stat()
require.NoError(t, err)
oldInode, oldDevice := sourceFileIdentity(snapshotInfo)
replacementPath := path + ".replacement"
require.NoError(t, os.WriteFile(replacementPath, []byte(replacement), 0o644))
require.NoError(t, os.Rename(replacementPath, path))
currentInfo, err := os.Stat(path)
require.NoError(t, err)
newInode, newDevice := sourceFileIdentity(currentInfo)
sess, messages, err := concrete.parseSessionSnapshot(
path, "local", false, snapshot, snapshotInfo,
)
require.NoError(t, err)
require.NotNil(t, sess)
require.Len(t, messages, 1)
assert.Equal(t, "old snapshot", messages[0].Content)
assert.Equal(t, "/workspace/original", sess.Cwd)
assert.Equal(t, int64(oldInode), sess.File.Inode)
assert.Equal(t, int64(oldDevice), sess.File.Device)
_, oldCursor := concrete.cursorCache.Get(
path, snapshotInfo.Size(), oldInode, oldDevice,
)
assert.True(t, oldCursor, "snapshot cursor must use the descriptor identity")
if oldInode != newInode || oldDevice != newDevice {
_, replacementCursor := concrete.cursorCache.Get(
path, snapshotInfo.Size(), newInode, newDevice,
)
assert.False(t, replacementCursor,
"old snapshot state must not be keyed to the replacement identity")
}
}
func TestCodexProviderIncrementalSnapshotExcludesLaterGrowth(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229f1"
prefix := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/workspace/project-a", "codex_cli_rs", tsEarly,
),
testjsonl.CodexTurnContextJSON("gpt-5.4", tsEarlyS1),
testjsonl.CodexMsgJSON("user", "persisted request", "2024-01-01T10:00:02Z"),
)
path := writeCodexProviderSessionContent(t, root, uuid, prefix)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
prefixFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
_, err = provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: prefixFingerprint,
})
require.NoError(t, err)
capturedTail := testjsonl.JoinJSONL(
testjsonl.CodexMsgJSON("assistant", "captured answer", "2024-01-01T10:00:03Z"),
)
appendCodexProviderContent(t, path, capturedTail)
capturedFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
laterTail := testjsonl.JoinJSONL(
testjsonl.CodexMsgJSON("assistant", "later answer", "2024-01-01T10:00:04Z"),
)
appendCodexProviderContent(t, path, laterTail)
first, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: capturedFingerprint,
SessionID: "codex:" + uuid,
Offset: int64(len(prefix)),
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
require.Len(t, first.Messages, 1)
assert.Equal(t, "captured answer", first.Messages[0].Content)
assert.Equal(t, 1, first.Messages[0].Ordinal)
assert.Equal(t, int64(len(capturedTail)), first.ConsumedBytes)
_, capturedStaged := concrete.cursorCache.Get(
path,
capturedFingerprint.Size,
capturedFingerprint.Inode,
capturedFingerprint.Device,
)
_, laterStaged := concrete.cursorCache.Get(
path,
capturedFingerprint.Size+int64(len(laterTail)),
capturedFingerprint.Inode,
capturedFingerprint.Device,
)
assert.True(t, capturedStaged)
assert.False(t, laterStaged)
currentFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
second, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: currentFingerprint,
SessionID: "codex:" + uuid,
Offset: capturedFingerprint.Size,
StartOrdinal: 2,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
require.Len(t, second.Messages, 1)
assert.Equal(t, "later answer", second.Messages[0].Content)
assert.Equal(t, 2, second.Messages[0].Ordinal)
assert.Equal(t, int64(len(laterTail)), second.ConsumedBytes)
}
func TestCodexProviderIncrementalSnapshotKeepsCapturedEOFConservative(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229f2"
prefix := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/workspace/project-a", "codex_cli_rs", tsEarly,
),
testjsonl.CodexMsgJSON("user", "persisted request", tsEarlyS1),
)
unterminated := testjsonl.CodexMsgJSON(
"assistant", "captured without newline", tsLate,
)
path := writeCodexProviderSessionContent(
t, root, uuid, prefix+unterminated,
)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
capturedFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
later := testjsonl.CodexMsgJSON(
"assistant", "later answer", tsLateS5,
) + "\n"
appendCodexProviderContent(t, path, "\n"+later)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: capturedFingerprint,
SessionID: "codex:" + uuid,
Offset: int64(len(prefix)),
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalNeedsFullParse, status)
assert.True(t, outcome.ForceReplace)
assert.Empty(t, outcome.Messages)
_, oldStaged := concrete.cursorCache.Get(
path,
int64(len(prefix)),
capturedFingerprint.Inode,
capturedFingerprint.Device,
)
_, newStaged := concrete.cursorCache.Get(
path,
capturedFingerprint.Size,
capturedFingerprint.Inode,
capturedFingerprint.Device,
)
assert.False(t, oldStaged)
assert.False(t, newStaged)
}
func TestCodexProviderIncrementalRejectsFingerprintIdentityMismatch(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229f3"
prefix := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/workspace/project-a", "codex_cli_rs", tsEarly,
),
testjsonl.CodexMsgJSON("user", "persisted request", tsEarlyS1),
)
tail := testjsonl.JoinJSONL(
testjsonl.CodexMsgJSON("assistant", "new answer", tsLate),
)
writeCodexProviderSessionContent(t, root, uuid, prefix+tail)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
fingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
fingerprint.Inode++
if fingerprint.Inode == 0 {
fingerprint.Inode = 1
}
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: fingerprint,
SessionID: "codex:" + uuid,
Offset: int64(len(prefix)),
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalNeedsFullParse, status)
assert.True(t, outcome.ForceReplace)
assert.Empty(t, outcome.Messages)
}
func TestCodexProviderIncrementalFirstGenuinePromptNeedsFullParse(t *testing.T) {
const childID = "019c9c96-6ee7-77c0-ba4c-380f844289d5"
orphanNotification := "<subagent_notification>\n" +
`{"agent_id":"` + childID +
`","status":{"completed":"Orphan task finished"}}` + "\n" +
"</subagent_notification>"
tail := testjsonl.JoinJSONL(
testjsonl.CodexMsgJSON(
"user", "first genuine request", tsLate,
),
)
for _, mode := range []string{"warm", "cold"} {
t.Run(mode, func(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229f4"
prefix := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/workspace/project-a", "codex_cli_rs", tsEarly,
),
testjsonl.CodexMsgJSON(
"user", orphanNotification, tsEarlyS1,
),
)
content := prefix
if mode == "cold" {
content += tail
}
path := writeCodexProviderSessionContent(
t, root, uuid, content,
)
provider, ok := NewProvider(
AgentCodex, ProviderConfig{Roots: []string{root}},
)
require.True(t, ok)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
if mode == "warm" {
prefixFingerprint, err := provider.Fingerprint(
context.Background(), source,
)
require.NoError(t, err)
full, err := provider.Parse(
context.Background(),
ParseRequest{
Source: source, Fingerprint: prefixFingerprint,
},
)
require.NoError(t, err)
require.Len(t, full.Results, 1)
assert.Empty(t, full.Results[0].Result.Session.FirstMessage)
require.Len(t, full.Results[0].Result.Messages, 1)
assert.Equal(
t, "Orphan task finished",
full.Results[0].Result.Messages[0].Content,
)
appendCodexProviderContent(t, path, tail)
}
fingerprint, err := provider.Fingerprint(
context.Background(), source,
)
require.NoError(t, err)
inode, device := fingerprint.Inode, fingerprint.Device
prefixOffset := int64(len(prefix))
_, oldBefore := concrete.cursorCache.Get(
path, prefixOffset, inode, device,
)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: fingerprint,
SessionID: "codex:" + uuid,
Offset: prefixOffset,
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalNeedsFullParse, status)
assert.True(t, outcome.ForceReplace)
assert.Empty(t, outcome.Messages)
_, oldAfter := concrete.cursorCache.Get(
path, prefixOffset, inode, device,
)
_, newAfter := concrete.cursorCache.Get(
path, fingerprint.Size, inode, device,
)
assert.Equal(t, oldBefore, oldAfter)
assert.False(t, newAfter)
if mode == "cold" {
assert.False(t, oldAfter)
}
full, err := provider.Parse(
context.Background(),
ParseRequest{Source: source, Fingerprint: fingerprint},
)
require.NoError(t, err)
require.Len(t, full.Results, 1)
assert.Equal(
t, "first genuine request",
full.Results[0].Result.Session.FirstMessage,
)
require.Len(t, full.Results[0].Result.Messages, 2)
})
}
}
func TestCodexProviderColdIncrementalStagesRetryCursorVersions(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229ed"
prefix := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/home/user/code/api", "codex_cli_rs", tsEarly,
),
testjsonl.CodexTurnContextJSON("gpt-5.4", tsEarlyS1),
testjsonl.CodexMsgJSON("user", "persisted request", "2024-01-01T10:00:02Z"),
codexEventMsgJSON("task_complete", "2024-01-01T10:00:03Z"),
)
tail := testjsonl.JoinJSONL(
testjsonl.CodexTurnContextJSON("gpt-5.5", "2024-01-01T10:00:04Z"),
testjsonl.CodexMsgJSON("assistant", "proposed answer", "2024-01-01T10:00:05Z"),
codexEventMsgJSON("task_started", "2024-01-01T10:00:06Z"),
)
path := writeCodexProviderSessionContent(t, root, uuid, prefix+tail)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
fingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
prefixOffset := int64(len(prefix))
_, oldBefore := concrete.cursorCache.Get(
path, prefixOffset, fingerprint.Inode, fingerprint.Device,
)
_, newBefore := concrete.cursorCache.Get(
path, fingerprint.Size, fingerprint.Inode, fingerprint.Device,
)
assert.False(t, oldBefore)
assert.False(t, newBefore)
req := IncrementalRequest{
Source: source,
Fingerprint: fingerprint,
SessionID: "codex:" + uuid,
Offset: prefixOffset,
StartOrdinal: 1,
}
first, status, err := provider.ParseIncremental(context.Background(), req)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
require.Len(t, first.Messages, 1)
assert.Equal(t, RoleAssistant, first.Messages[0].Role)
assert.Equal(t, "proposed answer", first.Messages[0].Content)
assert.Equal(t, 1, first.Messages[0].Ordinal)
assert.Equal(t, "gpt-5.5", first.Messages[0].Model)
oldCursor, oldStaged := concrete.cursorCache.Get(
path, prefixOffset, fingerprint.Inode, fingerprint.Device,
)
newCursor, newStaged := concrete.cursorCache.Get(
path, fingerprint.Size, fingerprint.Inode, fingerprint.Device,
)
require.True(t, oldStaged, "persisted offset must be warm for a DB retry")
require.True(t, newStaged, "proposed offset must keep its separate cursor")
assert.Equal(t, "gpt-5.4", oldCursor.model)
assert.Equal(t, "task_complete", oldCursor.lastTaskEvent)
assert.Equal(t, "gpt-5.5", newCursor.model)
assert.Equal(t, "task_started", newCursor.lastTaskEvent)
// Simulate a failed DB write: the persisted offset remains unchanged and
// the same tail is retried from the old cursor version.
retry, retryStatus, err := provider.ParseIncremental(
context.Background(), req,
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, retryStatus)
assert.Equal(t, first, retry)
}
func TestCodexProviderParseIncrementalSuccess(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e4"
initial := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/home/user/code/api", "codex_cli_rs", tsEarly,
),
testjsonl.CodexTurnContextJSON("gpt-5.4", tsEarlyS1),
testjsonl.CodexMsgJSON("user", "initial request", "2024-01-01T10:00:02Z"),
codexEventMsgJSON("task_complete", "2024-01-01T10:00:03Z"),
)
path := writeCodexProviderSessionContent(t, root, uuid, initial)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
initialFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
full, err := provider.Parse(context.Background(), ParseRequest{
Source: source,
Fingerprint: initialFingerprint,
})
require.NoError(t, err)
require.Len(t, full.Results, 1)
require.Len(t, full.Results[0].Result.Messages, 1)
tail := testjsonl.JoinJSONL(
testjsonl.CodexTurnContextJSON("gpt-5.5", "2024-01-01T10:00:04Z"),
testjsonl.CodexMsgJSON("user", "follow-up request", "2024-01-01T10:00:05Z"),
codexEventMsgJSON("task_started", "2024-01-01T10:00:06Z"),
testjsonl.CodexMsgJSON("assistant", "tail answer", "2024-01-01T10:00:07Z"),
testjsonl.CodexTokenCountJSON("2024-01-01T10:00:08Z", 100_000, 250, 64_000),
codexEventMsgJSON("task_complete", "2024-01-01T10:00:09Z"),
)
appendCodexProviderContent(t, path, tail)
currentFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: currentFingerprint,
SessionID: "codex:" + uuid,
Offset: initialFingerprint.Size,
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
assert.Equal(t, "codex:"+uuid, outcome.SessionID)
require.Len(t, outcome.Messages, 2)
assert.Equal(t, RoleUser, outcome.Messages[0].Role)
assert.Equal(t, "follow-up request", outcome.Messages[0].Content)
assert.Equal(t, 1, outcome.Messages[0].Ordinal)
assert.Equal(t, "gpt-5.5", outcome.Messages[0].Model)
assert.Equal(t, RoleAssistant, outcome.Messages[1].Role)
assert.Equal(t, "tail answer", outcome.Messages[1].Content)
assert.Equal(t, 2, outcome.Messages[1].Ordinal)
assert.Equal(t, "gpt-5.5", outcome.Messages[1].Model)
assert.Equal(t, time.Date(2024, time.January, 1, 10, 0, 9, 0, time.UTC), outcome.EndedAt)
assert.Equal(t, int64(len(tail)), outcome.ConsumedBytes)
assert.Equal(t, 2, outcome.MessageCount)
assert.Equal(t, 1, outcome.UserMessageCount)
assert.Equal(t, 250, outcome.TotalOutputTokens)
assert.Equal(t, 100_000, outcome.PeakContextTokens)
assert.True(t, outcome.HasTotalOutputTokens)
assert.True(t, outcome.HasPeakContextTokens)
require.NotNil(t, outcome.TerminationStatus)
assert.Equal(t, TerminationAwaitingUser, *outcome.TerminationStatus)
assert.False(t, outcome.ForceReplace)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
oldSeed, oldOK := concrete.cursorCache.Get(
path,
initialFingerprint.Size,
initialFingerprint.Inode,
initialFingerprint.Device,
)
newSeed, newOK := concrete.cursorCache.Get(
path,
initialFingerprint.Size+int64(len(tail)),
currentFingerprint.Inode,
currentFingerprint.Device,
)
require.True(t, oldOK)
require.True(t, newOK)
assert.Equal(t, "task_complete", oldSeed.lastTaskEvent)
assert.Equal(t, "task_complete", newSeed.lastTaskEvent)
}
func TestCodexProviderTokenCountCursorParity(t *testing.T) {
cases := []struct {
name string
uuid string
tailToken string
wantHasTokens bool
wantOutput int
wantPeakContext int
}{
{
name: "duplicate usage stays suppressed",
uuid: "019eb791-cf7d-75c1-8439-9ed74c1229ee",
tailToken: testjsonl.CodexTokenCountJSON("2024-01-01T10:00:06Z", 100_000, 250, 64_000),
},
{
name: "changed usage attaches",
uuid: "019eb791-cf7d-75c1-8439-9ed74c1229ef",
tailToken: testjsonl.CodexTokenCountJSON("2024-01-01T10:00:06Z", 110_000, 300, 70_000),
wantHasTokens: true,
wantOutput: 300,
wantPeakContext: 110_000,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
prefix := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
tc.uuid, "/workspace/project-a", "codex_cli_rs", tsEarly,
),
testjsonl.CodexTurnContextJSON("gpt-5.4", tsEarlyS1),
testjsonl.CodexMsgJSON("user", "measure usage", "2024-01-01T10:00:02Z"),
testjsonl.CodexMsgJSON("assistant", "prefix answer", "2024-01-01T10:00:03Z"),
testjsonl.CodexTokenCountJSON("2024-01-01T10:00:04Z", 100_000, 250, 64_000),
)
tail := testjsonl.JoinJSONL(
testjsonl.CodexMsgJSON("assistant", "tail answer", "2024-01-01T10:00:05Z"),
tc.tailToken,
)
fullRoot := t.TempDir()
writeCodexProviderSessionContent(
t, fullRoot, tc.uuid, prefix+tail,
)
fullProvider, ok := NewProvider(
AgentCodex, ProviderConfig{Roots: []string{fullRoot}},
)
require.True(t, ok)
fullSource := requireCodexProviderSource(t, fullProvider, tc.uuid)
fullFingerprint, err := fullProvider.Fingerprint(
context.Background(), fullSource,
)
require.NoError(t, err)
full, err := fullProvider.Parse(context.Background(), ParseRequest{
Source: fullSource, Fingerprint: fullFingerprint,
})
require.NoError(t, err)
require.Len(t, full.Results, 1)
require.Len(t, full.Results[0].Result.Messages, 3)
fullTail := full.Results[0].Result.Messages[2]
assert.Equal(t, RoleAssistant, fullTail.Role)
assert.Equal(t, "tail answer", fullTail.Content)
assert.Equal(t, tc.wantHasTokens, fullTail.HasOutputTokens)
assert.Equal(t, tc.wantOutput, fullTail.OutputTokens)
assert.Equal(t, tc.wantPeakContext, fullTail.ContextTokens)
for _, mode := range []string{"warm", "cold"} {
t.Run(mode, func(t *testing.T) {
root := t.TempDir()
content := prefix
if mode == "cold" {
content += tail
}
path := writeCodexProviderSessionContent(
t, root, tc.uuid, content,
)
provider, ok := NewProvider(
AgentCodex, ProviderConfig{Roots: []string{root}},
)
require.True(t, ok)
source := requireCodexProviderSource(t, provider, tc.uuid)
if mode == "warm" {
prefixFingerprint, err := provider.Fingerprint(
context.Background(), source,
)
require.NoError(t, err)
_, err = provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: prefixFingerprint,
})
require.NoError(t, err)
appendCodexProviderContent(t, path, tail)
}
fingerprint, err := provider.Fingerprint(
context.Background(), source,
)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: fingerprint,
SessionID: "codex:" + tc.uuid,
Offset: int64(len(prefix)),
StartOrdinal: 2,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
require.Len(t, outcome.Messages, 1)
message := outcome.Messages[0]
assert.Equal(t, RoleAssistant, message.Role)
assert.Equal(t, "tail answer", message.Content)
assert.Equal(t, 2, message.Ordinal)
assert.Equal(t, tc.wantHasTokens, message.HasOutputTokens)
assert.Equal(t, tc.wantOutput, message.OutputTokens)
assert.Equal(t, tc.wantPeakContext, message.ContextTokens)
assert.Equal(t, tc.wantOutput, outcome.TotalOutputTokens)
assert.Equal(t, tc.wantPeakContext, outcome.PeakContextTokens)
assert.Equal(t, tc.wantHasTokens, outcome.HasTotalOutputTokens)
assert.Equal(t, tc.wantHasTokens, outcome.HasPeakContextTokens)
assert.Equal(t, int64(len(tail)), outcome.ConsumedBytes)
})
}
})
}
}
func TestCodexProviderParseIncrementalNoLifecycleMarker(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e5"
path := writeCodexProviderSession(t, root, uuid, "initial request")
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
initialFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
_, err = provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: initialFingerprint,
})
require.NoError(t, err)
tail := testjsonl.JoinJSONL(
testjsonl.CodexMsgJSON("assistant", "tail answer", tsLate),
)
appendCodexProviderContent(t, path, tail)
currentFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: currentFingerprint,
SessionID: "codex:" + uuid,
Offset: initialFingerprint.Size,
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
require.Len(t, outcome.Messages, 1)
assert.Equal(t, "tail answer", outcome.Messages[0].Content)
assert.Nil(t, outcome.TerminationStatus)
}
func TestCodexProviderParseIncrementalStagesCursorWithoutMessages(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229eb"
path := writeCodexProviderSession(t, root, uuid, "initial request")
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
initialFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
_, err = provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: initialFingerprint,
})
require.NoError(t, err)
tail := testjsonl.JoinJSONL(
testjsonl.CodexTurnContextJSON("gpt-5.6", "2024-01-01T10:00:04Z"),
codexEventMsgJSON("task_started", "2024-01-01T10:00:05Z"),
)
appendCodexProviderContent(t, path, tail)
currentFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: currentFingerprint,
SessionID: "codex:" + uuid,
Offset: initialFingerprint.Size,
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
assert.Empty(t, outcome.Messages)
assert.Equal(t, 0, outcome.MessageCount)
assert.Equal(t, 0, outcome.UserMessageCount)
assert.Equal(t, int64(len(tail)), outcome.ConsumedBytes)
assert.Equal(t, time.Date(2024, time.January, 1, 10, 0, 5, 0, time.UTC), outcome.EndedAt)
require.NotNil(t, outcome.TerminationStatus)
assert.Equal(t, TerminationToolCallPending, *outcome.TerminationStatus)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
seed, staged := concrete.cursorCache.Get(
path,
currentFingerprint.Size,
currentFingerprint.Inode,
currentFingerprint.Device,
)
require.True(t, staged)
assert.Equal(t, "gpt-5.6", seed.model)
assert.Equal(t, "task_started", seed.lastTaskEvent)
}
func TestCodexProviderParseIncrementalNoDataAndTruncation(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e6"
writeCodexProviderSession(t, root, uuid, "initial request")
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
fingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
_, err = provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: fingerprint,
})
require.NoError(t, err)
noData, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: fingerprint,
SessionID: "codex:" + uuid,
Offset: fingerprint.Size,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalNoNewData, status)
assert.Equal(t, IncrementalOutcome{}, noData)
truncated, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: fingerprint,
SessionID: "codex:" + uuid,
Offset: fingerprint.Size + 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalNeedsFullParse, status)
assert.True(t, truncated.ForceReplace)
assert.Empty(t, truncated.Messages)
}
func TestCodexProviderParseIncrementalUnsafePartialOffset(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e7"
messageLine := testjsonl.CodexMsgJSON(
"user", "record completed after the first parse", tsEarlyS1,
)
cut := len(messageLine) / 2
partial := testjsonl.CodexSessionMetaJSON(
uuid, "/home/user/code/api", "codex_cli_rs", tsEarly,
) + "\n" + messageLine[:cut]
path := writeCodexProviderSessionContent(t, root, uuid, partial)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
partialFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
initial, err := provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: partialFingerprint,
})
require.NoError(t, err)
require.Len(t, initial.Results, 1)
assert.Empty(t, initial.Results[0].Result.Messages)
assert.Equal(t, int64(len(partial)), initial.Results[0].Result.Session.File.Size)
appendCodexProviderContent(t, path, messageLine[cut:]+"\n")
completeFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: completeFingerprint,
SessionID: "codex:" + uuid,
Offset: partialFingerprint.Size,
StartOrdinal: 0,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalNeedsFullParse, status)
assert.True(t, outcome.ForceReplace)
assert.Empty(t, outcome.Messages)
full, err := provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: completeFingerprint,
})
require.NoError(t, err)
require.Len(t, full.Results, 1)
require.Len(t, full.Results[0].Result.Messages, 1)
assert.Equal(t,
"record completed after the first parse",
full.Results[0].Result.Messages[0].Content,
)
}
func TestCodexProviderParseIncrementalStagesCompleteRecordsOnly(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e8"
path := writeCodexProviderSession(t, root, uuid, "initial request")
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
initialFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
_, err = provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: initialFingerprint,
})
require.NoError(t, err)
completeRecord := testjsonl.CodexMsgJSON(
"assistant", "complete tail record", tsLate,
) + "\n"
deferredRecord := testjsonl.CodexMsgJSON(
"user", "partial record completed later", tsLateS5,
)
deferredCut := len(deferredRecord) / 2
appendCodexProviderContent(
t, path, completeRecord+deferredRecord[:deferredCut],
)
unterminatedFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: unterminatedFingerprint,
SessionID: "codex:" + uuid,
Offset: initialFingerprint.Size,
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
require.Len(t, outcome.Messages, 1)
assert.Equal(t, "complete tail record", outcome.Messages[0].Content)
assert.Equal(t, 1, outcome.Messages[0].Ordinal)
assert.Equal(t, int64(len(completeRecord)), outcome.ConsumedBytes)
stagedOffset := initialFingerprint.Size + int64(len(completeRecord))
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
_, stagedOK := concrete.cursorCache.Get(
path,
stagedOffset,
unterminatedFingerprint.Inode,
unterminatedFingerprint.Device,
)
_, eofOK := concrete.cursorCache.Get(
path,
unterminatedFingerprint.Size,
unterminatedFingerprint.Inode,
unterminatedFingerprint.Device,
)
assert.True(t, stagedOK)
assert.False(t, eofOK)
appendCodexProviderContent(t, path, deferredRecord[deferredCut:]+"\n")
completeFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
completed, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: completeFingerprint,
SessionID: "codex:" + uuid,
Offset: stagedOffset,
StartOrdinal: 2,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalApplied, status)
require.Len(t, completed.Messages, 1)
assert.Equal(t, RoleUser, completed.Messages[0].Role)
assert.Equal(t, "partial record completed later", completed.Messages[0].Content)
assert.Equal(t, 2, completed.Messages[0].Ordinal)
assert.Equal(t, int64(len(deferredRecord)+1), completed.ConsumedBytes)
}
func TestCodexProviderParseIncrementalValidEOFNeedsFullParse(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229ea"
path := writeCodexProviderSession(t, root, uuid, "initial request")
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
initialFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
_, err = provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: initialFingerprint,
})
require.NoError(t, err)
unterminatedRecord := testjsonl.CodexMsgJSON(
"assistant", "valid record without newline", tsLate,
)
appendCodexProviderContent(t, path, unterminatedRecord)
currentFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: currentFingerprint,
SessionID: "codex:" + uuid,
Offset: initialFingerprint.Size,
StartOrdinal: 1,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalNeedsFullParse, status)
assert.True(t, outcome.ForceReplace)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
_, staged := concrete.cursorCache.Get(
path,
currentFingerprint.Size,
currentFingerprint.Inode,
currentFingerprint.Device,
)
assert.False(t, staged)
full, err := provider.Parse(context.Background(), ParseRequest{
Source: source, Fingerprint: currentFingerprint,
})
require.NoError(t, err)
require.Len(t, full.Results, 1)
require.Len(t, full.Results[0].Result.Messages, 2)
assert.Equal(t,
"valid record without newline",
full.Results[0].Result.Messages[1].Content,
)
}
func TestCodexProviderParseIncrementalFallbackDoesNotStage(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e9"
initial := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/home/user/code/api", "codex_cli_rs", tsEarly,
),
testjsonl.CodexMsgJSON("user", "initial request", tsEarlyS1),
testjsonl.CodexMsgJSON("assistant", "initial answer", tsLate),
)
path := writeCodexProviderSessionContent(t, root, uuid, initial)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
initialFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
tail := testjsonl.JoinJSONL(
testjsonl.CodexTokenCountJSON(tsLateS5, 100_000, 250, 64_000),
)
appendCodexProviderContent(t, path, tail)
currentFingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
outcome, status, err := provider.ParseIncremental(
context.Background(),
IncrementalRequest{
Source: source,
Fingerprint: currentFingerprint,
SessionID: "codex:" + uuid,
Offset: initialFingerprint.Size,
StartOrdinal: 2,
},
)
require.NoError(t, err)
assert.Equal(t, IncrementalNeedsFullParse, status)
assert.True(t, outcome.ForceReplace)
_, oldStaged := concrete.cursorCache.Get(
path,
initialFingerprint.Size,
currentFingerprint.Inode,
currentFingerprint.Device,
)
_, newStaged := concrete.cursorCache.Get(
path,
currentFingerprint.Size,
currentFingerprint.Inode,
currentFingerprint.Device,
)
assert.False(t, oldStaged)
assert.False(t, newStaged)
}
func TestCodexProviderColdSeedErrorDoesNotStage(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229f0"
prefix := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
uuid, "/workspace/project-a", "codex_cli_rs", tsEarly,
),
testjsonl.CodexMsgJSON("user", "persisted request", tsEarlyS1),
)
tail := testjsonl.JoinJSONL(
testjsonl.CodexMsgJSON("assistant", "new answer", tsLate),
)
path := writeCodexProviderSessionContent(t, root, uuid, prefix+tail)
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
concrete, ok := provider.(*codexProvider)
require.True(t, ok)
source := requireCodexProviderSource(t, provider, uuid)
fingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
wantErr := errors.New("cold prefix failed")
tailReadCalled := false
result, err := concrete.parseSessionFromWithReaders(
path,
int64(len(prefix)),
1,
false,
func(string, int64, func(string)) (int64, error) {
tailReadCalled = true
return int64(len(tail)), nil
},
func(string, int64) (codexIncrementalSeed, error) {
return codexIncrementalSeed{}, wantErr
},
)
require.ErrorIs(t, err, wantErr)
assert.Empty(t, result.messages)
assert.False(t, tailReadCalled)
_, oldStaged := concrete.cursorCache.Get(
path,
int64(len(prefix)),
fingerprint.Inode,
fingerprint.Device,
)
_, newStaged := concrete.cursorCache.Get(
path,
fingerprint.Size,
fingerprint.Inode,
fingerprint.Device,
)
assert.False(t, oldStaged)
assert.False(t, newStaged)
}
func TestCodexProviderParseIncrementalHonorsContext(t *testing.T) {
provider, ok := NewProvider(AgentCodex, ProviderConfig{})
require.True(t, ok)
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, status, err := provider.ParseIncremental(ctx, IncrementalRequest{})
require.ErrorIs(t, err, context.Canceled)
assert.Equal(t, IncrementalUnsupported, status)
}
func TestCodexProviderDiscoverDedupesLiveAndArchivedByUUID(t *testing.T) {
base := t.TempDir()
liveRoot := filepath.Join(base, "sessions")
archivedRoot := filepath.Join(base, "archived_sessions")
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e5"
livePath := writeCodexProviderSession(t, liveRoot, uuid, "live")
archivedPath := writeCodexProviderArchivedSession(
t, archivedRoot, uuid, "archived",
)
provider, ok := NewProvider(AgentCodex, ProviderConfig{
Roots: []string{archivedRoot, liveRoot},
})
require.True(t, ok)
discovered, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, discovered, 1)
assert.Equal(t, livePath, discovered[0].DisplayPath)
assert.NotEqual(t, archivedPath, discovered[0].DisplayPath)
}
func TestCodexProviderFindSourcePinsExactArchivedDuplicate(t *testing.T) {
base := t.TempDir()
liveRoot := filepath.Join(base, "sessions")
archivedRoot := filepath.Join(base, "archived_sessions")
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e6"
livePath := writeCodexProviderSession(t, liveRoot, uuid, "live")
archivedPath := writeCodexProviderArchivedSession(
t, archivedRoot, uuid, "archived",
)
provider, ok := NewProvider(AgentCodex, ProviderConfig{
Roots: []string{archivedRoot, liveRoot},
})
require.True(t, ok)
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: archivedPath,
FullSessionID: "codex:" + uuid,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, archivedPath, found.DisplayPath)
found, ok, err = provider.FindSource(context.Background(), FindSourceRequest{
FullSessionID: "codex:" + uuid,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, livePath, found.DisplayPath)
}
func TestCodexProviderFindSourcePreferStoredSourceKeepsArchivedDuplicate(t *testing.T) {
base := t.TempDir()
liveRoot := filepath.Join(base, "sessions")
archivedRoot := filepath.Join(base, "archived_sessions")
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e6"
livePath := writeCodexProviderSession(t, liveRoot, uuid, "live")
archivedPath := writeCodexProviderArchivedSession(
t, archivedRoot, uuid, "archived",
)
provider, ok := NewProvider(AgentCodex, ProviderConfig{
Roots: []string{archivedRoot, liveRoot},
})
require.True(t, ok)
// PreferStoredSource pins the stored archived duplicate even when a fresh
// source is required, instead of canonicalizing to the live duplicate.
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: archivedPath,
FullSessionID: "codex:" + uuid,
RequireFreshSource: true,
PreferStoredSource: true,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, archivedPath, found.DisplayPath,
"PreferStoredSource must preserve the stored archived path")
// Without the hint, RequireFreshSource canonicalizes to the live duplicate,
// which is exactly the behavior PreferStoredSource opts out of.
found, ok, err = provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: archivedPath,
FullSessionID: "codex:" + uuid,
RequireFreshSource: true,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, livePath, found.DisplayPath,
"RequireFreshSource without PreferStoredSource canonicalizes to live")
}
func TestCodexProviderFindSourceAcceptsLegacyShapedStoredPath(t *testing.T) {
root := t.TempDir()
sessionID := "test-uuid"
sourcePath := filepath.Join(
root,
"2024",
"01",
"15",
"rollout-20240115-"+sessionID+".jsonl",
)
content := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(
sessionID,
"/home/user/code/api",
"codex_cli_rs",
tsEarly,
),
testjsonl.CodexMsgJSON("user", "Add tests", tsEarlyS1),
)
require.NoError(t, os.MkdirAll(filepath.Dir(sourcePath), 0o755))
require.NoError(t, os.WriteFile(sourcePath, []byte(content), 0o644))
provider, ok := NewProvider(AgentCodex, ProviderConfig{
Roots: []string{root},
Machine: "devbox",
})
require.True(t, ok)
discovered, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, discovered, 1)
assert.Equal(t, sourcePath, discovered[0].DisplayPath)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: sourcePath, EventKind: "write"},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, sourcePath, changed[0].DisplayPath)
source, found, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: sourcePath,
FingerprintKey: sourcePath,
})
require.NoError(t, err)
require.True(t, found)
assert.Equal(t, AgentCodex, source.Provider)
assert.Equal(t, sourcePath, source.DisplayPath)
assert.Equal(t, sourcePath, source.FingerprintKey)
fingerprint, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
assert.Equal(t, sourcePath, fingerprint.Key)
assert.NotEmpty(t, fingerprint.Hash)
outcome, err := provider.Parse(context.Background(), ParseRequest{
Source: source,
Fingerprint: fingerprint,
Machine: "devbox",
})
require.NoError(t, err)
require.True(t, outcome.ResultSetComplete)
require.Len(t, outcome.Results, 1)
result := outcome.Results[0]
assert.Equal(t, "codex:"+sessionID, result.Result.Session.ID)
assert.Equal(t, "api", result.Result.Session.Project)
assert.Equal(t, "devbox", result.Result.Session.Machine)
assert.Equal(t, fingerprint.Hash, result.Result.Session.File.Hash)
assert.Len(t, result.Result.Messages, 1)
}
func TestCodexProviderChangedPathPinsArchivedDuplicate(t *testing.T) {
base := t.TempDir()
liveRoot := filepath.Join(base, "sessions")
archivedRoot := filepath.Join(base, "archived_sessions")
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e7"
_ = writeCodexProviderSession(t, liveRoot, uuid, "live")
archivedPath := writeCodexProviderArchivedSession(
t, archivedRoot, uuid, "archived",
)
provider, ok := NewProvider(AgentCodex, ProviderConfig{
Roots: []string{archivedRoot, liveRoot},
})
require.True(t, ok)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: archivedPath, EventKind: "write"},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, archivedPath, changed[0].DisplayPath)
}
func TestCodexProviderChangedPathClassifiesRemovedTranscript(t *testing.T) {
root := t.TempDir()
uuid := "019eb791-cf7d-75c1-8439-9ed74c1229e8"
sourcePath := writeCodexProviderSession(t, root, uuid, "remove")
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
require.NoError(t, os.Remove(sourcePath))
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: sourcePath, EventKind: "remove"},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, sourcePath, changed[0].DisplayPath)
}
func TestCodexProviderIndexPathClassifiesAllSiblingSources(t *testing.T) {
base := t.TempDir()
root := filepath.Join(base, "sessions")
firstUUID := "019eb791-cf7d-75c1-8439-9ed74c1229e9"
secondUUID := "019eb791-cf7d-75c1-8439-9ed74c1229ea"
firstPath := writeCodexProviderSession(t, root, firstUUID, "first")
secondPath := writeCodexProviderSession(t, root, secondUUID, "second")
indexPath := filepath.Join(base, CodexSessionIndexFilename)
require.NoError(t, os.WriteFile(indexPath, []byte(
`{"id":"`+firstUUID+`","thread_name":"Only first remains","updated_at":"2026-06-11T17:34:20Z"}`+"\n",
), 0o644))
provider, ok := NewProvider(AgentCodex, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: indexPath, EventKind: "write"},
)
require.NoError(t, err)
assert.Equal(t, []string{firstPath, secondPath}, sourceDisplayPaths(changed))
}
func writeCodexProviderSession(
t *testing.T,
root, uuid, prompt string,
) string {
t.Helper()
content := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(uuid, "/home/user/code/api", "codex_cli_rs", tsEarly),
testjsonl.CodexMsgJSON("user", prompt, tsEarlyS1),
)
return writeCodexProviderSessionContent(t, root, uuid, content)
}
func writeCodexProviderArchivedSession(
t *testing.T,
root, uuid, prompt string,
) string {
t.Helper()
content := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON(uuid, "/home/user/code/archive", "codex_cli_rs", tsEarly),
testjsonl.CodexMsgJSON("user", prompt, tsEarlyS1),
)
path := filepath.Join(root, "rollout-2026-06-11T12-44-06-"+uuid+".jsonl")
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
require.NoError(t, os.WriteFile(path, []byte(content), 0o644))
return path
}
func writeCodexProviderSessionContent(
t *testing.T,
root, uuid, content string,
) string {
t.Helper()
path := filepath.Join(
root,
"2026",
"06",
"11",
"rollout-2026-06-11T12-44-06-"+uuid+".jsonl",
)
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
require.NoError(t, os.WriteFile(path, []byte(content), 0o644))
return path
}
func requireCodexProviderSource(
t *testing.T,
provider Provider,
uuid string,
) SourceRef {
t.Helper()
source, found, err := provider.FindSource(
context.Background(),
FindSourceRequest{FullSessionID: "codex:" + uuid},
)
require.NoError(t, err)
require.True(t, found)
return source
}
func appendCodexProviderContent(t *testing.T, path, content string) {
t.Helper()
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o644)
require.NoError(t, err)
_, err = f.WriteString(content)
require.NoError(t, err)
require.NoError(t, f.Close())
}