source dump of claude code
at main 141 lines 4.4 kB view raw
1import { logEvent } from '../services/analytics/index.js' 2import { isTerminalTaskStatus } from '../Task.js' 3import type { LocalAgentTaskState } from '../tasks/LocalAgentTask/LocalAgentTask.js' 4 5// Inlined from framework.ts — importing creates a cycle through 6// BackgroundTasksDialog. Keep in sync with PANEL_GRACE_MS there. 7const PANEL_GRACE_MS = 30_000 8 9import type { AppState } from './AppState.js' 10 11// Inline type check instead of importing isLocalAgentTask — breaks the 12// teammateViewHelpers → LocalAgentTask runtime edge that creates a cycle 13// through BackgroundTasksDialog. 14function isLocalAgent(task: unknown): task is LocalAgentTaskState { 15 return ( 16 typeof task === 'object' && 17 task !== null && 18 'type' in task && 19 task.type === 'local_agent' 20 ) 21} 22 23/** 24 * Return the task released back to stub form: retain dropped, messages 25 * cleared, evictAfter set if terminal. Shared by exitTeammateView and 26 * the switch-away path in enterTeammateView. 27 */ 28function release(task: LocalAgentTaskState): LocalAgentTaskState { 29 return { 30 ...task, 31 retain: false, 32 messages: undefined, 33 diskLoaded: false, 34 evictAfter: isTerminalTaskStatus(task.status) 35 ? Date.now() + PANEL_GRACE_MS 36 : undefined, 37 } 38} 39 40/** 41 * Transitions the UI to view a teammate's transcript. 42 * Sets viewingAgentTaskId and, for local_agent, retain: true (blocks eviction, 43 * enables stream-append, triggers disk bootstrap) and clears evictAfter. 44 * If switching from another agent, releases the previous one back to stub. 45 */ 46export function enterTeammateView( 47 taskId: string, 48 setAppState: (updater: (prev: AppState) => AppState) => void, 49): void { 50 logEvent('tengu_transcript_view_enter', {}) 51 setAppState(prev => { 52 const task = prev.tasks[taskId] 53 const prevId = prev.viewingAgentTaskId 54 const prevTask = prevId !== undefined ? prev.tasks[prevId] : undefined 55 const switching = 56 prevId !== undefined && 57 prevId !== taskId && 58 isLocalAgent(prevTask) && 59 prevTask.retain 60 const needsRetain = 61 isLocalAgent(task) && (!task.retain || task.evictAfter !== undefined) 62 const needsView = 63 prev.viewingAgentTaskId !== taskId || 64 prev.viewSelectionMode !== 'viewing-agent' 65 if (!needsRetain && !needsView && !switching) return prev 66 let tasks = prev.tasks 67 if (switching || needsRetain) { 68 tasks = { ...prev.tasks } 69 if (switching) tasks[prevId] = release(prevTask) 70 if (needsRetain) { 71 tasks[taskId] = { ...task, retain: true, evictAfter: undefined } 72 } 73 } 74 return { 75 ...prev, 76 viewingAgentTaskId: taskId, 77 viewSelectionMode: 'viewing-agent', 78 tasks, 79 } 80 }) 81} 82 83/** 84 * Exit teammate transcript view and return to leader's view. 85 * Drops retain and clears messages back to stub form; if terminal, 86 * schedules eviction via evictAfter so the row lingers briefly. 87 */ 88export function exitTeammateView( 89 setAppState: (updater: (prev: AppState) => AppState) => void, 90): void { 91 logEvent('tengu_transcript_view_exit', {}) 92 setAppState(prev => { 93 const id = prev.viewingAgentTaskId 94 const cleared = { 95 ...prev, 96 viewingAgentTaskId: undefined, 97 viewSelectionMode: 'none' as const, 98 } 99 if (id === undefined) { 100 return prev.viewSelectionMode === 'none' ? prev : cleared 101 } 102 const task = prev.tasks[id] 103 if (!isLocalAgent(task) || !task.retain) return cleared 104 return { 105 ...cleared, 106 tasks: { ...prev.tasks, [id]: release(task) }, 107 } 108 }) 109} 110 111/** 112 * Context-sensitive x: running → abort, terminal → dismiss. 113 * Dismiss sets evictAfter=0 so the filter hides immediately. 114 * If viewing the dismissed agent, also exits to leader. 115 */ 116export function stopOrDismissAgent( 117 taskId: string, 118 setAppState: (updater: (prev: AppState) => AppState) => void, 119): void { 120 setAppState(prev => { 121 const task = prev.tasks[taskId] 122 if (!isLocalAgent(task)) return prev 123 if (task.status === 'running') { 124 task.abortController?.abort() 125 return prev 126 } 127 if (task.evictAfter === 0) return prev 128 const viewingThis = prev.viewingAgentTaskId === taskId 129 return { 130 ...prev, 131 tasks: { 132 ...prev.tasks, 133 [taskId]: { ...release(task), evictAfter: 0 }, 134 }, 135 ...(viewingThis && { 136 viewingAgentTaskId: undefined, 137 viewSelectionMode: 'none', 138 }), 139 } 140 }) 141}