Skip to content

feat: continue external Codex / Claude / Cursor sessions in their own CLI - #560

Open
Harry19081 wants to merge 8 commits into
developfrom
feat/external-session-cli-resume
Open

feat: continue external Codex / Claude / Cursor sessions in their own CLI#560
Harry19081 wants to merge 8 commits into
developfrom
feat/external-session-cli-resume

Conversation

@Harry19081

@Harry19081 Harry19081 commented Jul 28, 2026

Copy link
Copy Markdown
Member

What

Imported external sessions can now be continued in the CLI that owns them — the cc-switch-style resume flow, integrated with ORGII's existing CLI TUI support instead of an external terminal.

  • Chat panel: imported sessions get a Continue in <CLI> header action (next to Fork). It opens a chat-panel terminal tab running the provider's resume command in the session's recorded workspace, backed by a managed code_sessions row (runner='tui') so ORGII_SESSION_ID lifecycle attribution and the managed-mirror dedup work exactly like GUI-launched CLI sessions.
  • orgtrack CLI: orgtrack resume <session-id> (and --print) execs the same plan from a terminal.
  • Core: orgtrack_core::sources::cli_resume is the single mapping both hosts share; external_history_cli_resume_plan exposes it to the frontend with host-side freshness checks (binary installed, cwd exists, source store on disk → disabled states explain why).

Support matrix (each verified against the installed binary and its own store)

Source Command Notes
claude_code claude --resume <uuid> cwd required — Claude keys sessions on the project path; resuming from another dir fails with No conversation found (tested)
codex_app codex resume <thread-uuid> uuid extracted from the rollout-<ts>-<uuid> stem; parent-flags-before-subcommand ordering verified
cursor_cli cursor-agent --resume <chat-id> global lookup
opencode opencode --session <ses_id> ids match session.id in its central db exactly
mimo_code mimo --session <ses_id> OpenCode fork — identical flag surface per mimo --help
cline cline --id <id> ids match sessions.session_id in its global db exactly
omp omp --session <transcript-path> pi-family id lookup is cwd-scoped, so the plan addresses the session file — works from anywhere

