({
messages,
queryParams,
description,
setAppState,
agentDefinition,
}: {
messages: Message[]
queryParams: Omit<QueryParams, 'messages'>
description: string
setAppState: SetAppState
agentDefinition?: AgentDefinition
})
| 339 | * as a background task. The caller's foreground query continues running normally. |
| 340 | */ |
| 341 | export function startBackgroundSession({ |
| 342 | messages, |
| 343 | queryParams, |
| 344 | description, |
| 345 | setAppState, |
| 346 | agentDefinition, |
| 347 | }: { |
| 348 | messages: Message[] |
| 349 | queryParams: Omit<QueryParams, 'messages'> |
| 350 | description: string |
| 351 | setAppState: SetAppState |
| 352 | agentDefinition?: AgentDefinition |
| 353 | }): string { |
| 354 | const { taskId, abortSignal } = registerMainSessionTask( |
| 355 | description, |
| 356 | setAppState, |
| 357 | agentDefinition, |
| 358 | ) |
| 359 | |
| 360 | // Persist the pre-backgrounding conversation to the task's isolated |
| 361 | // transcript so TaskOutput shows context immediately. Subsequent messages |
| 362 | // are written incrementally below. |
| 363 | void recordSidechainTranscript(messages, taskId).catch(err => |
| 364 | logForDebugging(`bg-session initial transcript write failed: ${err}`), |
| 365 | ) |
| 366 | |
| 367 | // Wrap in agent context so skill invocations scope to this task's agentId |
| 368 | // (not null). This lets clearInvokedSkills(preservedAgentIds) selectively |
| 369 | // preserve this task's skills across /clear. AsyncLocalStorage isolates |
| 370 | // concurrent async chains — this wrapper doesn't affect the foreground. |
| 371 | const agentContext: SubagentContext = { |
| 372 | agentId: taskId, |
| 373 | agentType: 'subagent', |
| 374 | subagentName: 'main-session', |
| 375 | isBuiltIn: true, |
| 376 | } |
| 377 | |
| 378 | void runWithAgentContext(agentContext, async () => { |
| 379 | try { |
| 380 | const bgMessages: Message[] = [...messages] |
| 381 | const recentActivities: ToolActivity[] = [] |
| 382 | let toolCount = 0 |
| 383 | let tokenCount = 0 |
| 384 | let lastRecordedUuid: UUID | null = messages.at(-1)?.uuid ?? null |
| 385 | |
| 386 | for await (const event of query({ |
| 387 | messages: bgMessages, |
| 388 | ...queryParams, |
| 389 | })) { |
| 390 | if (abortSignal.aborted) { |
| 391 | // Aborted mid-stream — completeMainSessionTask won't be reached. |
| 392 | // chat:killAgents path already marked notified + emitted; stopTask path did not. |
| 393 | let alreadyNotified = false |
| 394 | updateTaskState<LocalMainSessionTaskState>( |
| 395 | taskId, |
| 396 | setAppState, |
| 397 | task => { |
| 398 | alreadyNotified = task.notified === true |
no test coverage detected