Session Operations: export, dump, share, fork, resume/continue
This document describes operator-visible behavior for session export/share/fork/resume operations as currently implemented.
Implementation files
../src/modes/controllers/command-controller.ts../src/session/agent-session.ts../src/session/session-manager.ts../src/export/html/index.ts../src/export/custom-share.ts../src/main.ts
Operation matrix
| Operation | Entry path | Session mutation | Session file creation/switch | Output artifact |
|---|---|---|---|---|
/dump |
Interactive slash command | No | No | Clipboard text |
/export [path] |
Interactive slash command | No | No | HTML file |
--export <session.jsonl> [outputPath] |
CLI startup fast-path | No runtime session mutation | No active session; reads target file | HTML file |
/share |
Interactive slash command | No | No | Temp HTML + share URL/gist |
/fork |
Interactive slash command | Yes (active session identity changes) | Creates new session file and switches current session to it (persistent mode only) | Copies artifact directory to new session namespace when present |
--fork <id|path> |
CLI startup | Yes after session creation | Creates a new session fork from the selected source into current cwd/session dir | None |
/resume |
Interactive slash command | Yes (active in-memory state replaced) | Switches to selected existing session file | None |
--resume |
CLI startup picker | Yes after session creation | Opens selected existing session file | None |
--resume <id|path> |
CLI startup | Yes after session creation | Opens existing session; global cross-project match can fork into current project | None |
--continue |
CLI startup | Yes after session creation | Opens terminal breadcrumb or most-recent session; creates new one if none exists | None |
Export and dump
/export [outputPath] (interactive)
Flow:
InputControllerroutes/export...toCommandController.handleExportCommand.- The command splits on whitespace and uses only the first argument after
/exportasoutputPath. AgentSession.exportToHtml()callsexportSessionToHtml(sessionManager, state, { outputPath, themeName }).- On success, UI shows path and opens the file in browser.
Behavior details:
--copy,clipboard, andcopyarguments are explicitly rejected with a warning to use/dump.- Export embeds session header/entries/leaf plus current
systemPromptand tool descriptions from agent state. - No session entries are appended during export.
Caveat:
- Argument parsing is whitespace-based (
text.split(/\s+/)), so quoted paths with spaces are not preserved as a single path by this command path.
--export <inputSessionFile> [outputPath] (CLI)
Flow in main.ts:
- Handled early (before interactive/session startup).
- Calls
exportFromFile(inputPath, outputPath?). SessionManager.open(inputPath)loads entries, then HTML is generated and written.- Process prints
Exported to: ...and exits.
Behavior details:
- Missing input file surfaces as
File not found: <path>. - This path does not create an
AgentSessionand does not mutate any running session.
/dump (interactive clipboard export)
Flow:
CommandController.handleDumpCommand()callssession.formatSessionAsText().- If empty string, reports
No messages to dump yet. - Otherwise copies to clipboard via native
copyToClipboard.
Dump content includes:
- System prompt
- Active model/thinking level
- Tool definitions + parameters
- User/assistant messages
- Thinking blocks and tool calls
- Tool results and execution blocks (except
excludeFromContextbash/python entries) - Custom/hook/file mention/branch summary/compaction summary entries
No session persistence changes are made by dumping.
Share
/share is interactive-only and always starts by exporting current session to a temp HTML file.
Phase 1: temp export
- Temp file path:
${os.tmpdir()}/${Snowflake.next()}.html - Uses
session.exportToHtml(tmpFile) - If export fails (notably in-memory sessions), share ends with error.
Phase 2: custom share handler (if present)
loadCustomShare() checks ~/.omp/agent for first existing candidate:
share.tsshare.jsshare.mjs
Requirements:
- Module must default-export a function
(htmlPath) => Promise<CustomShareResult | string | undefined>.
If present and valid:
- UI enters
Sharing...loader state. - Handler result interpretation:
- string => treated as URL, shown and opened
- object =>
urland/ormessageshown;urlopened undefined/falsy => genericSession shared
- Temp file is removed after completion.
Critical fallback behavior:
- If custom handler exists but loading fails, command errors and returns.
- If custom handler executes and throws, command errors and returns.
- In both failure cases, it does not fall back to GitHub gist.
- Gist fallback happens only when no custom share script exists.
Phase 3: default gist fallback
Only when no custom share handler is found:
- Validates
gh auth status. - Shows
Creating gist...loader. - Runs
gh gist create --public=false <tmpFile>. - Parses gist URL, derives gist id, builds preview URL
https://gistpreview.github.io/?<id>. - Shows both preview and gist URLs; opens preview.
Cancellation/abort semantics in share:
- Loader has
onAborthook that restores editor UI and reportsShare cancelled. - The underlying
gh gist createcommand is not passed an abort signal in this code path; cancellation is UI-level and checked after command returns.
Fork
Interactive /fork creates a new session from the current one and switches the active session identity.
Preconditions and immediate guards
- If agent is streaming,
/forkis rejected with warning. - UI status/loading indicators are cleared before operation.
Session-level flow
AgentSession.fork():
- Emits
session_before_switchwithreason: "fork"(cancellable). - Flushes pending writes.
- Calls
SessionManager.fork(). - Copies artifacts directory from old session namespace to new namespace (best-effort; non-ENOENT copy failures are logged, not fatal).
- Updates
agent.sessionId. - Emits
session_switchwithreason: "fork".
SessionManager.fork() behavior:
- Requires persistent mode and existing session file.
- Creates new session id and new JSONL file path.
- Rewrites header with:
- new
id - new timestamp
cwdunchangedparentSessionset to previous session id
- new
- Keeps all non-header entries unchanged in the new file.
Non-persistent behavior
- In-memory session manager returns
undefinedfromfork(). AgentSession.fork()returnsfalse.- UI reports
Fork failed (session not persisted or cancelled).
CLI --fork <id|path>
Startup --fork is resolved before normal session creation:
--forkis rejected with--no-session.- Path-like values (
/,\, or.jsonl) callSessionManager.forkFrom(path, cwd, sessionDir). - Other values resolve via
resolveResumableSession(...): local sessions first, then global search whensessionDiris not forced. Matching accepts lowercased session id prefixes, full JSONL filename prefixes, and timestamp-stripped filename id suffixes. - The forked file is created in the current cwd/session-dir scope and becomes the active session manager for startup.
Resume and continue
Interactive /resume
Flow:
- Opens session selector populated via
SessionManager.list(currentCwd, currentSessionDir). - On selection,
SelectorController.handleResumeSession(sessionPath)callssession.switchSession(sessionPath). - UI clears/rebuilds chat and todos, then reports
Resumed session.
Notes:
- This picker only lists sessions in the current session directory scope.
- It does not use global cross-project search.
CLI --resume
--resume (no value)
main.tslists sessions for current cwd/sessionDir and opens picker.- Selected path is opened with
SessionManager.open(selectedPath)before session creation.
--resume <value>
createSessionManager() resolution order:
- If value looks like path (
/,\, or.jsonl), open directly. - Else
resolveResumableSession(...)searches:- current scope (
SessionManager.list(cwd, sessionDir)) - global sessions (
SessionManager.listAll()) only when no explicitsessionDirwas provided
- current scope (
- Matching accepts case-insensitive session id prefixes, full JSONL filename prefixes, and the id suffix after the timestamp in
<timestamp>_<sessionId>.jsonl.
Cross-project id match behavior:
- If matched session cwd differs from current cwd, CLI asks:
Session found in different project ... Fork into current directory? [y/N]
- On yes:
SessionManager.forkFrom(match.path, cwd, sessionDir)creates a new local forked file. - On no/non-TTY default: command errors.
CLI --continue
SessionManager.continueRecent(cwd, sessionDir):
- Resolves session dir for current cwd.
- Reads terminal-scoped breadcrumb first.
- Falls back to most recently modified session file.
- Opens found session; if none exists, creates new session.
This is startup-only behavior; there is no interactive /continue slash command.
How session switching actually mutates runtime state
AgentSession.switchSession(sessionPath) does the runtime transition used by resume-like operations:
- Emit
session_before_switchwithreason: "resume"andtargetSessionFile(cancellable). - Disconnect agent event subscription and abort in-flight work.
- Flush current session manager writes.
- Capture rollback state for the current session, agent messages, queued steering/follow-up/next-turn messages, model/thinking/service-tier, MCP selections, tools, and system prompt.
- Clear queued steering/follow-up/next-turn messages.
sessionManager.setSessionFile(sessionPath)and updateagent.sessionId.- Build session context from loaded entries.
- Restore MCP selections/tools/system prompt for the target session.
- Emit
session_switchwithreason: "resume". - Replace agent messages from context and sync todos.
- Close provider sessions when switching files, or when same-file reload changed replay messages.
- Restore model (if available in current registry).
- Restore or initialize thinking level and service tier.
- Reconnect agent event subscription.
If any step after the capture fails, switchSession() restores the captured state and reconnects the previous agent subscription before rethrowing.
No new session file is created by switchSession() itself.
Event emissions and cancellation points
Switch/fork lifecycle hooks
For newSession, fork, and switchSession:
- Before event:
session_before_switch- reasons:
new,fork,resume - cancellable by returning
{ cancel: true }
- reasons:
- After event:
session_switch- same reason set
- includes
previousSessionFile
ExtensionRunner.emit() returns early on the first cancelling before-event result.
Custom tool onSession behavior
SDK bridges extension session events to custom tool onSession callbacks:
session_switch->onSession({ reason: "switch", previousSessionFile })session_branch->reason: "branch"session_start->reason: "start"session_tree->reason: "tree"session_shutdown->reason: "shutdown"
These callbacks are observational; they do not cancel switch/fork.
Other cancellation surfaces relevant to this doc
/forkis blocked while streaming (user must wait/abort current response first)./resumeselector can be cancelled by user closing selector.- Cross-project
--resume <id>can be cancelled by declining fork prompt. /sharehas UI abort path (Share cancelled) for gist flow; it does not wire process-kill semantics forgh gist createin this code path.
Non-persistent (in-memory) session behavior
When session manager is created with SessionManager.inMemory() (--no-session):
- Session file path is absent.
/exportand/sharefail withCannot export in-memory session to HTML(propagated to command error UI)./forkfails becauseSessionManager.fork()requires persistence./dumpstill works because it serializes in-memory agent state.- CLI resume/continue semantics are bypassed if
--no-sessionis set, because manager creation returns in-memory immediately.
Known implementation caveats (as of current code)
SelectorController.handleResumeSession()does not check the boolean result fromsession.switchSession(...); a hook-cancelled switch can still proceed through UI "Resumed session" repaint/status path./sharecustom-share failures do not degrade to default gist fallback; they terminate the command with error./exportargument tokenization is simplistic and does not preserve quoted paths with spaces.