import * as api from '@/api/index.js'
/**
* Background tasks: the server's snapshot plus the live task_update events,
* which are never replayed on (re)connect.
*/
export function createTaskTracking(state) {
const { currentId, backgroundTasks, _terminalTaskIds, toolIndex } = state
// task_update events are delivered live over the WS and are never replayed
// on (re)connect — fetch the server's snapshot to rebuild the backgrounds
// tab. Snapshot entries fill gaps; live entries (from task_update) are kept
// even if the snapshot doesn't know them yet.
async function fetchTasks(id = currentId.value) {
if (!id) {
backgroundTasks.value = {}
return
}
let tasks = []
try {
tasks = (await api.getSessionTasks(id)).tasks ?? []
} catch {
return
}
if (currentId.value !== id) return // user switched meanwhile
const next = {}
for (const t of tasks) {
next[t.task_id] = {
taskId: t.task_id,
tool: t.tool,
status: t.status,
startedAt: t.started_at ?? null,
finishedAt: t.finished_at ?? null,
argsSummary: t.args_summary ?? '',
preview: t.result_preview ?? '',
subagentTokens: t.subagent_tokens ?? null,
}
if (t.status !== 'running') _terminalTaskIds.add(t.task_id)
}
for (const live of Object.values(backgroundTasks.value)) {
next[live.taskId] = live
}
backgroundTasks.value = next
}
// Out-of-band task_update: upsert/remove the background-task chip entry and
// append progress to the card that started the task (spawn/terminal/etc.).
function onTaskUpdate(data) {
const taskId = data.task_id
if (!taskId) return
// Out-of-band events can arrive in the narrow window after this store was
// reset for a new session but before the WS reconnects — ignore updates
// that belong to an older session.
if (data.session_id && currentId.value && data.session_id !== currentId.value) return
if (data.parent_tool_call_id) {
const card = toolIndex.get(data.parent_tool_call_id)
if (card) {
// A repeated 'running' publish must not duplicate the spinner row.
const last = card.steps[card.steps.length - 1]
const sameAsLast =
last?.kind === 'task_update' &&
last.status === data.status &&
last.preview === (data.result_preview ?? '')
if (!sameAsLast) {
card.steps.push({
kind: 'task_update',
taskId,
tool: data.tool,
status: data.status,
preview: data.result_preview ?? '',
})
}
if (data.subagent_tokens != null) card.subagentTokens = data.subagent_tokens
}
}
if (data.status !== 'running') {
_terminalTaskIds.add(taskId)
const prev = backgroundTasks.value[taskId]
if (prev) {
// Keep the entry — the Backgrounds tab shows recently finished tasks;
// _terminalTaskIds is what stops a late 'running' from resurrecting it.
backgroundTasks.value = {
...backgroundTasks.value,
[taskId]: {
...prev,
status: data.status,
preview: data.result_preview ?? prev.preview,
finishedAt: data.finished_at ?? prev.finishedAt ?? null,
subagentTokens: data.subagent_tokens ?? prev.subagentTokens ?? null,
},
}
}
} else if (!_terminalTaskIds.has(taskId) && !backgroundTasks.value[taskId]) {
backgroundTasks.value = {
...backgroundTasks.value,
[taskId]: {
taskId,
tool: data.tool,
status: data.status,
startedAt: data.started_at ?? null,
finishedAt: null,
argsSummary: data.args_summary ?? '',
preview: '',
subagentTokens: null,
},
}
}
}
return { fetchTasks, onTaskUpdate }
}