teammateViewHelpers.ts
state/teammateViewHelpers.ts
No strong subsystem tag
142
Lines
4399
Bytes
3
Exports
4
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with general runtime concerns. It contains 142 lines, 4 detected imports, and 3 detected exports.
Important relationships
Detected exports
enterTeammateViewexitTeammateViewstopOrDismissAgent
Keywords
prevtasktaskstaskidevictafterappstateretainviewingagenttaskidsetappstatevoid
Detected imports
../services/analytics/index.js../Task.js../tasks/LocalAgentTask/LocalAgentTask.js./AppState.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
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',
}),
}
})
}