Not resumable, and why: cursor_ide (composer ids share no space with cursor-agent's chat store — 0/11 overlap on a real machine), windsurf / warp / trae / qoder (app-bound stores, no CLI surface), zcode / qoder_cli / workbuddy (CLIs likely resume, but no binary was present to verify the flag shape — the module doc marks them as the extension point).

Testing

  • cargo test -p orgtrack_core cli_resume — 11 unit tests (id-shape gating per provider, codex stem extraction, omp path addressing, quoting, cache lookup incl. subagent/unsupported rejection)
  • cargo test -p orgtrack_cli (29), clippy clean for touched code, app lib compiles
  • pnpm typecheck, eslint clean; cliTerminalSession vitest 5/5; full ChatPanel suite 793/793
  • Live: orgtrack resume --print produced correct command lines for real sessions of all seven providers present on this machine; id spaces cross-checked against opencode's and cline's own SQLite stores; i18n keys in all 13 locales

Imported-session chat pane behavior (round 3)

  • Non-continuable sources are now pure read-only replays: the chat composer is hidden for cursor_ide / windsurf / warp / trae / qoder / zcode / qoder_cli / workbuddy (capability declared per source descriptor — same source of truth gates the header button and skips its backend round-trip).
  • Honest composer wording: for continuable sources the placeholder says what typing does — "Message to continue in a new ORGII session — or resume in {agent} from the header…" — localized in all 13 locales.
  • Right model preselected: the fork setup dialog now prefers the imported session's own model over the fallback agent's configured model when no agent was explicitly chosen (pure helper + unit tests; collab forks with agent hints keep the old order). Account preselection already keyed on the source model.

@Harry19081

Copy link
Copy Markdown
Member Author

Comparison: ORGII continue-in-CLI vs cc-switch session manager

(From reading cc-switch's session_manager source directly.)

Resume coverage. cc-switch emits resume commands for 5 of its 7 providers: codex resume, claude --resume, opencode -s, gemini --resume, grok --resume (openclaw and hermes are None — gateway/cloud-managed). ORGII now covers 7 of 15 imported sources: the same codex/claude/opencode trio plus cursor-agent, mimo, cline, and omp, which cc-switch has no readers for. gemini/grok resume would come for free here once those importers land (they're already 'known format, not yet imported' in the Data Sources panel). Neither tool can resume IDE-bound stores (cursor_ide/windsurf/trae/qoder) — nothing exposes a CLI entry point for them.

Correctness depth. cc-switch templates cd <dir> && <cmd> uniformly and treats resume failures as a copy-fallback. ORGII encodes per-provider semantics learned empirically: Claude's resume is cwd-required (verified: wrong dir → 'No conversation found'), codex's thread-uuid is extracted from the rollout stem, and omp addresses the session file by path because pi-family bare-id lookup is scoped to the current project's session dir — a case cc-switch's uniform template would get wrong. Id spaces were cross-checked against opencode's and cline's own SQLite stores. Availability is pre-checked (binary installed, cwd exists, source store on disk) with per-reason disabled states instead of failing at launch.

Integration model. cc-switch launches an external terminal (Terminal/iTerm/Ghostty/kitty/WezTerm/Kaku/Alacritty/Warp/custom, AppleScript/open-based, macOS-only) and the session leaves its world. ORGII resumes into the in-app chat-panel terminal backed by a managed code_sessions row — so the continued session keeps live status, ORGII_SESSION_ID hook attribution, launch-profile flags (permission modes, command overrides), usage tracking, and managed-mirror dedup (the re-imported transcript won't double-list). Cross-platform by construction (PTY, no AppleScript).

What cc-switch has that we deliberately don't (candidate follow-ups):

  • an explicit copy-resume-command button (our tooltip shows the command and orgtrack resume --print emits it, but there's no one-click copy in the header)
  • external-terminal choice with remembered preference — skipped since the in-app CLI terminal is the vehicle here
  • session deletion from the manager UI — out of scope for this PR

What we have that cc-switch doesn't: managed-session integration (status/dedup/usage), per-provider availability gating, launch-profile awareness, a scriptable orgtrack resume (exec or --print), 4 extra providers, and i18n across 13 locales.

New orgtrack_core::sources::cli_resume maps an imported session (claude_code /
codex_app / cursor_cli) to the invocation its owning CLI accepts:
claude --resume <uuid> (original cwd required — Claude keys session storage
on the project path), codex resume <thread-uuid> (extracted from the rollout
stem), cursor-agent --resume <chat-id>. Cursor IDE composers are deliberately
unsupported: their ids share no space with cursor-agent's chat store.

The desktop app exposes it as external_history_cli_resume_plan, folding in
the host-only freshness checks (workspace dir still exists, source store
still on disk).
Imported Claude Code / Codex / Cursor CLI sessions get a 'Continue in
<CLI>' header action next to the fork button. It reuses the existing
chat-panel TUI launch flow end to end: launch-profile command resolution,
a managed code_sessions row (runner='tui') for ORGII_SESSION_ID lifecycle
attribution and managed-mirror dedup, and the session's recorded workspace
as cwd. Disabled states explain exactly why (CLI not installed, original
folder gone for Claude's cwd-scoped resume, source store deleted).

Sources with no CLI resume path (Cursor IDE composers and the rest) simply
don't render the button — the backend plan is the single source of truth.
orgtrack resume <session-id> scans only the provider that owns the id,
plans the invocation via core's cli_resume, and execs it from the
session's recorded workspace (--print emits the cd-prefixed command line
instead). Verified against real local history: claude/codex/cursor-agent
command lines all resolve, and Claude's cwd requirement is enforced —
resuming from another directory provably fails upstream.
Verified against the installed binaries and their own stores:
- opencode --session <ses_id> — central db, ids match session.id exactly
- mimo --session <ses_id> — OpenCode fork, identical flag surface
- cline --id <id> — global sessions.db, ids match sessions.session_id
- omp --session <transcript-path> — pi-family id lookup is scoped to the
  current project's session dir, so the plan addresses the session file by
  absolute path instead (works from anywhere)

Still out, with reasons in the module doc: cursor_ide/windsurf/warp/trae/
qoder (app-bound, no CLI surface) and zcode/qoder_cli/workbuddy (no binary
present to verify the flag shape).
…oser copy, source-model preselect

Three imported-session UX fixes around continuation:

- Sources no CLI can reopen (Cursor IDE, Windsurf, Trae, Warp, Qoder,
  ZCode, WorkBuddy, Qoder CLI) no longer render the chat composer — they
  are pure read-only replays. The capability lives on the source
  descriptor (cliResume {agentType, displayName}) so the composer gate and
  the header continue-button share one sync source of truth, and the
  header skips the backend plan round-trip for never-resumable sources.

- The composer placeholder now says what typing actually does — fork into
  a new ORGII session — and names the native alternative per source
  ('…or resume in Claude Code from the header'), in all 13 locales.

- The fork setup dialog preselects the imported session's own model: when
  no agent was explicitly chosen (user pick or collab agent hint), the
  source session's model outranks the fallback agent's configured model
  whenever the selected account offers it. Extracted as a pure helper with
  unit tests; collab forks with agent hints keep their existing order.
@Neonforge98
Neonforge98 force-pushed the feat/external-session-cli-resume branch from f6b45ab to 9880eb7 Compare July 30, 2026 08:37
- Quote resumed/launched CLI commands for the chat-panel PTY's actual
  default shell: PowerShell on Windows, POSIX elsewhere. POSIX '\''
  escaping was corrupting backslash-heavy Windows paths (e.g. omp's
  --session <path>) since PowerShell is the Windows PTY default, not a
  POSIX shell. appendCliCommandArgs/formatCliTuiCommand take an
  injectable platform flag (defaults to the real OS) so both quoting
  styles are covered by tests regardless of host OS.
- handleOpenCliTerminal now looks up an already-open terminal tab bound
  to the same agent/resolved-command/cwd before spawning a new one,
  since the backend mints a fresh managed session on every call and a
  repeat "Continue in <CLI>" click would otherwise launch a second CLI
  process against the same resumed conversation. Lookup extracted to a
  pure, unit-tested helper.
- Extracted the duplicated deriveExpectedProcess (SessionContinueCliHeaderExtras,
  useChatPanelLaunch) into cliTerminalSession.ts and fixed it to unwrap
  a leading quoted binary instead of naively splitting on whitespace.
- Simplified ChatViewPostHistoryOverlays' composer placeholder: the
  fork-imported fallback branch was unreachable (the composer's parent
  render gate already requires a CLI-resume descriptor).
shell_quote (used by CliResumePlan::display_command and orgtrack
resume's cwd `cd` prefix) only escaped for POSIX shells. On Windows the
printed command is copy-pasted into PowerShell, where POSIX '\''-style
escaping mangles backslash-heavy paths (e.g. omp's --session <path>).
Both functions now pick PowerShell- or POSIX-style quoting from the
host OS, with a _for_shell variant that takes the target platform
explicitly so tests can cover both without #[cfg(windows)]-gating the
test itself.

Also fixes a stale doc comment on resume_source_for_session_id: it
dispatches all seven CLI-resumable imported sources, not three.
The TUI launch path wrote the CLI command terminated with LF, which
Windows PowerShell + PSReadLine leaves sitting unexecuted at a
continuation prompt - the resume terminal opened with the command typed
but never run. Terminals send CR for Enter, and POSIX shells accept CR
as line-accept too (ICRNL/readline), so terminate with CR on every
platform. TerminalService.ts already follows this convention.

Real-machine verified on Windows 11: Continue in Claude Code now spawns
claude.exe with the resume args on a single click, and a second click
focuses the existing terminal tab without spawning a second process.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants