sdkEventQueue.ts
utils/sdkEventQueue.ts
No strong subsystem tag
135
Lines
4077
Bytes
4
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 135 lines, 4 detected imports, and 4 detected exports.
Important relationships
Detected exports
SdkEventenqueueSdkEventdrainSdkEventsemitTaskTerminatedSdk
Keywords
queuesystemsubtypeusagesummaryoptsuuidtask_idtool_use_idtask_notification
Detected imports
cryptocrypto../bootstrap/state.js../types/tools.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 type { UUID } from 'crypto'
import { randomUUID } from 'crypto'
import { getIsNonInteractiveSession, getSessionId } from '../bootstrap/state.js'
import type { SdkWorkflowProgress } from '../types/tools.js'
type TaskStartedEvent = {
type: 'system'
subtype: 'task_started'
task_id: string
tool_use_id?: string
description: string
task_type?: string
workflow_name?: string
prompt?: string
}
type TaskProgressEvent = {
type: 'system'
subtype: 'task_progress'
task_id: string
tool_use_id?: string
description: string
usage: {
total_tokens: number
tool_uses: number
duration_ms: number
}
last_tool_name?: string
summary?: string
// Delta batch of workflow state changes. Clients upsert by
// `${type}:${index}` then group by phaseIndex to rebuild the phase tree,
// same fold as collectFromEvents + groupByPhase in PhaseProgress.tsx.
workflow_progress?: SdkWorkflowProgress[]
}
// Emitted when a foreground agent completes without being backgrounded.
// Drained by drainSdkEvents() directly into the output stream — does NOT
// go through the print.ts XML task_notification parser and does NOT trigger
// the LLM loop. Consumers (e.g. VS Code session.ts) use this to remove the
// task from the subagent panel.
type TaskNotificationSdkEvent = {
type: 'system'
subtype: 'task_notification'
task_id: string
tool_use_id?: string
status: 'completed' | 'failed' | 'stopped'
output_file: string
summary: string
usage?: {
total_tokens: number
tool_uses: number
duration_ms: number
}
}
// Mirrors notifySessionStateChanged. The CCR bridge already receives this
// via its own listener; SDK consumers (scmuxd, VS Code) need the same signal
// to know when the main turn's generator is idle vs actively producing.
// The 'idle' transition fires AFTER heldBackResult flushes and the bg-agent
// do-while loop exits — so SDK consumers can trust it as the authoritative
// "turn is over" signal even when result was withheld for background agents.
type SessionStateChangedEvent = {
type: 'system'
subtype: 'session_state_changed'
state: 'idle' | 'running' | 'requires_action'
}
export type SdkEvent =
| TaskStartedEvent
| TaskProgressEvent
| TaskNotificationSdkEvent
| SessionStateChangedEvent
const MAX_QUEUE_SIZE = 1000
const queue: SdkEvent[] = []
export function enqueueSdkEvent(event: SdkEvent): void {
// SDK events are only consumed (drained) in headless/streaming mode.
// In TUI mode they would accumulate up to the cap and never be read.
if (!getIsNonInteractiveSession()) {
return
}
if (queue.length >= MAX_QUEUE_SIZE) {
queue.shift()
}
queue.push(event)
}
export function drainSdkEvents(): Array<
SdkEvent & { uuid: UUID; session_id: string }
> {
if (queue.length === 0) {
return []
}
const events = queue.splice(0)
return events.map(e => ({
...e,
uuid: randomUUID(),
session_id: getSessionId(),
}))
}
/**
* Emit a task_notification SDK event for a task reaching a terminal state.
*
* registerTask() always emits task_started; this is the closing bookend.
* Call this from any exit path that sets a task terminal WITHOUT going
* through enqueuePendingNotification-with-<task-id> (print.ts parses that
* XML into the same SDK event, so paths that do both would double-emit).
* Paths that suppress the XML notification (notified:true pre-set, kill
* paths, abort branches) must call this directly so SDK consumers
* (Scuttle's bg-task dot, VS Code subagent panel) see the task close.
*/
export function emitTaskTerminatedSdk(
taskId: string,
status: 'completed' | 'failed' | 'stopped',
opts?: {
toolUseId?: string
summary?: string
outputFile?: string
usage?: { total_tokens: number; tool_uses: number; duration_ms: number }
},
): void {
enqueueSdkEvent({
type: 'system',
subtype: 'task_notification',
task_id: taskId,
tool_use_id: opts?.toolUseId,
status,
output_file: opts?.outputFile ?? '',
summary: opts?.summary ?? '',
usage: opts?.usage,
})
}