feat(resume): Resume in the kebab menu (A1) + harden the resume kickoff (A2)

A1 — SessionMenu gains a Resume item, gated to status!=="finalized" && !archived
(matching the backend's 409 read-only guard), wired in AppShell to doResume ->
POST /sessions/:id/resume. SessionMenu.test.tsx (3 tests); frontend 96/96, tsc clean.

A2 — diagnosis-first clean-room repro driving `pi --mode rpc` with the backend's
exact resume handshake shows the cold-start stall NO LONGER reproduces on pi
0.79.4 (8/8 chained into `tht session show` + `read SKILL.md` in-turn, fresh and
partway sessions). The earlier narrate-and-stop predates the pi upgrade.
Defense-in-depth anyway: RIPRENDI_KICKOFF hardened to force the in-turn tool call
(gate_resume_kickoff.test.js + live regression 2/2). Gate JS 34/34.

PROJECT_STATE open-item #1 (resume stall) flipped to RESOLVED; cross-model resume
robustness folded into workstream G.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 18:19:10 +02:00
co-authored by Claude Opus 4.8
parent cef9ae4368
commit 0a13f71b4d
6 changed files with 105 additions and 15 deletions
+8 -1
View File
@@ -5,6 +5,7 @@ import type { SessionSummary } from "../api/types";
interface Props {
session: SessionSummary;
groups: string[];
onResume: () => void;
onView: () => void;
onRename: () => void;
onMove: (group: string) => void;
@@ -16,7 +17,10 @@ interface Props {
const itemCls =
"flex w-full cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none data-highlighted:bg-accent data-highlighted:text-accent-foreground";
export function SessionMenu({ session, groups, onView, onRename, onMove, onNewGroup, onArchiveToggle, onDelete }: Props) {
export function SessionMenu({ session, groups, onResume, onView, onRename, onMove, onNewGroup, onArchiveToggle, onDelete }: Props) {
// Resume re-enters the workflow; only meaningful while the session is still live
// (the backend returns 409 for finalized/archived, matching this guard).
const canResume = session.status !== "finalized" && !session.archived;
return (
<Menu.Root>
<Menu.Trigger
@@ -29,6 +33,9 @@ export function SessionMenu({ session, groups, onView, onRename, onMove, onNewGr
<Menu.Portal>
<Menu.Positioner side="bottom" align="end" sideOffset={4}>
<Menu.Popup className="z-50 min-w-44 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-none">
{canResume && (
<Menu.Item className={itemCls} onClick={onResume}>Resume</Menu.Item>
)}
<Menu.Item className={itemCls} onClick={onView}>Split view</Menu.Item>
<Menu.Item className={itemCls} onClick={onRename}>Rename</Menu.Item>
<Menu.SubmenuRoot>