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 |
/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; cross-project case can fork into current project |
--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 ~/.pisces/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
/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).
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 treat as id prefix:
- search current scope (
SessionManager.list(cwd, sessionDir)) - if not found and no explicit
sessionDir, search global (SessionManager.listAll())
- search current scope (
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.
- Clear queued steering/follow-up/next-turn messages.
- Flush current session manager writes.
sessionManager.setSessionFile(sessionPath)and updateagent.sessionId.- Build session context from loaded entries.
- Emit
session_switchwithreason: "resume". - Replace agent messages from context.
- Restore model (if available in current registry).
- Restore or initialize thinking level.
- Reconnect agent event subscription.
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.