fix(omo-senpi): rebuild the plan gate on session resume by replaying the transcript
Summary
Refs #10378 (item 2b). After a restart or a restore, the plan gate for plan-consultant / plan-reviewer forgot that the user had asked for ulw-plan and touched a plan. The lane had to re-send /skill:ulw-plan and touch the plan again.
skill-invocation-tracker.ts now rebuilds its three per-session channels on session_start by replaying the session's own branch through the same observers the live events use. This is option A from the design on the issue.
- Tool-result channels replay for every session, whatever engine wrote it: a successful
readof a SKILL.md, andread/write/edit/apply_patchtouching.omo/plans/*.md. EachtoolResultentry is paired with its assistant tool call bytoolCallIdto recover the arguments. - The user-request channel replays only from user entries that carry the engine's
inputSource(code-yeongyu/senpi#3690), and never fromextension. A user entry with no source can't be told apart from injected input, so it never arms the gate. On a senpi without #3690, replay rebuilds the tool channels and the user still re-arms ulw-plan once. Nothing is trusted that wasn't trusted live. - The live handlers and the replay share
observeUserInput/observeToolResult, so classification and trust are identical by construction. The replay resets the session first, so a secondsession_startdoes not double-count touches.
Known residual: a persisted user entry holds the text after skill/template expansion and input transforms, while the live input event saw the raw text. Skill bodies and the injected <ultrawork-mode> / <system-reminder> blocks are stripped as before, but a prompt template's expanded body is classified on replay. The user still chose to run that template, and the entry is still human-sourced.
Evidence (head d693ecfc97)
- RED:
skill-invocation-tracker-replay.test.ts, 4 of 7 failed on the pre-fix tracker (the four positive replays). The three trust-boundary cases (extension-sourced, unsourced, failed or orphaned tool results) pass both before and after by design. - GREEN:
bun test packages/omo-senpi/src/components/task/skill-invocation-tracker, 51/51 across 3 files. tsgo --noEmit -p packages/omo-senpi/tsconfig.json: exit 0.- Real senpi,
plan-gated-agents-e2e.mjs --scenario resume(new): results to follow in a comment.
Summary by cubic
Rebuilds the plan gate's per-session state on session_start by replaying the session's transcript, so a restarted or restored session no longer forgets that the user asked for ulw-plan and touched a plan.
- Replays tool results for every session — successful
readof a SKILL.md andread/write/edit/apply_patchtouching.omo/plans/*.md— pairing each result with its tool call bytoolCallId; failed and orphaned results never count. - Replays the user-request channel only from entries carrying
inputSourceofinteractiveorrpc;extension, absent, unknown, and message-level sources never arm the gate. - Live handlers and replay share
observeUserInput/observeToolResult; session state resets first so a secondsession_startdoes not double-count touches, and a host that cannot read the branch keeps its live state. - Adds a
resumescenario to the plan-gated-agents e2e that continues a session in a second process, with--expect replayedand--expect unsourcedverdicts.