mirror of
https://github.com/stablyai/orca.git
synced 2026-11-05 19:47:27 +00:00
* fix(runtime): save a windowless host's chat tabs in its layout Claude-Session: p2-headless * fix(runtime): remove a chat's saved tab on the index's hidden notice, which every close reaches Claude-Session: p2-headless * fix(runtime): a chat whose record is gone loses its saved tab at startup Claude-Session: p2-headless * test: run the windowless chat-layout host test in the Node runtime project Claude-Session: p2-headless * fix(runtime): remove a chat's saved tab from whichever partition still holds it Claude-Session: p2-headless * refactor(runtime): list saved chat tabs across partitions in the layout module Claude-Session: p2-headless * refactor(runtime): read saved chat tabs straight from the store's partitions Claude-Session: p2-headless * fix(chat-tabs): save a windowless chat tab under its own tab id Review pass: the saved tab reuses the id status and attention address, the close checks for a window before scanning partitions, and the host tests seed readable records with unreadable, other-host and no-index cases. Claude-Session: p2-headless * test(chat-tabs): a window opening a windowless-saved chat reuses its tab Claude-Session: p2-headless * test(chat-tabs): check the saved tab with the lookup a window reuses The window's existing-tab lookup moves to the shared projection so the windowless-layout test exercises it without importing the renderer. Claude-Session: p2-headless * test(chat-tabs): assert the reused tab's identity directly Drops the shared lookup added for the test; two functions already carry that name. Claude-Session: p2-headless * fix(chat-tabs): leave an unstamped chat tab in an SSH partition to that host Claude-Session: p2-headless * fix(chat-tabs): count an unstamped chat tab as this host's only in the local partition A desktop mirrors a remote server's chats into that server's runtime partition without a host stamp; the startup sweep must not delete them. Claude-Session: p2-headless * refactor(chat-tabs): one chat tab writer, one startup match, local partition only Chats run only on local hosts, so their saved tabs live in the local partition: the cross-partition listing and its writer go. Open and close share one layout load, the startup fill and sweep become one function beside the index seed, and the tab filter is the existing savedSessionId. Claude-Session: p2-headless * fix(chat-tabs): restore and sweep saved Claude chat tabs like any structured chat The Claude skip predates Claude structured chats (#18519, #18560); an id the host has no record of is already dropped by the readable restorer. Claude-Session: p2-headless
67 lines
3.0 KiB
TypeScript
67 lines
3.0 KiB
TypeScript
// A chat tab a host with no window shows, saved in its layout as a window saves its own, so the
|
|
// layout is a complete record of the host's tabs.
|
|
|
|
import type { AgentType } from '../../shared/agent-status-types'
|
|
import type { ExecutionHostId } from '../../shared/execution-host'
|
|
import { applyLayoutCommand } from '../../shared/workspace-layout/workspace-layout-commands'
|
|
import { loadWorkspaceLayout } from '../../shared/workspace-layout/workspace-layout-load'
|
|
import { nameBasedLoadContext } from '../../shared/workspace-layout/workspace-layout-minted-ids'
|
|
import type { LayoutCommand } from '../../shared/workspace-layout/workspace-layout-command-types'
|
|
import { saveWorkspaceLayout } from '../../shared/workspace-layout/workspace-layout-save'
|
|
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
|
|
import { randomUUID } from 'node:crypto'
|
|
import { getStructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-registry'
|
|
import { structuredAgentSessionTabId } from '../../shared/structured-agent-session-projection'
|
|
|
|
export type ChatTabChange =
|
|
| { open: { sessionId: string; agent: AgentType; tabId: string } }
|
|
| { close: string }
|
|
|
|
/**
|
|
* The partition with the worktree's chat tabs changed in one layout load: a chat opens in the active
|
|
* group unless a saved tab shows it, and a close drops the chat's tab. Null when nothing changed.
|
|
*/
|
|
export function withChatTabChanges(
|
|
hostId: ExecutionHostId,
|
|
session: WorkspaceSessionState,
|
|
worktreeId: string,
|
|
changes: readonly ChatTabChange[]
|
|
): WorkspaceSessionState | null {
|
|
const loaded = loadWorkspaceLayout(hostId, session, nameBasedLoadContext())
|
|
const groupId = loaded.desktopView.activeGroupIdByWorktree?.[worktreeId]
|
|
let layout = loaded.layout
|
|
for (const change of changes) {
|
|
const workspace = layout.workspaces[worktreeId]
|
|
const sessionId = 'close' in change ? change.close : change.open.sessionId
|
|
const tabId =
|
|
workspace?.tabs.find((tab) => tab.kind === 'agent-session' && tab.entityId === sessionId)
|
|
?.id ?? null
|
|
let command: LayoutCommand | null = null
|
|
if ('close' in change && tabId) {
|
|
command = { type: 'closeTabs', workspace: worktreeId, tabIds: [tabId], force: true }
|
|
} else if ('open' in change && !tabId) {
|
|
const open = { type: 'openAgentSessionTab', workspace: worktreeId, ...change.open } as const
|
|
command = groupId ? { ...open, groupId } : open
|
|
}
|
|
const applied = command
|
|
? applyLayoutCommand(layout, command, {
|
|
mintId: randomUUID,
|
|
mintLeafId: randomUUID,
|
|
now: Date.now
|
|
})
|
|
: null
|
|
if (applied?.ok) {
|
|
layout = applied.model
|
|
}
|
|
}
|
|
return layout === loaded.layout ? null : saveWorkspaceLayout({ ...loaded, layout })
|
|
}
|
|
|
|
/** The id the window, the index and status updates all address the chat's tab by. */
|
|
export function chatTabId(sessionId: string): string {
|
|
return (
|
|
getStructuredAgentSessionHost()?.getSessionTabId?.(sessionId) ??
|
|
structuredAgentSessionTabId(sessionId)
|
|
)
|
|
}
|