diff --git a/internal/parser/gptme.go b/internal/parser/gptme.go index 166df2d94..2900fa4cb 100644 --- a/internal/parser/gptme.go +++ b/internal/parser/gptme.go @@ -5,62 +5,16 @@ import ( "fmt" "os" "path/filepath" - "sort" "strings" "time" "github.com/tidwall/gjson" ) -// DiscoverGptmeSessions finds gptme session files under the -// given logs directory. Each session is a subdirectory containing -// a conversation.jsonl file (e.g. ~/.local/share/gptme/logs/). -func DiscoverGptmeSessions(logsDir string) []DiscoveredFile { - if logsDir == "" { - return nil - } - entries, err := os.ReadDir(logsDir) - if err != nil { - return nil - } - var files []DiscoveredFile - for _, entry := range entries { - if !isDirOrSymlink(entry, logsDir) { - continue - } - convPath := filepath.Join(logsDir, entry.Name(), "conversation.jsonl") - if _, err := os.Stat(convPath); err != nil { - continue - } - files = append(files, DiscoveredFile{ - Path: convPath, - Agent: AgentGptme, - }) - } - sort.Slice(files, func(i, j int) bool { - return files[i].Path < files[j].Path - }) - return files -} - -// FindGptmeSourceFile locates a gptme session by its raw session ID -// (the directory name, without the "gptme:" prefix). -func FindGptmeSourceFile(logsDir, rawID string) string { - if logsDir == "" || rawID == "" { - return "" - } - candidate := filepath.Join(logsDir, rawID, "conversation.jsonl") - if info, err := os.Stat(candidate); err == nil && !info.IsDir() { - return candidate - } - return "" -} - -// ParseGptmeSession parses a gptme conversation.jsonl file. -// gptme stores one message per line with role/content/timestamp -// fields. Assistant messages carry an optional metadata object -// with model and usage sub-fields. -func ParseGptmeSession( +// parseSession parses a gptme conversation.jsonl file. gptme stores one +// message per line with role/content/timestamp fields. Assistant messages +// carry an optional metadata object with model and usage sub-fields. +func (p *gptmeProvider) parseSession( path, machine string, ) (*ParsedSession, []ParsedMessage, error) { info, err := os.Stat(path) diff --git a/internal/parser/gptme_provider.go b/internal/parser/gptme_provider.go new file mode 100644 index 000000000..a5711ca51 --- /dev/null +++ b/internal/parser/gptme_provider.go @@ -0,0 +1,323 @@ +package parser + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" +) + +type gptmeProviderFactory struct { + def AgentDef +} + +func newGptmeProviderFactory(def AgentDef) ProviderFactory { + return gptmeProviderFactory{def: cloneAgentDef(def)} +} + +func (f gptmeProviderFactory) Definition() AgentDef { + return cloneAgentDef(f.def) +} + +func (f gptmeProviderFactory) Capabilities() Capabilities { + return gptmeProviderCapabilities() +} + +func (f gptmeProviderFactory) NewProvider(cfg ProviderConfig) Provider { + cfg = cfg.Clone() + return &gptmeProvider{ + ProviderBase: ProviderBase{ + Def: cloneAgentDef(f.def), + Caps: gptmeProviderCapabilities(), + Config: cfg, + }, + sources: newGptmeSourceSet(cfg.Roots), + } +} + +type gptmeProvider struct { + ProviderBase + sources JSONLSourceSet +} + +func (p *gptmeProvider) Discover(ctx context.Context) ([]SourceRef, error) { + sources, err := p.sources.Discover(ctx) + if err != nil { + return nil, err + } + return p.filterSources(sources), nil +} + +func (p *gptmeProvider) WatchPlan(ctx context.Context) (WatchPlan, error) { + return p.sources.WatchPlan(ctx) +} + +func (p *gptmeProvider) SourcesForChangedPath( + ctx context.Context, + req ChangedPathRequest, +) ([]SourceRef, error) { + sources, err := p.sources.SourcesForChangedPath(ctx, req) + if err != nil { + return nil, err + } + filtered := p.filterSources(sources) + if len(filtered) > 0 { + return filtered, nil + } + source, ok := p.sourceForEventPath(req) + if !ok { + return nil, nil + } + return []SourceRef{source}, nil +} + +func (p *gptmeProvider) FindSource( + ctx context.Context, + req FindSourceRequest, +) (SourceRef, bool, error) { + if err := ctx.Err(); err != nil { + return SourceRef{}, false, err + } + for _, path := range []string{ + req.StoredFilePath, + req.FingerprintKey, + } { + if path == "" { + continue + } + if source, ok, err := p.sourceForExistingPath(ctx, path); err != nil { + return SourceRef{}, false, err + } else if ok { + return source, true, nil + } + } + for _, id := range []string{ + req.RawSessionID, + p.rawSessionIDFromFull(req.FullSessionID), + } { + if id == "" { + continue + } + if source, ok, err := p.sourceForSessionID(ctx, id); err != nil { + return SourceRef{}, false, err + } else if ok { + return source, true, nil + } + } + return SourceRef{}, false, nil +} + +func (p *gptmeProvider) sourceForExistingPath( + ctx context.Context, + path string, +) (SourceRef, bool, error) { + source, ok, err := p.sources.sourceForPath(ctx, path) + if err != nil { + return SourceRef{}, false, err + } + if ok && p.isSource(source) { + return source, true, nil + } + return SourceRef{}, false, nil +} + +func (p *gptmeProvider) sourceForSessionID( + ctx context.Context, + id string, +) (SourceRef, bool, error) { + for _, root := range p.Config.Roots { + path := filepath.Join(root, id, "conversation.jsonl") + if source, ok, err := p.sourceForExistingPath(ctx, path); err != nil { + return SourceRef{}, false, err + } else if ok { + return source, true, nil + } + } + return SourceRef{}, false, nil +} + +func (p *gptmeProvider) rawSessionIDFromFull(id string) string { + if id == "" { + return "" + } + _, rawID := StripHostPrefix(id) + if !strings.HasPrefix(rawID, p.Def.IDPrefix) { + return "" + } + return strings.TrimPrefix(rawID, p.Def.IDPrefix) +} + +func (p *gptmeProvider) sourceForEventPath(req ChangedPathRequest) (SourceRef, bool) { + if req.Path == "" { + return SourceRef{}, false + } + if req.WatchRoot != "" { + root := filepath.Clean(req.WatchRoot) + if !p.hasRoot(root) { + return SourceRef{}, false + } + return gptmeSourceRef(root, filepath.Clean(req.Path)) + } + for _, root := range p.Config.Roots { + if source, ok := gptmeSourceRef(root, filepath.Clean(req.Path)); ok { + return source, true + } + } + return SourceRef{}, false +} + +func (p *gptmeProvider) hasRoot(root string) bool { + for _, configured := range p.Config.Roots { + if samePath(configured, root) { + return true + } + } + return false +} + +func (p *gptmeProvider) Fingerprint( + ctx context.Context, + source SourceRef, +) (SourceFingerprint, error) { + return p.sources.Fingerprint(ctx, source) +} + +func (p *gptmeProvider) Parse( + ctx context.Context, + req ParseRequest, +) (ParseOutcome, error) { + if err := ctx.Err(); err != nil { + return ParseOutcome{}, err + } + path, ok, err := p.sources.pathFromSource(ctx, req.Source) + if err != nil { + return ParseOutcome{}, err + } + if !ok { + return ParseOutcome{}, fmt.Errorf("gptme source path unavailable") + } + machine := firstNonEmptyJSONLString(req.Machine, p.Config.Machine) + sess, msgs, err := p.parseSession(path, machine) + if err != nil { + return ParseOutcome{}, err + } + if sess == nil { + return ParseOutcome{ + ResultSetComplete: true, + SkipReason: SkipNoSession, + }, nil + } + if req.Fingerprint.Hash != "" { + sess.File.Hash = req.Fingerprint.Hash + } + return ParseOutcome{ + Results: []ParseResultOutcome{{ + Result: ParseResult{ + Session: *sess, + Messages: msgs, + }, + DataVersion: DataVersionCurrent, + }}, + ResultSetComplete: true, + }, nil +} + +func (p *gptmeProvider) filterSources(sources []SourceRef) []SourceRef { + if len(sources) == 0 { + return nil + } + filtered := sources[:0] + for _, source := range sources { + if p.isSource(source) { + filtered = append(filtered, source) + } + } + return filtered +} + +func (p *gptmeProvider) isSource(source SourceRef) bool { + src, ok := source.Opaque.(JSONLSource) + if !ok { + return false + } + return isGptmeConversationPath(src.Root, src.Path) +} + +func newGptmeSourceSet(roots []string) JSONLSourceSet { + return newJSONLSourceSet(AgentGptme, roots, + withRecursive(), + withContentHashing(), + withSymlinkFollowing(), + withInclude(func(path string, info os.FileInfo) bool { + return !info.IsDir() && filepath.Base(path) == "conversation.jsonl" + }), + withProjectHint(func(root, path string) string { + sessionID := gptmeSessionIDFromPath(root, path) + if sessionID == "" { + return "" + } + return gptmeProjectFromSessionName(sessionID) + }), + withSessionIDFromPath(gptmeSessionIDFromPath), + ) +} + +func gptmeProviderCapabilities() Capabilities { + return Capabilities{ + Source: SourceCapabilities{ + DiscoverSources: CapabilitySupported, + WatchSources: CapabilitySupported, + ClassifyChangedPath: CapabilitySupported, + FindSource: CapabilitySupported, + CompositeFingerprint: CapabilitySupported, + MultiSessionSource: CapabilityNotApplicable, + PerSessionErrors: CapabilityNotApplicable, + ExcludedSessions: CapabilityNotApplicable, + ForceReplaceOnParse: CapabilityNotApplicable, + }, + Content: ContentCapabilities{ + FirstMessage: CapabilitySupported, + Model: CapabilitySupported, + PerMessageTokenUsage: CapabilitySupported, + }, + } +} + +func isGptmeConversationPath(root, path string) bool { + rel, err := filepath.Rel(root, path) + if err != nil { + return false + } + parts := strings.Split(rel, string(filepath.Separator)) + return len(parts) == 2 && parts[1] == "conversation.jsonl" && + parts[0] != "." && parts[0] != ".." && parts[0] != "" +} + +func gptmeSessionIDFromPath(root, path string) string { + if !isGptmeConversationPath(root, path) { + return "" + } + return filepath.Base(filepath.Dir(path)) +} + +func gptmeSourceRef(root, path string) (SourceRef, bool) { + root = filepath.Clean(root) + path = filepath.Clean(path) + if !isGptmeConversationPath(root, path) { + return SourceRef{}, false + } + sessionID := gptmeSessionIDFromPath(root, path) + return SourceRef{ + Provider: AgentGptme, + Key: path, + DisplayPath: path, + FingerprintKey: path, + ProjectHint: gptmeProjectFromSessionName(sessionID), + Opaque: JSONLSource{ + Root: root, + Path: path, + RelPath: filepath.Join(sessionID, "conversation.jsonl"), + }, + }, true +} diff --git a/internal/parser/gptme_provider_test.go b/internal/parser/gptme_provider_test.go new file mode 100644 index 000000000..327be7e23 --- /dev/null +++ b/internal/parser/gptme_provider_test.go @@ -0,0 +1,230 @@ +package parser + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGptmeProviderFactoryReplacesLegacyAdapter(t *testing.T) { + factory, ok := ProviderFactoryByType(AgentGptme) + require.True(t, ok) + require.NotNil(t, factory) + + caps := factory.Capabilities() + assert.Equal(t, CapabilitySupported, caps.Source.DiscoverSources) + assert.Equal(t, CapabilitySupported, caps.Source.WatchSources) + assert.Equal(t, CapabilitySupported, caps.Source.ClassifyChangedPath) + assert.Equal(t, CapabilitySupported, caps.Source.FindSource) + assert.Equal(t, CapabilitySupported, caps.Source.CompositeFingerprint) + assert.Equal(t, CapabilityNotApplicable, caps.Source.MultiSessionSource) + assert.Equal(t, CapabilitySupported, caps.Content.FirstMessage) + assert.Equal(t, CapabilitySupported, caps.Content.Model) + assert.Equal(t, CapabilitySupported, caps.Content.PerMessageTokenUsage) + + provider, ok := NewProvider(AgentGptme, ProviderConfig{ + Roots: []string{t.TempDir()}, + Machine: "devbox", + }) + require.True(t, ok) + require.NotNil(t, provider) +} + +func TestGptmeProviderSourceMethods(t *testing.T) { + root := t.TempDir() + sessionID := "2026-06-13-write-hello-world" + sourcePath := filepath.Join(root, sessionID, "conversation.jsonl") + writeSourceFile(t, sourcePath, gptmeProviderFixture()) + writeSourceFile(t, filepath.Join(root, "conversation.jsonl"), "{}\n") + writeSourceFile(t, filepath.Join(root, "nested", sessionID, "conversation.jsonl"), "{}\n") + writeSourceFile(t, filepath.Join(root, "other", "notes.jsonl"), "{}\n") + + provider, ok := NewProvider(AgentGptme, 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, AgentGptme, discovered[0].Provider) + assert.Equal(t, sourcePath, discovered[0].Key) + assert.Equal(t, sourcePath, discovered[0].FingerprintKey) + assert.Equal(t, "write-hello-world", discovered[0].ProjectHint) + + changed, err := provider.SourcesForChangedPath( + context.Background(), + ChangedPathRequest{Path: sourcePath, EventKind: "write", WatchRoot: root}, + ) + require.NoError(t, err) + require.Len(t, changed, 1) + assert.Equal(t, discovered[0].Key, changed[0].Key) + + found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{ + RawSessionID: sessionID, + }) + require.NoError(t, err) + require.True(t, ok) + assert.Equal(t, discovered[0].Key, found.Key) + + fingerprint, err := provider.Fingerprint(context.Background(), found) + require.NoError(t, err) + assert.Equal(t, sourcePath, fingerprint.Key) + assert.NotZero(t, fingerprint.Size) + assert.NotZero(t, fingerprint.MTimeNS) + assert.NotEmpty(t, fingerprint.Hash) +} + +func TestGptmeProviderDiscoversSymlinkSessionDirectories(t *testing.T) { + root := t.TempDir() + targetRoot := t.TempDir() + sessionID := "2026-06-13-write-hello-world" + targetDir := filepath.Join(targetRoot, sessionID) + writeSourceFile( + t, + filepath.Join(targetDir, "conversation.jsonl"), + gptmeProviderFixture(), + ) + linkDir := filepath.Join(root, sessionID) + if err := os.Symlink(targetDir, linkDir); err != nil { + t.Skipf("creating directory symlink: %v", err) + } + + provider, ok := NewProvider(AgentGptme, 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, filepath.Join(linkDir, "conversation.jsonl"), discovered[0].DisplayPath) +} + +func TestGptmeProviderClassifiesDeletedConversationPath(t *testing.T) { + root := t.TempDir() + sessionID := "2026-06-13-write-hello-world" + sourcePath := filepath.Join(root, sessionID, "conversation.jsonl") + writeSourceFile(t, sourcePath, gptmeProviderFixture()) + + provider, ok := NewProvider(AgentGptme, ProviderConfig{ + Roots: []string{root}, + Machine: "devbox", + }) + require.True(t, ok) + require.NoError(t, os.Remove(sourcePath)) + + changed, err := provider.SourcesForChangedPath( + context.Background(), + ChangedPathRequest{ + Path: sourcePath, + EventKind: "remove", + WatchRoot: root, + }, + ) + require.NoError(t, err) + require.Len(t, changed, 1) + assert.Equal(t, sourcePath, changed[0].Key) + assert.Equal(t, sourcePath, changed[0].DisplayPath) + assert.Equal(t, "write-hello-world", changed[0].ProjectHint) +} + +func TestGptmeProviderFindSourceUsesPersistedFallbacks(t *testing.T) { + root := t.TempDir() + sessionID := "2026-06-13-write-hello-world" + sourcePath := filepath.Join(root, sessionID, "conversation.jsonl") + writeSourceFile(t, sourcePath, gptmeProviderFixture()) + + provider, ok := NewProvider(AgentGptme, ProviderConfig{ + Roots: []string{root}, + Machine: "devbox", + }) + require.True(t, ok) + + for _, req := range []FindSourceRequest{ + {FingerprintKey: sourcePath}, + {FullSessionID: "gptme:" + sessionID}, + {FullSessionID: "host~gptme:" + sessionID}, + } { + found, ok, err := provider.FindSource(context.Background(), req) + require.NoError(t, err) + require.Truef(t, ok, "request %#v", req) + assert.Equal(t, sourcePath, found.DisplayPath) + } +} + +func TestGptmeProviderParse(t *testing.T) { + root := t.TempDir() + sessionID := "2026-06-13-write-hello-world" + sourcePath := filepath.Join(root, sessionID, "conversation.jsonl") + writeSourceFile(t, sourcePath, gptmeProviderFixture()) + + provider, ok := NewProvider(AgentGptme, ProviderConfig{ + Roots: []string{root}, + Machine: "devbox", + }) + require.True(t, ok) + + source, ok, err := provider.FindSource(context.Background(), FindSourceRequest{ + StoredFilePath: sourcePath, + }) + require.NoError(t, err) + require.True(t, ok) + fingerprint, err := provider.Fingerprint(context.Background(), source) + require.NoError(t, err) + + outcome, err := provider.Parse(context.Background(), ParseRequest{ + Source: source, + Fingerprint: fingerprint, + Machine: "devbox", + }) + require.NoError(t, err) + require.True(t, outcome.ResultSetComplete) + assert.False(t, outcome.ForceReplace) + assert.Empty(t, outcome.SourceErrors) + require.Len(t, outcome.Results, 1) + + result := outcome.Results[0] + assert.Equal(t, DataVersionCurrent, result.DataVersion) + assert.Empty(t, result.RetryReason) + assert.Equal(t, "gptme:"+sessionID, result.Result.Session.ID) + assert.Equal(t, "write-hello-world", result.Result.Session.Project) + assert.Equal(t, "devbox", result.Result.Session.Machine) + assert.Equal(t, fingerprint.Hash, result.Result.Session.File.Hash) + require.Len(t, result.Result.Messages, 2) + assert.Equal(t, RoleUser, result.Result.Messages[0].Role) + assert.Equal(t, RoleAssistant, result.Result.Messages[1].Role) +} + +func TestGptmeProviderParseMissingSourceIsWholeSourceError(t *testing.T) { + provider, ok := NewProvider(AgentGptme, ProviderConfig{ + Roots: []string{t.TempDir()}, + Machine: "devbox", + }) + require.True(t, ok) + + outcome, err := provider.Parse(context.Background(), ParseRequest{ + Source: SourceRef{ + Provider: AgentGptme, + Key: "/tmp/missing/conversation.jsonl", + DisplayPath: "/tmp/missing/conversation.jsonl", + FingerprintKey: "/tmp/missing/conversation.jsonl", + }, + Machine: "devbox", + }) + require.Error(t, err) + assert.Empty(t, outcome) + assert.False(t, errors.Is(err, ErrUnsupportedProviderFeature)) +} + +func gptmeProviderFixture() string { + return `{"role":"user","content":"Write hello world.","timestamp":"2026-06-13T10:00:01.000000"}` + "\n" + + `{"role":"assistant","content":"Hello from gptme.","timestamp":"2026-06-13T10:00:02.000000","metadata":{"model":"demo-model","usage":{"input_tokens":10,"output_tokens":4}}}` + "\n" +} diff --git a/internal/parser/gptme_test.go b/internal/parser/gptme_test.go index 0394a20e2..f0525bc6e 100644 --- a/internal/parser/gptme_test.go +++ b/internal/parser/gptme_test.go @@ -1,6 +1,7 @@ package parser import ( + "context" "path/filepath" "testing" "time" @@ -9,18 +10,28 @@ import ( "github.com/stretchr/testify/require" ) -func TestParseGptmeSession(t *testing.T) { - path := filepath.Join( - "testdata", "gptme", - "2026-06-13-write-hello-world", - "conversation.jsonl", - ) +func TestGptmeProviderParsesFixture(t *testing.T) { + logsDir := filepath.Join("testdata", "gptme") - sess, msgs, err := ParseGptmeSession(path, "testmachine") + provider, ok := NewProvider(AgentGptme, ProviderConfig{ + Roots: []string{logsDir}, + Machine: "testmachine", + }) + require.True(t, ok) + source, found, err := provider.FindSource(context.Background(), FindSourceRequest{ + RawSessionID: "2026-06-13-write-hello-world", + }) + require.NoError(t, err) + require.True(t, found) + outcome, err := provider.Parse(context.Background(), ParseRequest{ + Source: source, + Machine: "testmachine", + }) require.NoError(t, err) - require.NotNil(t, sess) - require.NotEmpty(t, msgs) + require.Len(t, outcome.Results, 1) + sess := outcome.Results[0].Result.Session + msgs := outcome.Results[0].Result.Messages assert.Equal(t, "gptme:2026-06-13-write-hello-world", sess.ID) assert.Equal(t, "write-hello-world", sess.Project) assert.Equal(t, "testmachine", sess.Machine) @@ -60,23 +71,35 @@ func TestParseGptmeSession(t *testing.T) { assert.Equal(t, 2, sess.UserMessageCount) } -func TestDiscoverGptmeSessions(t *testing.T) { +func TestGptmeProviderDiscoversFixture(t *testing.T) { logsDir := filepath.Join("testdata", "gptme") - files := DiscoverGptmeSessions(logsDir) - require.Len(t, files, 1) - assert.Equal(t, AgentGptme, files[0].Agent) - assert.Contains(t, files[0].Path, "conversation.jsonl") + provider, ok := NewProvider(AgentGptme, ProviderConfig{Roots: []string{logsDir}}) + require.True(t, ok) + + sources, err := provider.Discover(context.Background()) + require.NoError(t, err) + require.Len(t, sources, 1) + assert.Equal(t, AgentGptme, sources[0].Provider) + assert.Contains(t, sources[0].DisplayPath, "conversation.jsonl") } -func TestFindGptmeSourceFile(t *testing.T) { +func TestGptmeProviderFindsFixtureSource(t *testing.T) { logsDir := filepath.Join("testdata", "gptme") + provider, ok := NewProvider(AgentGptme, ProviderConfig{Roots: []string{logsDir}}) + require.True(t, ok) - found := FindGptmeSourceFile(logsDir, "2026-06-13-write-hello-world") - assert.NotEmpty(t, found) - assert.Contains(t, found, "conversation.jsonl") + found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{ + RawSessionID: "2026-06-13-write-hello-world", + }) + require.NoError(t, err) + require.True(t, ok) + assert.Contains(t, found.DisplayPath, "conversation.jsonl") - notFound := FindGptmeSourceFile(logsDir, "nonexistent-session") - assert.Empty(t, notFound) + _, ok, err = provider.FindSource(context.Background(), FindSourceRequest{ + RawSessionID: "nonexistent-session", + }) + require.NoError(t, err) + assert.False(t, ok) } func TestGptmeProjectFromSessionName(t *testing.T) { diff --git a/internal/parser/provider.go b/internal/parser/provider.go index 64745e779..7426f70ee 100644 --- a/internal/parser/provider.go +++ b/internal/parser/provider.go @@ -351,6 +351,8 @@ func providerFactoryForDef(def AgentDef) ProviderFactory { return newCommandCodeProviderFactory(def) case AgentIflow: return newIflowProviderFactory(def) + case AgentGptme: + return newGptmeProviderFactory(def) default: return legacyProviderFactory{def: def} } diff --git a/internal/parser/provider_migration.go b/internal/parser/provider_migration.go index 439ec5334..513a64d87 100644 --- a/internal/parser/provider_migration.go +++ b/internal/parser/provider_migration.go @@ -55,7 +55,7 @@ var providerMigrationModes = map[AgentType]ProviderMigrationMode{ AgentVibe: ProviderMigrationLegacyOnly, AgentZed: ProviderMigrationLegacyOnly, AgentQwenPaw: ProviderMigrationLegacyOnly, - AgentGptme: ProviderMigrationLegacyOnly, + AgentGptme: ProviderMigrationProviderAuthoritative, AgentShelley: ProviderMigrationLegacyOnly, AgentAider: ProviderMigrationLegacyOnly, AgentOMP: ProviderMigrationLegacyOnly, diff --git a/internal/parser/types.go b/internal/parser/types.go index 59e3010ad..6d382f959 100644 --- a/internal/parser/types.go +++ b/internal/parser/types.go @@ -582,15 +582,13 @@ var Registry = []AgentDef{ FindSourceFunc: FindQwenPawSourceFile, }, { - Type: AgentGptme, - DisplayName: "gptme", - EnvVar: "GPTME_DIR", - ConfigKey: "gptme_dirs", - DefaultDirs: []string{".local/share/gptme/logs"}, - IDPrefix: "gptme:", - FileBased: true, - DiscoverFunc: DiscoverGptmeSessions, - FindSourceFunc: FindGptmeSourceFile, + Type: AgentGptme, + DisplayName: "gptme", + EnvVar: "GPTME_DIR", + ConfigKey: "gptme_dirs", + DefaultDirs: []string{".local/share/gptme/logs"}, + IDPrefix: "gptme:", + FileBased: true, }, { // Shelley (exe.dev) stores all conversations in a single diff --git a/internal/sync/engine.go b/internal/sync/engine.go index 41d009f0f..732546e39 100644 --- a/internal/sync/engine.go +++ b/internal/sync/engine.go @@ -1450,23 +1450,6 @@ func (e *Engine) classifyOnePath( } } - // gptme: //conversation.jsonl - for _, gptmeDir := range e.agentDirs[parser.AgentGptme] { - if gptmeDir == "" { - continue - } - if rel, ok := isUnder(gptmeDir, path); ok { - parts := strings.Split(rel, sep) - if len(parts) != 2 || parts[1] != "conversation.jsonl" { - continue - } - return parser.DiscoveredFile{ - Path: path, - Agent: parser.AgentGptme, - }, true - } - } - if df, ok := e.classifyAiderPath(path); ok { return df, true } @@ -4643,8 +4626,6 @@ func (e *Engine) processFile( res = e.processAntigravityCLI(file, info) case parser.AgentQwenPaw: res = e.processQwenPaw(file, info) - case parser.AgentGptme: - res = e.processGptme(file, info) case parser.AgentAider: res = e.processAider(file, info) default: @@ -7000,35 +6981,6 @@ func (e *Engine) processPositron( } } -func (e *Engine) processGptme( - file parser.DiscoveredFile, info os.FileInfo, -) processResult { - if e.shouldSkipByPath(file.Path, info) { - return processResult{skip: true} - } - - sess, msgs, err := parser.ParseGptmeSession( - file.Path, e.machine, - ) - if err != nil { - return processResult{err: err} - } - if sess == nil { - return processResult{} - } - - hash, err := ComputeFileHash(file.Path) - if err == nil { - sess.File.Hash = hash - } - - return processResult{ - results: []parser.ParseResult{ - {Session: *sess, Messages: msgs}, - }, - } -} - // aiderFileUnchanged reports whether a physical aider history file is // unchanged since the last sync. Aider sessions are stored under virtual // "#" paths, so the generic shouldSkipByPath (which looks the