feat: canonical workspace tab IDs with URL routing and draft agent tabs

Rewrite workspace tab management to use stable tab IDs (agent_, terminal_, draft_, file_) with
dedicated /tab/[tabId] routes, replace separate file-tabs store with unified workspace-tabs store,
support multiple concurrent draft agent tabs, and detect text files by content instead of extension allowlist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-03-03 20:11:55 +07:00
co-authored by Claude Opus 4.6
parent 97f0f4b266
commit 73eabd2eea
17 changed files with 1732 additions and 768 deletions
@@ -51,6 +51,7 @@ import { AGENT_PROVIDER_DEFINITIONS } from '@server/server/agent/provider-manife
import { buildHostAgentDetailRoute } from '@/utils/host-routes'
import { useTauriDragHandlers } from '@/utils/tauri-window'
import { useKeyboardShiftStyle } from '@/hooks/use-keyboard-shift-style'
import { normalizeAgentSnapshot } from '@/utils/agent-snapshots'
const DRAFT_AGENT_ID = '__new_agent__'
const EMPTY_PENDING_PERMISSIONS = new Map()
@@ -1023,9 +1024,15 @@ function DraftAgentScreenContent({
git: gitOptions,
})
const agentId = (result as { id?: string })?.id
const agentId = result.id
if (agentId && selectedServerId) {
updatePendingAgentId(agentId)
updatePendingAgentId({ draftId: draftIdRef.current, agentId })
useSessionStore.getState().setAgents(selectedServerId, (prev) => {
const next = new Map(prev)
next.set(agentId, normalizeAgentSnapshot(result, selectedServerId))
return next
})
void createAgentClient.refreshAgent(agentId).catch(() => undefined)
router.replace(buildHostAgentDetailRoute(selectedServerId, agentId) as any)
return
}
@@ -1035,15 +1042,15 @@ function DraftAgentScreenContent({
message: 'Failed to create agent',
})
onCreateFlowActiveChange?.(false)
markPendingCreateLifecycle('abandoned')
clearPendingCreateAttempt()
markPendingCreateLifecycle({ draftId: draftIdRef.current, lifecycle: 'abandoned' })
clearPendingCreateAttempt({ draftId: draftIdRef.current })
throw new Error('Failed to create agent')
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create agent'
dispatch({ type: 'CREATE_FAILED', message })
onCreateFlowActiveChange?.(false)
markPendingCreateLifecycle('abandoned')
clearPendingCreateAttempt()
markPendingCreateLifecycle({ draftId: draftIdRef.current, lifecycle: 'abandoned' })
clearPendingCreateAttempt({ draftId: draftIdRef.current })
throw error // Re-throw so AgentInputArea knows it failed
}
},