π File detail
state/teammateViewHelpers.ts
π§© .tsπ 142 linesπΎ 4,399 bytesπ text
β Back to All Filesπ― Use case
This file lives under βstate/β, which covers central application state slices and reducers/stores. On the API surface it exposes enterTeammateView, exitTeammateView, and stopOrDismissAgent β mainly functions, hooks, or classes. It composes internal code from services, Task, tasks, and AppState (relative imports). What the file header says: Inlined from framework.ts β importing creates a cycle through BackgroundTasksDialog. Keep in sync with PANEL_GRACE_MS there.
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
Inlined from framework.ts β importing creates a cycle through BackgroundTasksDialog. Keep in sync with PANEL_GRACE_MS there.
π€ Exports (heuristic)
enterTeammateViewexitTeammateViewstopOrDismissAgent
π₯οΈ Source preview
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',
}),
}
})
}