Skip to content
Merged
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
9 changes: 9 additions & 0 deletions changelog.d/omp-moved-session-store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### Fixed
- An omp pane whose sessions live outside `~/.omp/agent` shows its chat: the bridge finds the store
the pane's own omp process writes to, from its `--session-dir` or profile and the `HOME`,
`PI_CODING_AGENT_SESSION_DIR`, `PI_CODING_AGENT_DIR`, `PI_CONFIG_DIR` or XDG data and state
directories it was started with, never from the bridge's own environment. A session herdr names
outside that store, a link out of it, another folder's session in a session directory of its own,
a session version omp does not write, an omp whose environment cannot be read and two omp
processes using different stores show no chat rather than a guess. Values omp reads from its own
`.env` files are not seen, and Windows keeps reading `~/.omp` and its profiles only.
2 changes: 1 addition & 1 deletion docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Every agent herdr runs shows up with its live status, terminal and alerts. The c
| --- | --- | --- |
| **Claude Code** | Native transcript, resolved through herdr | ✓ approvals, questions, plan and menu picks, and the `/model` list (picked for the session) |
| **Codex** | Native rollout, with tool results and commentary/final phases | ✓ approvals and questions, including queued ones, and the `/model` lists (picked for the session) |
| **omp** | Native session file | ✓ approvals, questions, and the model picker of `/model` and `/switch` (picked for the session: the chat sends `/model` as `/switch`, since omp's own `/model` opens its role hub) |
| **omp** | Native session file, in the store the pane's omp process writes to: `~/.omp/agent`, a profile's, or where its `--session-dir`, or the `PI_CODING_AGENT_SESSION_DIR`, `PI_CODING_AGENT_DIR`, `PI_CONFIG_DIR` or XDG data and state directories it was started with, put it (absolute paths only, not values from omp's `.env` files; on Windows only `~/.omp` and its profiles) | ✓ approvals, questions, and the model picker of `/model` and `/switch` (picked for the session: the chat sends `/model` as `/switch`, since omp's own `/model` opens its role hub) |
| **omo** | Native session file, found through the pane's process tree | ✓ the `/model` list (picked for the session); other menus — use Terminal |
| **gjc** | Native session file reported by the web UI's GJC extension, verified against the pane's process | — use Terminal |
| **pi** | Native session file, resolved through herdr; after `/tree`, the branch in play | ✓ its dialogs: a question, a confirmation, an answer typed in |
Expand Down
229 changes: 229 additions & 0 deletions scripts/omp-store-layout-probe.ts

Large diffs are not rendered by default.

43 changes: 26 additions & 17 deletions server/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
* at ~/.claude/projects/<project>/<session>.jsonl (claude-store.ts finds it).
* - omp: herdr's agent.get hands us the session jsonl path outright under
* ~/.omp/agent/sessions/<cwd-slug>/, or ~/.omp/profiles/<name>/agent/sessions/<cwd-slug>/
* for `omp --profile <name>` — same shape of truth, one less hop. When herdr names none, the
* pane's omp process does (omp.ts).
* for `omp --profile <name>` — same shape of truth, one less hop. The path is held to the store
* the pane's omp process writes to, which its argv and environment may move (omp.ts
* ompStoreLayout); when herdr names none of it, the pane's omp process does.
* - omo: herdr knows nothing about its store and its label for the pane flips
* between `pi` and `claude` as omo spawns model CLIs, so the pane's process
* tree routes it and process/session evidence selects a unique transcript
Expand Down Expand Up @@ -46,7 +47,7 @@ import { AmbiguousClaudeStore, attachedJob, claudeJobSession, claudeProcessSessi
import { forgetGjcPane, forgetGjcState, gjcPidUnderShell, gjcTranscriptForPane, isGjcProcess, storeRelative } from "./gjc-runtime.ts";
import { isOmoProcess, omoProcessIdentity, omoSessionForPane, processEnviron } from "./omo.ts";
import { processStartedAt } from "./process-start.ts";
import { ompHeldTranscript } from "./omp.ts";
import { ompPaneRoute, ompRouteOwns, ompRouteStore, ompRouteTranscript } from "./omp.ts";
import { forgetOpencodeRead, forgetOpencodeState, OPENCODE_IMAGE_REF, opencodeConversation, opencodeDatabasePath, opencodeImage, opencodeReadKey, opencodeSessionId, opencodeToolOutput } from "./opencode.ts";
import { piTranscriptInStore, piTranscriptPath, unwrittenSession } from "./pi.ts";
import { cmdTranscriptPath } from "./cmd.ts";
Expand Down Expand Up @@ -891,27 +892,35 @@ export function ompSessionStore(value: unknown, home: string, paths: PlatformPat
}

/**
* omp's transcript: herdr hands over the absolute path, accepted only inside the user's own
* stores. When herdr names none (or another agent's session), the pane's omp process does.
* omp's transcript: herdr hands over the absolute path, accepted only inside the store the pane's
* omp processes write to (omp.ts ompPaneRoute, from their own argv and environment), as a session
* of the pane's directory; else the pane's omp process names it. Only where herdr shows no omp
* process, and on Windows, which shows no process's environment, is herdr's path held to the
* user's own default and profile stores instead.
*/
async function ompTranscriptPath(paneId: string, cwd: string): Promise<string> {
const info = await herdrRpc<{ agent: { agent_session?: { kind?: unknown; value?: unknown } } }>("agent.get", { target: paneId });
const session = info.agent.agent_session;
const path = session?.kind === "path" && typeof session.value === "string" ? session.value : null;
// a Windows bridge starts with HOME set to the profile directory (remote-entry.ts)
const store = ompSessionStore(path, process.env["HOME"] ?? "");
if (path === null || store === null) {
const held = await ompHeldTranscript(paneId, cwd);
if (held === null) throw new ConversationUnavailable("no_session_path");
return held;
const home = process.env["HOME"] ?? "";
const route = process.platform === "win32" ? null : await ompPaneRoute(paneId);
// an omp whose store nothing proves, or two keeping different ones
if (route === "unproved") throw new ConversationUnavailable("no_session_path");
const store = route === null ? ompSessionStore(path, home) : path === null ? null : ompRouteStore(route, path);
if (path !== null && store !== null) {
// omp names its session file at start and writes it with the first answer
const unwritten = unwrittenSession(path, store);
if (unwritten !== null) throw new ConversationNotStarted(unwritten.id, "omp-transcript", unwritten.path);
// the check above is lexical: a link out of the store is no transcript of this pane
const inStore = piTranscriptInStore(path, store);
if (inStore !== null && (route === null || ompRouteOwns(route, inStore, cwd))) return inStore;
if (route === null) throw new ConversationUnavailable("no_session_path");
}
// omp names its session file at start and writes it with the first answer
const unwritten = unwrittenSession(path, store);
if (unwritten !== null) throw new ConversationNotStarted(unwritten.id, "omp-transcript", unwritten.path);
// the check above is lexical: a link out of the store is no transcript of this pane
const inStore = piTranscriptInStore(path, store);
if (inStore === null) throw new ConversationUnavailable("no_session_path");
return inStore;
// herdr names no session of this store (or another agent's): the pane's omp process does
const held = route === null ? null : ompRouteTranscript(route, cwd);
if (held === null) throw new ConversationUnavailable("no_session_path");
return held;
}

/** Where a pane's conversation is: a transcript file, or a session in OpenCode's database. */
Expand Down
Loading
Loading