Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 0 additions & 78 deletions internal/parser/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -1574,81 +1574,3 @@ func extractIflowBaseSessionID(sessionID string) string {
// If we didn't find 5 hyphens, this is not a fork ID
return sessionID
}

// DiscoverVibeSessions finds all Vibe session files under the given root directory.
// Vibe stores sessions in: ~/.vibe/logs/session/session_YYYYMMDD_HHMMSS_uuid/
// Each session directory contains messages.jsonl
func DiscoverVibeSessions(root string) []DiscoveredFile {
var results []DiscoveredFile

entries, err := os.ReadDir(root)
if err != nil {
return results
}

for _, entry := range entries {
if !isDirOrSymlink(entry, root) {
continue
}

// Vibe session directories match pattern: session_YYYYMMDD_HHMMSS_uuid
// The uuid part can contain hyphens
if !strings.HasPrefix(entry.Name(), "session_") || !strings.Contains(entry.Name(), "_") {
continue
}

sessionDir := filepath.Join(root, entry.Name())
messagesPath := filepath.Join(sessionDir, "messages.jsonl")

if info, err := os.Stat(messagesPath); err == nil && !info.IsDir() {
results = append(results, DiscoveredFile{
Path: messagesPath,
Agent: AgentVibe,
Project: entry.Name(),
})
}
}

return results
}

// FindVibeSourceFile locates a specific Vibe session file by ID. The ID is the
// session_id recorded in meta.json (a uuid), which usually differs from the
// session directory name. Sessions without meta.json fall back to the directory
// name, so a direct path is tried first before scanning meta.json files.
func FindVibeSourceFile(root, sessionID string) string {
// Fast path: sessionID is the directory name (no-meta fallback).
if messagesPath := filepath.Join(root, sessionID, "messages.jsonl"); isVibeMessagesFile(messagesPath) {
return messagesPath
}

// Otherwise sessionID is a meta.json session_id; scan session
// directories and match on their recorded session_id.
entries, err := os.ReadDir(root)
if err != nil {
return ""
}
for _, entry := range entries {
if !isDirOrSymlink(entry, root) || !strings.HasPrefix(entry.Name(), "session_") {
continue
}
messagesPath := filepath.Join(root, entry.Name(), "messages.jsonl")
if !isVibeMessagesFile(messagesPath) {
continue
}
metaPath := filepath.Join(root, entry.Name(), "meta.json")
if meta, err := parseVibeMetadata(metaPath); err == nil && meta.SessionID == sessionID {
return messagesPath
}
}
return ""
}

