feat(coding-agent): enabled importing foreign sessions from claude and codex

- Implemented session stores and metadata converters to import Claude and Codex sessions into OMP.
- Added `--from-claude` and `--from-codex` CLI flags and `/resume` command arguments for foreign session resolution.
- Updated session selector components and controllers to support listing and picking external agent sessions.
- Added comprehensive unit tests and documentation covering foreign session import functionality.
This commit is contained in:
can1357
2026-07-30 00:28:40 +02:00
parent 47d3317d32
commit f11641d5a8
20 changed files with 1756 additions and 80 deletions
@@ -724,6 +724,12 @@ export interface SessionSelectorOptions {
loadAllSessions?: () => Promise<SessionInfo[]>;
/** Preloaded all-projects list; cached so the first Tab toggle is instant. */
allSessions?: SessionInfo[];
/** Picker heading; defaults to "Resume Session". */
title?: string;
/** Fixed scope label, or false to omit the scope suffix. */
scopeLabel?: string | false;
/** Show each session's working directory in the list. */
showCwd?: boolean;
/**
* Reads the live terminal height so the visible window fits the viewport.
* Omitted only in tests; defaults to a conservative 24 rows.
@@ -773,6 +779,8 @@ export class SessionSelectorComponent extends Container {
readonly #getTerminalRows: () => number;
readonly #fillHeight: boolean;
readonly #bottomBorder = new DynamicBorder();
readonly #title: string;
readonly #scopeLabel: string | false | undefined;
constructor(
sessions: SessionInfo[],
@@ -790,6 +798,8 @@ export class SessionSelectorComponent extends Container {
this.#globalSessions = options.allSessions ?? null;
this.#getTerminalRows = options.getTerminalRows ?? (() => 24);
this.#fillHeight = options.fillHeight ?? false;
this.#title = options.title ?? "Resume Session";
this.#scopeLabel = options.scopeLabel;
// Add header
this.addChild(new Spacer(1));
this.#headerText = new Text(this.#headerLabel(), 1, 0);
@@ -801,7 +811,12 @@ export class SessionSelectorComponent extends Container {
// Create session list in folder scope; the empty-state hint invites the
// user to Tab into all-projects rather than silently surfacing other
// projects' history (issue #3099).
this.#sessionList = new SessionList(sessions, false, options.historyMatcher, options.getTerminalRows);
this.#sessionList = new SessionList(
sessions,
options.showCwd ?? false,
options.historyMatcher,
options.getTerminalRows,
);
// Every exit path cancels the list's pending history merge, so a stale
// debounce timer can never run its SQLite lookup after the picker closed.
this.#sessionList.onSelect = session => {
@@ -831,8 +846,9 @@ export class SessionSelectorComponent extends Container {
}
#headerLabel(): string {
const scopeLabel = this.#scope === "all" ? "all projects" : "current folder";
return `${theme.bold("Resume Session")} ${theme.fg("muted", `(${scopeLabel})`)}`;
if (this.#scopeLabel === false) return theme.bold(this.#title);
const scopeLabel = this.#scopeLabel ?? (this.#scope === "all" ? "all projects" : "current folder");
return `${theme.bold(this.#title)} ${theme.fg("muted", `(${scopeLabel})`)}`;
}
/**