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