mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-26 17:38:36 +00:00
fix(desktop): avoid stale actions in memoized surfaces
This commit is contained in:
parent
5baf174781
commit
8f53429651
3 changed files with 104 additions and 23 deletions
46
apps/desktop/src/app/contrib/latest-actions.test.ts
Normal file
46
apps/desktop/src/app/contrib/latest-actions.test.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { latestChatActions } from './latest-actions'
|
||||
import type { ChatActions } from './types'
|
||||
|
||||
function makeActions(onSubmit: ChatActions['onSubmit']): ChatActions {
|
||||
return {
|
||||
onAddContextRef: vi.fn(),
|
||||
onAddUrl: vi.fn(),
|
||||
onAttachDroppedItems: vi.fn(),
|
||||
onAttachImageBlob: vi.fn(),
|
||||
onBranchInNewChat: vi.fn(),
|
||||
onCancel: vi.fn(),
|
||||
onDeleteSelectedSession: vi.fn(),
|
||||
onDismissError: vi.fn(),
|
||||
onEdit: vi.fn(),
|
||||
onPasteClipboardImage: vi.fn(),
|
||||
onPickFiles: vi.fn(),
|
||||
onPickFolders: vi.fn(),
|
||||
onPickImages: vi.fn(),
|
||||
onReload: vi.fn(),
|
||||
onRemoveAttachment: vi.fn(),
|
||||
onRestoreToMessage: vi.fn(),
|
||||
onRetryResume: vi.fn(),
|
||||
onSteer: vi.fn(),
|
||||
onSubmit,
|
||||
onThreadMessagesChange: vi.fn(),
|
||||
onToggleSelectedPin: vi.fn(),
|
||||
onTranscribeAudio: vi.fn()
|
||||
}
|
||||
}
|
||||
|
||||
describe('latestActions adapters', () => {
|
||||
it('dereferences the latest chat handler from a stable actions object', async () => {
|
||||
const staleSubmit = vi.fn(async () => false)
|
||||
const latestSubmit = vi.fn(async () => true)
|
||||
const actions = makeActions(staleSubmit)
|
||||
const adapted = latestChatActions(actions)
|
||||
|
||||
actions.onSubmit = latestSubmit
|
||||
|
||||
await expect(adapted.onSubmit('continue in selected session')).resolves.toBe(true)
|
||||
expect(staleSubmit).not.toHaveBeenCalled()
|
||||
expect(latestSubmit).toHaveBeenCalledWith('continue in selected session')
|
||||
})
|
||||
})
|
||||
52
apps/desktop/src/app/contrib/latest-actions.ts
Normal file
52
apps/desktop/src/app/contrib/latest-actions.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import type { ChatActions, SidebarActions } from './types'
|
||||
|
||||
/**
|
||||
* Surfaces receive one stable `actions` object whose fields are mutated by the
|
||||
* wiring controller each render. If a memoized surface passes `actions.foo`
|
||||
* directly, the child keeps the function from the surface's last render and can
|
||||
* submit/click against a stale session closure. These adapters keep a stable
|
||||
* wrapper but dereference the latest field at call time.
|
||||
*/
|
||||
export function latestChatActions(actions: ChatActions): ChatActions {
|
||||
return {
|
||||
onAddContextRef: (...args) => actions.onAddContextRef(...args),
|
||||
onAddUrl: (...args) => actions.onAddUrl(...args),
|
||||
onAttachDroppedItems: (...args) => actions.onAttachDroppedItems(...args),
|
||||
onAttachImageBlob: (...args) => actions.onAttachImageBlob(...args),
|
||||
onBranchInNewChat: (...args) => actions.onBranchInNewChat(...args),
|
||||
onCancel: (...args) => actions.onCancel(...args),
|
||||
onDeleteSelectedSession: (...args) => actions.onDeleteSelectedSession(...args),
|
||||
onDismissError: (...args) => actions.onDismissError?.(...args),
|
||||
onEdit: (...args) => actions.onEdit(...args),
|
||||
onPasteClipboardImage: (...args) => actions.onPasteClipboardImage(...args),
|
||||
onPickFiles: (...args) => actions.onPickFiles(...args),
|
||||
onPickFolders: (...args) => actions.onPickFolders(...args),
|
||||
onPickImages: (...args) => actions.onPickImages(...args),
|
||||
onReload: (...args) => actions.onReload(...args),
|
||||
onRemoveAttachment: (...args) => actions.onRemoveAttachment(...args),
|
||||
onRestoreToMessage: (...args) => actions.onRestoreToMessage?.(...args) ?? Promise.resolve(),
|
||||
onRetryResume: (...args) => actions.onRetryResume(...args),
|
||||
onSteer: (...args) => actions.onSteer(...args),
|
||||
onSubmit: (...args) => actions.onSubmit(...args),
|
||||
onThreadMessagesChange: (...args) => actions.onThreadMessagesChange(...args),
|
||||
onToggleSelectedPin: (...args) => actions.onToggleSelectedPin(...args),
|
||||
onTranscribeAudio: (...args) => actions.onTranscribeAudio?.(...args) ?? Promise.resolve('')
|
||||
}
|
||||
}
|
||||
|
||||
export function latestSidebarActions(actions: SidebarActions): SidebarActions {
|
||||
return {
|
||||
onArchiveSession: (...args) => actions.onArchiveSession(...args),
|
||||
onBranchSession: (...args) => actions.onBranchSession(...args),
|
||||
onDeleteSession: (...args) => actions.onDeleteSession(...args),
|
||||
onLoadMoreMessaging: (...args) => actions.onLoadMoreMessaging?.(...args),
|
||||
onLoadMoreProfileSessions: (...args) => actions.onLoadMoreProfileSessions?.(...args),
|
||||
onLoadMoreSessions: (...args) => actions.onLoadMoreSessions(...args),
|
||||
onManageCronJob: (...args) => actions.onManageCronJob(...args),
|
||||
onNavigate: (...args) => actions.onNavigate(...args),
|
||||
onNewSessionInWorkspace: (...args) => actions.onNewSessionInWorkspace(...args),
|
||||
onNewSessionSplit: (...args) => actions.onNewSessionSplit(...args),
|
||||
onResumeSession: (...args) => actions.onResumeSession(...args),
|
||||
onTriggerCronJob: (...args) => actions.onTriggerCronJob(...args)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import { useStatusbarItems } from '../shell/hooks/use-statusbar-items'
|
|||
import { ModelMenuPanel } from '../shell/model-menu-panel'
|
||||
import { StatusbarControls } from '../shell/statusbar-controls'
|
||||
|
||||
import { latestChatActions, latestSidebarActions } from './latest-actions'
|
||||
import { setStatusbarItemGroup, useStatusbarContributions } from './panes'
|
||||
import type { SidebarActions, WiringActions } from './types'
|
||||
|
||||
|
|
@ -48,7 +49,9 @@ export const SidebarSurface = memo(function SidebarSurface({
|
|||
actions: SidebarActions
|
||||
currentView: ComponentProps<typeof ChatSidebar>['currentView']
|
||||
}) {
|
||||
return <ChatSidebar currentView={currentView} {...actions} />
|
||||
const latestActions = useMemo(() => latestSidebarActions(actions), [actions])
|
||||
|
||||
return <ChatSidebar currentView={currentView} {...latestActions} />
|
||||
})
|
||||
|
||||
export const TerminalSurface = memo(function TerminalSurface() {
|
||||
|
|
@ -137,33 +140,13 @@ export const ChatRoutesSurface = memo(function ChatRoutesSurface({
|
|||
[actions, activeGatewayProfile, gateway, gatewayState]
|
||||
)
|
||||
|
||||
const chatActions = useMemo(() => latestChatActions(actions), [actions])
|
||||
const chatView = (
|
||||
<ChatView
|
||||
gateway={gateway}
|
||||
maxVoiceRecordingSeconds={maxVoiceRecordingSeconds}
|
||||
modelMenuContent={modelMenuContent}
|
||||
onAddContextRef={actions.onAddContextRef}
|
||||
onAddUrl={actions.onAddUrl}
|
||||
onAttachDroppedItems={actions.onAttachDroppedItems}
|
||||
onAttachImageBlob={actions.onAttachImageBlob}
|
||||
onBranchInNewChat={actions.onBranchInNewChat}
|
||||
onCancel={actions.onCancel}
|
||||
onDeleteSelectedSession={actions.onDeleteSelectedSession}
|
||||
onDismissError={actions.onDismissError}
|
||||
onEdit={actions.onEdit}
|
||||
onPasteClipboardImage={actions.onPasteClipboardImage}
|
||||
onPickFiles={actions.onPickFiles}
|
||||
onPickFolders={actions.onPickFolders}
|
||||
onPickImages={actions.onPickImages}
|
||||
onReload={actions.onReload}
|
||||
onRemoveAttachment={actions.onRemoveAttachment}
|
||||
onRestoreToMessage={actions.onRestoreToMessage}
|
||||
onRetryResume={actions.onRetryResume}
|
||||
onSteer={actions.onSteer}
|
||||
onSubmit={actions.onSubmit}
|
||||
onThreadMessagesChange={actions.onThreadMessagesChange}
|
||||
onToggleSelectedPin={actions.onToggleSelectedPin}
|
||||
onTranscribeAudio={actions.onTranscribeAudio}
|
||||
{...chatActions}
|
||||
/>
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue