mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 08:03:58 +00:00
fix: harden structured desktop status projection
This commit is contained in:
@@ -4,6 +4,7 @@ import { parsePaneKey } from '../../../../shared/stable-pane-id'
|
||||
|
||||
export type LiveEntriesByWorktreeCache = {
|
||||
tabsByWorktree: AppState['tabsByWorktree']
|
||||
unifiedTabsByWorktree: AppState['unifiedTabsByWorktree'] | undefined
|
||||
agentStatusByPaneKey: AppState['agentStatusByPaneKey']
|
||||
entriesByWorktree: Map<string, AgentStatusEntry[]>
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
AgentStatusEntry,
|
||||
MigrationUnsupportedPtyEntry
|
||||
} from '../../../../shared/agent-status-types'
|
||||
import type { TerminalTab } from '../../../../shared/types'
|
||||
import type { Tab, TerminalTab } from '../../../../shared/types'
|
||||
import type { RetainedAgentEntry } from '@/store/slices/agent-status'
|
||||
import { makePaneKey } from '../../../../shared/stable-pane-id'
|
||||
import { getLiveEntriesFullRebuildCountForTests } from './worktree-agent-live-index-patch'
|
||||
@@ -152,6 +152,44 @@ describe('selectLiveAgentStatusEntriesForWorktree', () => {
|
||||
expect(selectLiveAgentStatusEntriesForWorktree(state, 'wt-1')).toEqual([childEntry])
|
||||
})
|
||||
|
||||
it('keeps an idle structured session visible while its unified tab exists', () => {
|
||||
const entry = makeEntry(PANE_KEY_1, 1000, {
|
||||
state: 'done',
|
||||
sessionBoundary: true,
|
||||
worktreeId: 'wt-1',
|
||||
tabId: 'tab-1'
|
||||
})
|
||||
const structuredTab = {
|
||||
id: 'tab-1',
|
||||
worktreeId: 'wt-1',
|
||||
groupId: 'group-1',
|
||||
contentType: 'agent-session',
|
||||
entityId: 'session-1',
|
||||
label: 'Codex Chat',
|
||||
customLabel: null,
|
||||
color: null,
|
||||
sortOrder: 0,
|
||||
createdAt: 0,
|
||||
isPinned: false,
|
||||
agentSessionAgent: 'codex'
|
||||
} satisfies Tab
|
||||
const state = {
|
||||
tabsByWorktree: { 'wt-1': [] },
|
||||
unifiedTabsByWorktree: { 'wt-1': [structuredTab] },
|
||||
agentStatusByPaneKey: { [PANE_KEY_1]: entry },
|
||||
migrationUnsupportedByPtyId: {},
|
||||
retainedAgentsByPaneKey: {}
|
||||
}
|
||||
|
||||
expect(selectLiveAgentStatusEntriesForWorktree(state, 'wt-1')).toEqual([entry])
|
||||
expect(
|
||||
selectLiveAgentStatusEntriesForWorktree(
|
||||
{ ...state, unifiedTabsByWorktree: { 'wt-1': [] } },
|
||||
'wt-1'
|
||||
)
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('patches instead of full-rebuilding across within-state pings, and stays correct on transitions', () => {
|
||||
const wt1Entry = makeEntry(PANE_KEY_1, 1000, { state: 'working', prompt: 'wt1 prompt' })
|
||||
const wt2Entry = makeEntry(PANE_KEY_2, 1000, { state: 'working', prompt: 'wt2 prompt' })
|
||||
|
||||
@@ -28,7 +28,9 @@ type WorktreeAgentRowsState = Pick<
|
||||
| 'migrationUnsupportedByPtyId'
|
||||
| 'retainedAgentsByPaneKey'
|
||||
| 'tabsByWorktree'
|
||||
>
|
||||
> & {
|
||||
unifiedTabsByWorktree?: AppState['unifiedTabsByWorktree']
|
||||
}
|
||||
|
||||
type TabWorktreeIndexCache = {
|
||||
tabsByWorktree: WorktreeAgentRowsState['tabsByWorktree']
|
||||
@@ -82,18 +84,38 @@ function getTabIdToWorktreeId(
|
||||
return tabIdToWorktreeId
|
||||
}
|
||||
|
||||
function getLiveTabIdToWorktreeId(
|
||||
tabsByWorktree: WorktreeAgentRowsState['tabsByWorktree'],
|
||||
unifiedTabsByWorktree: WorktreeAgentRowsState['unifiedTabsByWorktree']
|
||||
): Map<string, string> {
|
||||
const tabIdToWorktreeId = new Map(getTabIdToWorktreeId(tabsByWorktree))
|
||||
for (const [worktreeId, tabs] of Object.entries(unifiedTabsByWorktree ?? EMPTY_RECORD)) {
|
||||
for (const tab of tabs) {
|
||||
if (tab.contentType === 'agent-session') {
|
||||
tabIdToWorktreeId.set(tab.id, worktreeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tabIdToWorktreeId
|
||||
}
|
||||
|
||||
function getLiveEntriesByWorktree(state: WorktreeAgentRowsState): Map<string, AgentStatusEntry[]> {
|
||||
const agentStatusByPaneKey = state.agentStatusByPaneKey ?? EMPTY_RECORD
|
||||
const tabsByWorktree = state.tabsByWorktree ?? EMPTY_RECORD
|
||||
const unifiedTabsByWorktree = state.unifiedTabsByWorktree
|
||||
if (
|
||||
liveEntriesByWorktreeCache?.tabsByWorktree === tabsByWorktree &&
|
||||
liveEntriesByWorktreeCache.unifiedTabsByWorktree === unifiedTabsByWorktree &&
|
||||
liveEntriesByWorktreeCache.agentStatusByPaneKey === agentStatusByPaneKey
|
||||
) {
|
||||
return liveEntriesByWorktreeCache.entriesByWorktree
|
||||
}
|
||||
|
||||
const tabIdToWorktreeId = getTabIdToWorktreeId(tabsByWorktree)
|
||||
if (liveEntriesByWorktreeCache?.tabsByWorktree === tabsByWorktree) {
|
||||
const tabIdToWorktreeId = getLiveTabIdToWorktreeId(tabsByWorktree, unifiedTabsByWorktree)
|
||||
if (
|
||||
liveEntriesByWorktreeCache?.tabsByWorktree === tabsByWorktree &&
|
||||
liveEntriesByWorktreeCache.unifiedTabsByWorktree === unifiedTabsByWorktree
|
||||
) {
|
||||
const patched = patchLiveEntriesByWorktree(
|
||||
liveEntriesByWorktreeCache,
|
||||
agentStatusByPaneKey,
|
||||
@@ -102,6 +124,7 @@ function getLiveEntriesByWorktree(state: WorktreeAgentRowsState): Map<string, Ag
|
||||
if (patched) {
|
||||
liveEntriesByWorktreeCache = {
|
||||
tabsByWorktree,
|
||||
unifiedTabsByWorktree,
|
||||
agentStatusByPaneKey,
|
||||
entriesByWorktree: patched
|
||||
}
|
||||
@@ -128,6 +151,7 @@ function getLiveEntriesByWorktree(state: WorktreeAgentRowsState): Map<string, Ag
|
||||
}
|
||||
liveEntriesByWorktreeCache = {
|
||||
tabsByWorktree,
|
||||
unifiedTabsByWorktree,
|
||||
agentStatusByPaneKey,
|
||||
entriesByWorktree
|
||||
}
|
||||
|
||||
@@ -2280,7 +2280,6 @@ function applyWebSessionTabsSnapshotWithContext(
|
||||
return sameOpenFiles(state.openFiles, next) ? state.openFiles : next
|
||||
})()
|
||||
advanceWebSessionOpenFilesIndex(batchContext, nextOpenFiles, worktreeId)
|
||||
const currentUnifiedTabs = state.unifiedTabsByWorktree[worktreeId] ?? []
|
||||
const retainedUnifiedTabs = currentUnifiedTabs.filter((tab) => {
|
||||
if (tab.contentType === 'agent-session') {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user