diff --git a/apps/cli/src/commands.ts b/apps/cli/src/commands.ts index 7f8bd42..3e1c8a7 100644 --- a/apps/cli/src/commands.ts +++ b/apps/cli/src/commands.ts @@ -415,9 +415,25 @@ export const AddDirCommand: SlashCommand = { export const ResumeCommand: SlashCommand = { name: '/resume', - description: 'List recent sessions.', - async run(_args, ctx) { + description: 'List recent sessions, or `/resume ` to switch live.', + async run(args, ctx) { const sessions = await ctx.sessions.list(); + if (args[0]) { + // Accept a full id, or a 1-based index into the recent list below. + let id = args[0].trim(); + const n = Number(id); + if (Number.isInteger(n) && n >= 1 && n <= sessions.length) id = sessions[n - 1]!.id; + const loaded = await ctx.sessions.load(id); + if (!loaded) return [`Session ${id} not found. Run /resume to list recent sessions.`]; + // Swap the live conversation (REPL applies newHistory) + the append target. + ctx.sessionId = id; + ctx.newHistory = loaded.messages; + const c = loaded.messages.length; + return [ + `โ†ป Switched to session ${id} (${c} message${c === 1 ? '' : 's'}).`, + 'New messages now append to this session.', + ]; + } if (sessions.length === 0) return ['No previous sessions.']; const top = sessions.slice(0, 10); return [ @@ -426,7 +442,7 @@ export const ResumeCommand: SlashCommand = { (s, i) => ` ${String(i + 1).padStart(2)}. ${s.id} ${s.title ?? s.cwd} (${s.updatedAt})`, ), '', - 'To resume: deepcode --resume (M2 picker in next iteration).', + 'Switch live with `/resume `, or `deepcode --resume ` at launch.', ]; }, }; diff --git a/apps/cli/src/parity-commands.test.ts b/apps/cli/src/parity-commands.test.ts index 45667c9..5448f86 100644 --- a/apps/cli/src/parity-commands.test.ts +++ b/apps/cli/src/parity-commands.test.ts @@ -182,3 +182,29 @@ describe('/config set', () => { expect(out.join('\n')).toMatch(/Usage: \/config set/); }); }); + +describe('/resume (live switch)', () => { + it('switches the live session: sets sessionId + newHistory', async () => { + const sm = new SessionManager({ root: await tmpHome() }); + const s = await sm.create('/proj', { title: 'old chat' }); + await sm.append(s.id, { role: 'user', content: [{ type: 'text', text: 'hi' }] }); + const c = ctx({ sessions: sm, sessionId: 'current-session' }); + const out = await reg.match('/resume')!.cmd.run([s.id], c); + expect(out.join('\n')).toMatch(/Switched to session/); + expect(c.sessionId).toBe(s.id); + expect(c.newHistory).toHaveLength(1); + }); + + it('errors on an unknown id', async () => { + const sm = new SessionManager({ root: await tmpHome() }); + const out = await reg.match('/resume')!.cmd.run(['nope-xyz'], ctx({ sessions: sm })); + expect(out.join('\n')).toMatch(/not found/i); + }); + + it('lists sessions with no args', async () => { + const sm = new SessionManager({ root: await tmpHome() }); + await sm.create('/proj', { title: 'a' }); + const out = await reg.match('/resume')!.cmd.run([], ctx({ sessions: sm })); + expect(out.join('\n')).toMatch(/Recent sessions/); + }); +}); diff --git a/apps/cli/src/repl.ts b/apps/cli/src/repl.ts index 6c2c059..65548eb 100644 --- a/apps/cli/src/repl.ts +++ b/apps/cli/src/repl.ts @@ -609,7 +609,9 @@ export async function startRepl(opts: ReplOpts): Promise { temperature, maxTurns: opts.maxTurns, cwd: ctx.cwd, - session: { manager: sessions, id: session.id }, + // ctx.sessionId (not the launch `session.id`) so a live `/resume ` + // switch redirects new messages to the resumed session. + session: { manager: sessions, id: ctx.sessionId }, mode: ctx.mode as Mode, permissions: settings.permissions, hooks, diff --git a/docs/BEHAVIOR_PARITY.md b/docs/BEHAVIOR_PARITY.md index aa1fa20..9c985e5 100644 --- a/docs/BEHAVIOR_PARITY.md +++ b/docs/BEHAVIOR_PARITY.md @@ -33,7 +33,7 @@ Legend: `โœ…` matches ยท `๐ŸŸก` matches with caveats ยท `๐Ÿ”„` deferred ยท `โš  | `/cost` / `/usage` | โœ“ | โœ“ | โœ… | | `/context` | โœ“ | โœ“ | โœ… | | `/config` | โœ“ | โœ“ | ๐ŸŸก โ€” dumps merged settings + `/config set ` (dotted keys, JSON values) writes user settings; no full arrow-key editor | -| `/resume` | โœ“ | โœ“ (list only) | ๐ŸŸก โ€” Claude Code has fuzzy picker; ours lists; pick via `--resume ` | +| `/resume` | โœ“ | โœ“ | โœ… โ€” lists recent sessions; `/resume ` switches the live session in-REPL; `--resume ` / `-r` at launch | | `/init` | โœ“ | โœ“ | โœ… โ€” interactive 3-phase REPL flow (scan โ†’ draft โ†’ approve-write `AGENTS.md`) | | `/mcp` | โœ“ | โœ“ | โœ… | | `/add-dir` | โœ“ | โœ“ (records intent) | ๐ŸŸก โ€” M3 will enforce |