Skip to content

Commit cefd6ca

Browse files
feat(parser): migrate openhands provider
OpenHands stores each conversation as a directory with metadata and event files, so the provider needs a directory source facade rather than a JSONL file wrapper. This keeps the legacy discovery and dashed/undashed ID lookup behavior while making the composite snapshot fingerprint explicit at the provider boundary. The provider uses the existing OpenHands parser and snapshot helpers so freshness, shallow watch planning, changed-path classification, and normalized parse output stay aligned with the legacy sync path. test(parser): opt openhands into provider shadow OpenHands now has a concrete facade provider on this branch, so its migration mode should enter shadow comparison instead of remaining legacy-only and additive. Earlier provider opt-ins stay inherited and later provider branches own their modes. Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check test(sync): compare openhands shadow parity OpenHands is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseOpenHandsSession. The test uses the directory snapshot source shape so the provider fingerprint path and planned data-version behavior stay visible while the branch migrates away from legacy dispatch. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesOpenHandsLegacyParser|TestOpenHandsProvider|TestParseOpenHands|TestDiscoverAndFindOpenHands|TestClassifyOnePath_OpenHands|TestProcessFileOpenHandsUsesSnapshotMtimeForRetryCache' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/... refactor(parser): fold openhands into provider Move OpenHands discovery, source lookup, and parse ownership onto the concrete provider and delete the package-level DiscoverOpenHandsSessions, FindOpenHandsSourceFile, and ParseOpenHandsSession free functions. Discovery now walks conversation roots directly in the provider source set, raw-session-ID lookup folds the literal/dash-stripped/normalized matching into sessionDirForID, and parsing runs on a provider receiver method. The provider-neutral snapshot, session-dir predicate, and event parse helpers stay as shared free functions. Make OpenHands provider-authoritative and remove its legacy sync dispatch: the classifyOnePath block, the processFile case arm, the OpenHands snapshot-mtime branch, and processOpenHands are gone. Sync now classifies and processes OpenHands through provider changed-path handling, which preserves the base_state.json/TASKS.json/events companion remap to the session directory and keeps the snapshot mtime driving the skip-retry cache via the provider fingerprint. Drop the OpenHands AgentDef DiscoverFunc/FindSourceFunc hooks, remove the shadow baseline test, exempt the provider file from the shim scan, and add a guard asserting the legacy entrypoints stay deleted.
1 parent a73e4d1 commit cefd6ca

11 files changed

Lines changed: 703 additions & 188 deletions

internal/parser/openhands.go

Lines changed: 3 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"fmt"
88
"os"
99
"path/filepath"
10-
"sort"
1110
"strings"
1211
"time"
1312

@@ -20,82 +19,6 @@ const (
2019
openHandsObservationEvent = "ObservationEvent"
2120
)
2221

23-
// DiscoverOpenHandsSessions finds OpenHands CLI conversation
24-
// directories under ~/.openhands/conversations.
25-
func DiscoverOpenHandsSessions(
26-
conversationsDir string,
27-
) []DiscoveredFile {
28-
entries, err := os.ReadDir(conversationsDir)
29-
if err != nil {
30-
return nil
31-
}
32-
33-
var files []DiscoveredFile
34-
for _, entry := range entries {
35-
if !entry.IsDir() || !IsValidSessionID(entry.Name()) {
36-
continue
37-
}
38-
sessionDir := filepath.Join(
39-
conversationsDir, entry.Name(),
40-
)
41-
if !isOpenHandsSessionDir(sessionDir) {
42-
continue
43-
}
44-
files = append(files, DiscoveredFile{
45-
Path: sessionDir,
46-
Agent: AgentOpenHands,
47-
})
48-
}
49-
50-
sort.Slice(files, func(i, j int) bool {
51-
return files[i].Path < files[j].Path
52-
})
53-
return files
54-
}
55-
56-
// FindOpenHandsSourceFile locates an OpenHands conversation
57-
// directory by its raw session ID.
58-
func FindOpenHandsSourceFile(
59-
conversationsDir, rawID string,
60-
) string {
61-
if conversationsDir == "" || !IsValidSessionID(rawID) {
62-
return ""
63-
}
64-
65-
candidates := []string{rawID}
66-
stripped := strings.ReplaceAll(rawID, "-", "")
67-
if stripped != rawID {
68-
candidates = append(candidates, stripped)
69-
}
70-
71-
for _, cand := range candidates {
72-
sessionDir := filepath.Join(conversationsDir, cand)
73-
if isOpenHandsSessionDir(sessionDir) {
74-
return sessionDir
75-
}
76-
}
77-
78-
entries, err := os.ReadDir(conversationsDir)
79-
if err != nil {
80-
return ""
81-
}
82-
for _, entry := range entries {
83-
if !entry.IsDir() {
84-
continue
85-
}
86-
sessionDir := filepath.Join(
87-
conversationsDir, entry.Name(),
88-
)
89-
if !isOpenHandsSessionDir(sessionDir) {
90-
continue
91-
}
92-
if normalizeOpenHandsSessionID(entry.Name()) == normalizeOpenHandsSessionID(rawID) {
93-
return sessionDir
94-
}
95-
}
96-
return ""
97-
}
98-
9922
// OpenHandsSnapshot computes synthetic file metadata for an
10023
// OpenHands conversation directory by hashing the relevant
10124
// metadata of base_state.json, TASKS.json, and events/*.json.
@@ -184,9 +107,9 @@ func OpenHandsSnapshot(path string) (FileInfo, error) {
184107
}, nil
185108
}
186109

187-
// ParseOpenHandsSession parses a single OpenHands CLI
188-
// conversation directory into a session and messages.
189-
func ParseOpenHandsSession(
110+
// parseSession parses a single OpenHands CLI conversation
111+
// directory into a session and messages.
112+
func (p *openHandsProvider) parseSession(
190113
path, machine string,
191114
) (*ParsedSession, []ParsedMessage, error) {
192115
sessionDir, err := normalizeOpenHandsSessionPath(path)

0 commit comments

Comments
 (0)