feat(codex): SessionMutator stub + session-file resolver

Wires the host's mirror hook end-to-end: FindCodexSessionFile walks
~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*-<thread_id>.jsonl to
prove the path for the captured thread_id, then logs the no-op.

Real rewrite is deferred to v2: codex keys tool calls by `call_<id>`
not the `toolu_<id>` Plexum's canonical block format uses, so a v1
rewriter would never find a matching id. When tool-call id round-trip
through codex's native surface lands, the rewriter plugs into this
file's path resolver.

Also rescues cmd/plexum-openai-provider-plugin/main.go from .gitignore
(the bare 'plexum-openai-provider-plugin' pattern was too greedy — it
matched the source directory). Tightened to '/plexum-openai-provider-plugin'
+ '/bin/' so only built binaries get ignored.
This commit is contained in:
h z
2026-06-01 14:01:15 +01:00
parent e1c4add9fe
commit d075fc408a
3 changed files with 253 additions and 1 deletions

View File

@@ -0,0 +1,63 @@
// session_mutate.go — codex session file path resolution. The actual
// JSONL rewrite is deferred (see main.go MutateSession comment): codex
// keys tool calls by `call_<id>`, not the `toolu_<id>` Plexum uses,
// so a v1 mirror would never match anything. We expose the path
// finder here so the host-side wiring telemetry stays useful and the
// v2 rewriter has its entry point.
//
// Codex layout (observed):
//
// ~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-<ISO>-<thread_id>.jsonl
//
// Older layout (pre-0.5x) wrote into the top-level sessions dir with
// the same filename convention. We probe both.
package runner
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
// FindCodexSessionFile resolves the rollout JSONL for the thread_id
// captured into workspace/.plexum-codex-session. Returns ("", error)
// when no session id is recorded yet OR no matching file is on disk.
func FindCodexSessionFile(workspace string) (string, error) {
threadID := loadSessionID(workspace)
if threadID == "" {
return "", errors.New("no thread_id captured yet")
}
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
root := filepath.Join(home, ".codex", "sessions")
var found string
err = filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
if err != nil {
return nil // tolerate per-entry errors; keep walking
}
if d.IsDir() {
return nil
}
name := d.Name()
if !strings.HasSuffix(name, ".jsonl") && !strings.HasSuffix(name, ".json") {
return nil
}
if strings.Contains(name, threadID) {
found = p
return filepath.SkipAll
}
return nil
})
if err != nil {
return "", err
}
if found == "" {
return "", fmt.Errorf("no rollout file for thread_id=%s under %s", threadID, root)
}
return found, nil
}