Preserve history when deduplicating agent sessions#471
Preserve history when deduplicating agent sessions#471Liu-KM wants to merge 1 commit intotiann:mainfrom
Conversation
There was a problem hiding this comment.
Findings
- [Major] Active-duplicate permission requests are now routed to the wrong CLI session. The new active path calls
mergeSessionHistory()for a live duplicate (hub/src/sync/sessionCache.ts:683), which still copiesoldStored.agentState.requestsontonewSessionId; approvals then look up the request on the visible session and dispatch RPCs by that same session id (hub/src/web/routes/permissions.ts:52,hub/src/sync/rpcGateway.ts:54). If the pending request originated on the kept-alive duplicate, the UI will show it on the canonical session but the approve/deny call will go to the wrong socket. - [Major] History moved from an active duplicate will not appear in an already-open chat until a manual refresh. This new path moves rows in SQLite but only emits
session-updated(hub/src/sync/sessionCache.ts:538), while the web message window is refreshed only onmessage-receivedor explicitfetchLatestMessages(web/src/hooks/useSSE.ts:500,web/src/hooks/queries/useMessages.ts:59).
Summary
Review mode: initial
Two regressions found in the new active-duplicate merge path; details are attached inline.
Testing
Not run (automation)
HAPI Bot
| // persisted history into the visible dedup target. This preserves | ||
| // left-sidebar dedup while making resumed/restarted sessions show | ||
| // the full conversation history. | ||
| await this.mergeSessionHistory(id, targetId, targetNamespace) |
There was a problem hiding this comment.
[MAJOR] This new branch sends active duplicates through mergeSessionHistory(), which also merges oldStored.agentState into targetId. The problem is that permission approval/deny still routes by session id, so a pending request raised by the kept-alive duplicate will now be shown on the canonical session and then sent to the wrong CLI socket when the user acts on it.
Suggested fix:
if (candidate?.active) {
await this.mergeSessionHistory(id, targetId, targetNamespace, {
mergeAgentState: false,
})
}| this.refreshSession(newSessionId) | ||
| const refreshed = this.refreshSession(newSessionId) | ||
| if (refreshed) { | ||
| this.publisher.emit({ type: 'session-updated', sessionId: newSessionId, data: refreshed }) |
There was a problem hiding this comment.
[MAJOR] This history-only merge rewrites message rows in SQLite, but it only publishes session-updated afterwards. The web cache appends on message-received and otherwise reloads messages only on mount/refetch, so an already-open canonical chat keeps showing the pre-merge transcript until the user manually refreshes.
Suggested fix:
const moved = this.store.messages.mergeSessionMessages(oldSessionId, newSessionId)
if (moved.moved > 0) {
this.publisher.emit({ type: 'messages-invalidated', sessionId: newSessionId })
}
Summary
Preserve conversation history when multiple HAPI sessions point at the same underlying agent session ID.
Why
The sidebar deduplicates sessions by agent session ID, but historical messages could remain attached to the hidden duplicate. That made the visible session look like it had lost history.
Validation
cd hub && bun test src/sync/sessionModel.test.tsNotes
AI-assisted with GPT-5.4.