// isVibeMessagesFile reports whether path is an existing regular file.
func isVibeMessagesFile(path string) bool {
info, err := os.Stat(path)
if err != nil || info == nil {
return false
}
return !info.IsDir()
}
4 changes: 2 additions & 2 deletions internal/parser/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1424,7 +1424,7 @@ func TestIsPiSessionFile(t *testing.T) {

func TestDiscoverVibeSessionsIntegration(t *testing.T) {
// Test discovery with testdata
files := DiscoverVibeSessions("testdata/vibe")
files := discoverVibeTestSessions(t, "testdata/vibe")

// Should find all session directories with messages.jsonl
require.NotEmpty(t, files)
Expand All @@ -1442,7 +1442,7 @@ func TestDiscoverVibeSessionsIntegration(t *testing.T) {
func TestFindVibeSourceFileIntegration(t *testing.T) {
// Test with actual testdata
sessionID := "session_basic"
result := FindVibeSourceFile("testdata/vibe", sessionID)
result := findVibeTestSourceFile(t, "testdata/vibe", sessionID)

expected := filepath.Join("testdata", "vibe", sessionID, "messages.jsonl")
assert.Equal(t, expected, result)
Expand Down
2 changes: 2 additions & 0 deletions internal/parser/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ func providerFactoryForDef(def AgentDef) ProviderFactory {
return newQwenProviderFactory(def)
case AgentQwenPaw:
return newQwenPawProviderFactory(def)
case AgentVibe:
return newVibeProviderFactory(def)
case AgentWorkBuddy:
return newWorkBuddyProviderFactory(def)
case AgentZencoder:
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/provider_migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ var providerMigrationModes = map[AgentType]ProviderMigrationMode{
AgentPositron: ProviderMigrationLegacyOnly,
AgentAntigravity: ProviderMigrationLegacyOnly,
AgentAntigravityCLI: ProviderMigrationLegacyOnly,
AgentVibe: ProviderMigrationLegacyOnly,
AgentVibe: ProviderMigrationProviderAuthoritative,
AgentZed: ProviderMigrationLegacyOnly,
AgentQwenPaw: ProviderMigrationProviderAuthoritative,
AgentGptme: ProviderMigrationProviderAuthoritative,
Expand Down
1 change: 0 additions & 1 deletion internal/parser/provider_shim_scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ var pendingShimProviderFiles = map[string]bool{
"opencode_provider.go": true,
"positron_provider.go": true,
"shelley_provider.go": true,
"vibe_provider.go": true,
"visualstudio_copilot_provider.go": true,
"vscode_copilot_provider.go": true,
"zed_provider.go": true,
Expand Down
16 changes: 7 additions & 9 deletions internal/parser/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,15 +577,13 @@ var Registry = []AgentDef{
FindSourceFunc: FindShelleySourceFile,
},
{
Type: AgentVibe,
DisplayName: "Mistral Vibe",
EnvVar: "VIBE_SESSIONS_DIR",
ConfigKey: "vibe_session_dirs",
DefaultDirs: []string{".vibe/logs/session"},
IDPrefix: "vibe:",
FileBased: true,
DiscoverFunc: DiscoverVibeSessions,
FindSourceFunc: FindVibeSourceFile,
Type: AgentVibe,
DisplayName: "Mistral Vibe",
EnvVar: "VIBE_SESSIONS_DIR",
ConfigKey: "vibe_session_dirs",
DefaultDirs: []string{".vibe/logs/session"},
IDPrefix: "vibe:",
FileBased: true,
},
{
// Aider has no central session store. It writes one Markdown
Expand Down
18 changes: 10 additions & 8 deletions internal/parser/vibe.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ type VibeStats struct {
LastTurnTotalTokens int `json:"last_turn_total_tokens"`
}

// ParseVibeSession parses a Mistral Vibe messages.jsonl file
func ParseVibeSession(path string, fileInfo FileInfo) (ParseResult, error) {
// parseVibeResult parses a Mistral Vibe messages.jsonl file into a ParseResult.
// It owns the on-disk shape (messages.jsonl plus the sibling meta.json) for the
// Vibe provider; the package-level entrypoint was folded onto the provider.
func parseVibeResultFile(path string, fileInfo FileInfo) (ParseResult, error) {
result := ParseResult{
Session: ParsedSession{
Agent: AgentVibe,
Expand Down Expand Up @@ -386,11 +388,11 @@ func vibeToolArguments(args json.RawMessage) string {
return string(args)
}

// ParseVibeSessionWrapper wraps ParseVibeSession and returns the session,
// messages, and usage events in the shape the sync engine consumes:
// (*ParsedSession, []ParsedMessage, []ParsedUsageEvent, error). It stats the
// file to build FileInfo and optionally overrides the project and machine.
func ParseVibeSessionWrapper(path, project, machine string) (*ParsedSession, []ParsedMessage, []ParsedUsageEvent, error) {
// parseSession parses a Vibe session at path and returns the session, messages,
// and usage events in the shape the provider consumes: (*ParsedSession,
// []ParsedMessage, []ParsedUsageEvent, error). It stats the file to build
// FileInfo and optionally overrides the project and machine.
func parseVibeSession(path, project, machine string) (*ParsedSession, []ParsedMessage, []ParsedUsageEvent, error) {
info, err := os.Stat(path)
if err != nil {
return nil, nil, nil, fmt.Errorf("stat %s: %w", path, err)
Expand All @@ -402,7 +404,7 @@ func ParseVibeSessionWrapper(path, project, machine string) (*ParsedSession, []P
Mtime: info.ModTime().UnixNano(),
}

result, err := ParseVibeSession(path, fileInfo)
result, err := parseVibeResultFile(path, fileInfo)
if err != nil {
return nil, nil, nil, err
}
Expand Down
Loading