fix: resume double-refresh — idempotent session_start UI installs - #13
fix: resume double-refresh — idempotent session_start UI installs#13agnitum2009 wants to merge 12 commits into
Conversation
tasks 残留根因(遗忘+机制不全)→ commit-task-sync hook 尝试: - extensions/commit-task-sync/index.ts:pi.on(tool_result) 检测 bash git commit 成功 → pi.on(agent_settled) ctx.ui.notify 提示 agent 同步 tasks(仿 post-edit,fire-and-forget) - tsc 通过(exit 0) - docs/knowledge-base/:obsidian vault(residual-root-cause + hook-design + implementation) - 发现:openpi 有 pi.on(tool_result) 事件(PostToolUse 等价)
双通道提醒:
1. agent_settled → ui.notify(TUI/业主,建议性)
2. context → injectCommitReminder(对话/agent,强制——注入 <commit-task-sync> 块到下轮 messages)
context injection 仿 injectTaskProjection(tasks/index.ts:483 pi.on(context) return {messages})。
commit 检测后下轮注入提示+reset(仅提醒一次)。agent 不能忽略(在对话上下文)。
tsc 通过。
更新 commit-task-sync-implementation.md: - v1 ui.notify + v2 context injection 演进 - 双通道验证(user TUI ✅ + agent context injection ✅) - tsc TS2769 修复(event 类型推断) - openpi hook 机制发现(pi.on tool_result/context + post-edit/injectTaskProjection 范本) - pi.on 可用事件清单 - PR openpi-dev#6(tt-a1i/openpi) - 结论:tasks 残留从靠记忆力变机制强制提醒
升级 commit-task-sync(单信号 A)→ multi-signal-sync(MECE 全场景): - A commit(git commit exit 0,tool_result 检测) - B verify(tsc/test/verify PASS exit 0,tool_result 检测) - C authorization(user message 授权语,context 检测) dual-channel:agent_settled ui.notify + context injectSyncReminder(<multi-signal-sync> 块) D/E(无变更/取消)无信号 → 收口审计纪律(提醒文本已含) 第一性原理:tasks 完成 = 真实完成信号驱动,非 agent 记忆。 MECE:commit/验证/授权 三信号互斥穷尽(tool_result + context 可检测面)。 tsc 0错(TS2769 修复:event 类型推断)。
…ttled notify) bug: context 注入时 reset signals → agent_settled notify 不触发(signals 被 context 消费后已空)。 fix: 两个通道独立——signals(context 注入,下轮消费)+ pendingNotify(agent_settled notify,本轮消费)。 addSignal 同时加到两通道;context 消费 signals(注入块);agent_settled 消费 pendingNotify(notify)。
…s Indicator)
问题:ui.notify 瞬时消失(业主看不到驻留)。
fix:agent_settled → ctx.ui.setStatus(footer 驻留状态,跨 render 持续显示——tui.md Pattern 4)。
- 信号轮:setStatus('multi-signal-sync', '⚠️ 完成信号(...)— 请同步 tasks') 驻留
- 下一轮(无新信号):setStatus undefined 清除
- 双通道:对话 context 注入块(agent 提醒)+ footer 驻留状态(user 持续看到)
tsc 0错。
MECE 五类信号(A commit/B verify/C authorization/D 无变更/E 取消)+ 演进(v1-v5)+ 验证记录(A/B/C 三信号 notify 确认触发)+ v5 驻留(footer setStatus)+ openpi 机制发现。
…r gradient + blocked-task notifications - tasks: completed items stay in the widget struck through (omp todo-HUD style); in-progress subject carries a time-driven shimmer sweep with a 120ms redraw cadence; cross-extension attachment row under the census (shared/task-widget-attachment) so completion-signal reminders render in the task panel instead of the footer status bar - subagents: strip moved above the editor (aboveEditor) as a multi-row HUD with one row per running subagent, live tool action inline (omp currentTool style), unread-settled notice row; footer setStatus removed - workflows: strip moved above the editor as a multi-row HUD (header + per-agent rows), metrics in accent to stay distinguishable from the subagents HUD's warning yellow; footer setStatus removed - working-indicator: new extension overriding pi's braille spinner with a flowing truecolor gradient bar (10 frames, 80ms tick) on the Working... status line - goal/multi-signal-sync: blocked goal and blocked task announcements on agent_settled (once per blocked stint), fixing silent waits for human intervention
Port omp's (oh-my-pi) todo mechanisms that keep human-in-the-loop moments visible, so a task awaiting an owner decision can no longer sit silently behind a plausible status: - reconciliation (omp #reconcileTodosWithSubagents): settled subagents are recorded by the subagents extension and drained at agent_settled; a successful child whose title matches an open task auto-closes it with the child id as evidence, and a blocked task matching a successful child is closed as the unblock signal. Failed children stay open by design — the decision belongs to the user or the next turn. - idle recap (omp #runIdleRecap, notify variant): a settled turn with open work re-arms a 120s timer; if no new agent turn starts and open/blocked tasks remain, a single warning surfaces them (with a blocked count), instead of stalling until the user happens to ask. Any activity cancels. - markdown round-trip (omp /todo export|import): /tasks export writes an editable checklist (omp markers [ ], [/], [x], [!], [-]); /tasks import replaces the batch wholesale, notes via '-- note' suffix. - matching (omp normalizeForTodoMatch/todoMatchesAnyDescription): normalize then equal, substring fallback with a length floor; CJK digit-spacing variants reconcile because all non-alphanumerics including spaces drop. - single in-progress invariant (omp normalizeInProgressTask): starting a task demotes any other in-flight task to pending. - nextActionableTask exported for recap anchoring (in-flight first, blocked excluded).
…tall warning, task light-up DDD-layered visibility for running subagents, so a plausible 'running' status can no longer mask a stalled or failing child (first-principles: the surface must distinguish 在动 / 在等 / 死了 for every in-flight agent): - domain (pure, fully tested): lastActivityOf/isStalled (5m silence threshold), lastIntentOf (live streaming text, then latest assistant text), failureStreakOf over the snapshot; STALL_THRESHOLD_MS constant - application: manager folds every event into lastActivityAt; consecutive ToolEnd isError streaks count up and reset on success or settle (pi's kernel exposes no retry event, so the streak is the honest proxy) - UI (HUD rows): live tool → intent fallback (💬) → stall warning (⚠ 无活动 Nm); ✗ 连败 N after 2+ consecutive failures - light-up bridge (omp): subagents publishes running-child descriptions; tasks widget highlights a pending task whose subject matches a running child (accent icon + bold subject), so 'someone is already doing this' is visible before the reconcile closes it
/resume replays the session and re-fires session_start on every extension. Two structural updates were repeated on each resume, forcing a second full-viewport repaint after the kernel's own rebuild: - subagents installSubagentNavigation wrapped the editor again on every session_start, stacking another BelowEditorNavigationEditor layer and replacing the editor component (structural change → full repaint). Now installed once per runtime. - tasks updateTaskWidget called setWidget unconditionally, destroying and rebuilding the widget component on every resume. Now skips when the widget is already installed in the same shown/hidden state. (omp's kernel avoids the first repaint entirely via reuseSettledComponents component caching; pi 0.84.1 rebuilds the chat from scratch, so the extension-side amplifiers were the fixable part here.)
…/suggestions PR openpi-dev#13 made subagents' editor wrapper one-shot, but workflows and suggestions re-wrap the editor on every session_start too (each /resume stacks another WorkflowNavigationEditor / NextActionSuggestionEditor layer and replaces the editor component — a structural change forcing a second full-viewport repaint). Same one-shot guard applied to both; ui-customization header/footer re-install is addressed separately.
…/suggestions PR openpi-dev#13 made subagents' editor wrapper one-shot, but workflows and suggestions re-wrap the editor on every session_start too (each /resume stacks another WorkflowNavigationEditor / NextActionSuggestionEditor layer and replaces the editor component — a structural change forcing a second full-viewport repaint). Same one-shot guard applied to both; ui-customization header/footer re-install is addressed separately.
|
这个问题线索是有价值的,我按当前 Pi 在 Tasks 这处还有一个独立回归:widget factory 捕获了第一次安装时的 新增测试没有覆盖上述生命周期:它只在同一个 fake instance/context 上连续触发两次 我倾向于关闭这个旧 PR,保留问题,基于当前
我这次只做了源码审查,还没有做 TUI 肉眼复现,所以“当前版本仍可见双刷”暂时不当成已确认;但现有 guard 确定不能作为修复合入。 |
|
按当前 main 和 Pi 0.84.1 的真实 Resume 生命周期复核后,这版 instance-local guard 不能解决重新绑定,并会让 Task widget 捕获旧快照。先关闭这个累计旧栈 PR;后续会基于最新 main 提交带真实生命周期回归测试的小修复。 |
Problem
/resumevisibly refreshes the whole session content twice before settling. Root cause (compared against omp/oh-my-pi):rebuildChatFromMessages()=chatContainer.clear() + renderSessionEntries()— full rebuild with zero component reuse. omp's equivalent acceptsreuseSettledComponentsand reuses cached components from a WeakMap, so its replay does not repaint. (Not fixable from an extension package.)session_startre-fires on every resume, and two openpi extensions performed repeated structural updates that force a second full-viewport repaint:subagents/installSubagentNavigationwrapped the editor again each time — stacking anotherBelowEditorNavigationEditorlayer and replacing the editor component.tasks/updateTaskWidgetcalledsetWidgetunconditionally — destroying and rebuilding the widget component.Fix
session_startevents reuse it.setWidgetwhen the widget is already installed in the same shown/hidden state (mirrors the existing idempotency in subagents/workflows strip widgets).Tests
232 related tests passing (new: repeated
session_startdoes not rebuild an already-installed widget); tsc --noEmit clean; prettier clean.