feat(v0.3): O2 — resume past Claude sessions

- src/http/history.ts: listSessions() reads ~/.claude/projects/*/*.jsonl
  (mtime-sorted, top 50), parseSessionMeta() extracts cwd + first user prompt
  (pure, 4 unit tests); GET /sessions
- terminal-session: opts.initialInput (typed ~700ms after the shell is ready)
- tabs.newTabForResume(cwd,id): new tab in the project dir running
  'claude --resume <id>'
- public/history.ts: 🕘 panel listing sessions (project · time · preview) with
  Resume buttons
- Verified: /sessions returns 44 real sessions; panel lists project/time/preview.
  216 tests green.
This commit is contained in:
Yaojia Wang
2026-06-18 08:04:04 +02:00
parent 25269cad3f
commit e36ca272ed
8 changed files with 366 additions and 1 deletions

View File

@@ -54,6 +54,8 @@ export interface TerminalSessionOpts {
onClaudeStatus?: (status: ClaudeStatus, detail?: string) => void
/** Optional: spawn a NEW session in this directory ("new tab here", M6). */
cwd?: string
/** Optional: type this once the new session's shell is ready (O2 resume). */
initialInput?: string
}
export class TerminalSession {
@@ -69,6 +71,8 @@ export class TerminalSession {
private readonly onStatus: ((status: SessionStatus) => void) | undefined
private readonly onClaudeStatus: ((status: ClaudeStatus, detail?: string) => void) | undefined
private readonly spawnCwd: string | undefined
private readonly initialInput: string | undefined
private initialSent = false
private statusValue: SessionStatus = 'connecting'
private claudeStatusValue: ClaudeStatus = 'unknown'
private cwdValue: string | null = null
@@ -94,6 +98,7 @@ export class TerminalSession {
this.onStatus = opts.onStatus
this.onClaudeStatus = opts.onClaudeStatus
this.spawnCwd = opts.cwd
this.initialInput = opts.initialInput
this.el = document.createElement('div')
this.el.className = 'term-pane'
@@ -238,6 +243,12 @@ export class TerminalSession {
this.onSessionId(msg.sessionId)
const dims = this.safefit()
if (dims !== null) this.sendResize(dims.cols, dims.rows)
// O2: once the shell is up, type the resume command (only on first attach).
if (this.initialInput !== undefined && !this.initialSent) {
this.initialSent = true
const cmd = this.initialInput
setTimeout(() => this.send(cmd), 700)
}
break
}
case 'output': {