알림 시스템 (Notification System)
관련 소스 파일
목적 및 범위 (Purpose and Scope)
알림 시스템은 백그라운드 작업과 부모 세션 간의 통신을 관리하여, 위임된 작업이 완료되었을 때 부모 에이전트가 자동으로 알림을 받을 수 있도록 보장합니다. 이 페이지에서는 알림 큐(notification queue), 부모 세션 프롬프트 주입(prompt injection), 컨텍스트 보존(context preservation) 및 토스트 알림(toast notification) 메커니즘에 대해 설명합니다.
백그라운드 작업의 시작 및 관리에 대한 정보는 Background Manager를 참조하십시오. 작업 실행 및 완료 감지에 대한 자세한 내용은 Task Execution and Polling을 참조하십시오.
개요 (Overview)
백그라운드 작업이 완료되면, 오케스트레이션 에이전트(주로 Sisyphus)가 결과를 검색하고 후속 조치를 취할 수 있도록 시스템이 부모 세션에 알려야 합니다. 알림 시스템은 사용자 인지를 위한 시각적 토스트 알림과 에이전트 인지를 위한 자동 프롬프트 주입이라는 이중 알림 채널을 갖춘 큐 기반 방식을 구현합니다.
알림 흐름도 (Notification Flow Diagram)
flowchart TD
Idle["session.idle 이벤트"]
Poll["폴링이 유휴 상태 감지"]
Todo["checkSessionTodos()"]
Mark["markForNotification()"]
Queue["notifications Map"]
QueueEntry["큐 항목:<br>parentSessionID -> BackgroundTask[]"]
Notify["notifyParentSession()"]
Toast["showToast()<br>(시각적)"]
Prompt["session.prompt()<br>(에이전트)"]
GetMsgDir["getMessageDir()"]
FindPrev["findNearestMessageWithFields()"]
ModelCtx["parentModel / prevMessage.model"]
AgentCtx["prevMessage.agent"]
ClearQueue["clearNotificationsForTask()"]
DeleteTask["tasks.delete(taskId)"]
Todo -.-> Mark
QueueEntry -.-> Notify
Notify -.-> GetMsgDir
ModelCtx -.-> Prompt
AgentCtx -.-> Prompt
Prompt -.-> ClearQueue
subgraph Cleanup ["정리"]
ClearQueue
DeleteTask
ClearQueue -.-> DeleteTask
end
subgraph subGraph3 ["컨텍스트 보존"]
GetMsgDir
FindPrev
ModelCtx
AgentCtx
GetMsgDir -.-> FindPrev
FindPrev -.-> ModelCtx
FindPrev -.-> AgentCtx
end
subgraph subGraph2 ["부모 알림"]
Notify
Toast
Prompt
Notify -.-> Toast
Notify -.-> Prompt
end
subgraph subGraph1 ["알림 큐"]
Mark
Queue
QueueEntry
Mark -.-> Queue
Queue -.-> QueueEntry
end
subgraph subGraph0 ["작업 완료 감지"]
Idle
Poll
Todo
Idle -.->|"미완료 할 일 없음"| Todo
Poll -.-> Todo
end
출처: src/features/background-agent/manager.ts L176-L357
알림 큐 (Notification Queue)
BackgroundManager는 Map<string, BackgroundTask[]> 형태의 알림 큐를 유지합니다. 여기서 키는 부모 세션 ID이고, 값은 알림을 기다리는 완료된 작업들의 배열입니다.
큐 관리 메서드
| 메서드 | 목적 | 매개변수 | 동작 |
|---|---|---|---|
markForNotification() |
완료된 작업을 큐에 추가 | task: BackgroundTask |
부모의 알림 큐에 작업을 추가합니다. |
getPendingNotifications() |
대기 중인 알림 조회 | sessionID: string |
해당 세션에 대해 대기 중인 모든 작업을 반환합니다. |
clearNotifications() |
모든 알림 삭제 | sessionID: string |
해당 세션의 전체 큐를 제거합니다. |
clearNotificationsForTask() |
특정 작업 제거 | taskId: string |
모든 큐에서 해당 작업을 제거합니다 (private). |
출처: src/features/background-agent/manager.ts L259-L282
큐 구조
flowchart TD
Manager["BackgroundManager"]
NotifMap["notifications: Map"]
ParentA["'session-a'"]
QueueA["[task1, task2]"]
ParentB["'session-b'"]
QueueB["[task3]"]
Manager -.-> NotifMap
NotifMap -.-> ParentA
ParentA -.-> QueueA
NotifMap -.-> ParentB
ParentB -.-> QueueB
출처: src/features/background-agent/manager.ts L56-L64
부모 세션 알림 (Parent Session Notification)
백그라운드 작업이 완료되면, notifyParentSession()은 시각적 피드백과 자동화된 에이전트 통신을 결합한 다단계 알림 프로세스를 실행합니다.
알림 메시지 형식
시스템은 부모 세션에 다음과 같은 구조화된 알림 메시지를 주입합니다.
[BACKGROUND TASK COMPLETED] Task "{description}" finished in {duration}. Use background_output with task_id="{id}" to get results.
출처: src/features/background-agent/manager.ts L324
알림 프로세스
sequenceDiagram
participant p1 as 백그라운드 작업
participant p2 as BackgroundManager
participant p3 as 메시지 디렉토리
participant p4 as OpenCode 클라이언트
participant p5 as 부모 세션
participant p6 as 사용자 (TUI)
p1->>p2: 작업 완료
p2->>p2: formatDuration()
p2->>p4: tui.showToast()
p4->>p6: 토스트 표시<br/>(5초 지속)
p2->>p2: setTimeout(200ms)
note over p2: 안정성을 위한 지연
p2->>p3: getMessageDir(parentSessionID)
p3->>p2: 메시지 디렉토리 경로
p2->>p3: findNearestMessageWithFields()
p3->>p2: prevMessage (agent, model)
p2->>p4: session.prompt()
note over p4: agent: prevMessage.agent<br/>model: prevMessage.model<br/>text: 알림 메시지
p4->>p5: 알림 주입
p5->>p5: 에이전트가 알림 읽기<br/>background_output 호출
p2->>p2: clearNotificationsForTask()
p2->>p2: tasks.delete(taskId)
출처: src/features/background-agent/manager.ts L306-L357
소요 시간 형식 (Duration Formatting)
시스템은 작업 소요 시간을 사람이 읽기 쉬운 형식으로 변환합니다.
| 소요 시간 | 형식 예시 |
|---|---|
| 1분 미만 | 45s |
| 1분 - 59분 | 5m 23s |
| 1시간 이상 | 2h 15m 30s |
출처: src/features/background-agent/manager.ts L359-L371
컨텍스트 보존 (Context Preservation)
부모 세션이 올바른 에이전트 및 모델 구성으로 계속 진행될 수 있도록, 알림 시스템은 두 가지 메커니즘을 통해 컨텍스트를 보존합니다.
컨텍스트 소스
- 기본:
parentModel필드 - 부모 메시지로부터 작업 시작 시 캡처됩니다. - 폴백(Fallback): 메시지 디렉토리 조회 -
parentModel을 사용할 수 없는 경우 가장 최근 메시지를 읽습니다.
메시지 디렉토리 확인 (Message Directory Resolution)
flowchart TD
Start["getMessageDir(sessionID)"]
Storage["MESSAGE_STORAGE"]
Direct["직접 경로:<br>MESSAGE_STORAGE/sessionID"]
Nested["중첩 검색:<br>MESSAGE_STORAGE/*/sessionID"]
DirectExists["existsSync()?"]
NestedLoop["디렉토리 순회"]
NestedExists["찾았는가?"]
Found["경로 반환"]
NotFound["null 반환"]
Start -.-> Storage
Storage -.-> Direct
Direct -.-> DirectExists
DirectExists -.->|"예"| Found
DirectExists -.->|"아니오"| Nested
Nested -.-> NestedLoop
NestedLoop -.-> NestedExists
NestedExists -.->|"예"| Found
NestedExists -.->|"아니오"| NotFound
출처: src/features/background-agent/manager.ts L41-L53
src/tools/background-task/tools.ts L11-L23
컨텍스트 주입 로직
알림 프롬프트에는 다음이 포함됩니다.
{
agent: prevMessage?.agent,
model: modelField, // { providerID, modelID }
parts: [{ type: "text", text: message }]
}
이를 통해 부모 세션의 AI 모델이 알림을 처리할 때 일관성을 유지하며, 오케스트레이터를 혼란스럽게 할 수 있는 컨텍스트 전환을 방지합니다.
출처: src/features/background-agent/manager.ts L334-L347
토스트 알림 (Toast Notifications)
시스템은 OpenCode TUI의 토스트 알림 시스템을 통해 시각적 피드백을 제공합니다.
토스트 구성
| 속성 | 값 | 목적 |
|---|---|---|
title |
"Background Task Completed" |
알림 유형 식별 |
message |
Task "{description}" finished in {duration}. |
완료 요약 제공 |
variant |
"success" |
시각적 스타일링 (녹색/긍정적) |
duration |
5000 (ms) |
5초 후 자동 사라짐 |
출처: src/features/background-agent/manager.ts L313-L321
토스트 구현
const tuiClient = this.client as any
if (tuiClient.tui?.showToast) {
tuiClient.tui.showToast({
body: {
title: "Background Task Completed",
message: `Task "${task.description}" finished in ${duration}.`,
variant: "success",
duration: 5000,
},
}).catch(() => {})
}
토스트 호출은 fire-and-forget 방식(오류 무시)으로 처리되어, 알림 실패가 작업 정리 프로세스를 방해하지 않도록 합니다.
출처: src/features/background-agent/manager.ts L312-L322
알림 타이밍 (Notification Timing)
지연 주입 (Delayed Injection)
시스템은 알림 프롬프트를 주입하기 전에 200ms의 지연 시간을 사용합니다.
setTimeout(async () => {
// ... 알림 로직
}, 200)
이 지연은 다음을 보장합니다.
- 완료 감지 후 세션 상태가 안정화됨
- 이전 도구 결과가 완전히 기록됨
- 부모 세션이 새 메시지를 받을 준비가 됨
출처: src/features/background-agent/manager.ts L329
알림 트리거 (Notification Triggers)
알림은 세 가지 소스에서 트리거됩니다.
| 트리거 | 위치 | 조건 |
|---|---|---|
session.idle 이벤트 |
handleEvent() |
이벤트 기반 감지, 할 일(todos) 확인 |
| 폴링 루프 | pollRunningTasks() |
폴링 기반 감지, 할 일 확인 |
| 세션 삭제 | handleEvent() |
작업이 취소됨으로 표시됨, 알림 전송 안 함 |
출처: src/features/background-agent/manager.ts L218-L256
src/features/background-agent/manager.ts L380-L459
큐 정리 (Queue Cleanup)
작업 생명주기 (Task Lifecycle)
stateDiagram-v2
[*] --> Running : "launch()"
Running --> MarkNotification : "완료/오류/취소"
MarkNotification --> Queued : "markForNotification()"
Queued --> Notifying : "notifyParentSession()"
Notifying --> Cleanup : "프롬프트 주입 후"
Cleanup --> ClearFromQueue : "clearNotificationsForTask()"
ClearFromQueue --> DeleteFromTasks : "tasks.delete()"
DeleteFromTasks --> [*] : "tasks.delete()"
Running --> DeleteFromTasks : "세션 삭제됨"
출처: src/features/background-agent/manager.ts L239-L256
src/features/background-agent/manager.ts L348-L356
정리 메서드
clearNotificationsForTask() 메서드는 모든 알림 큐에서 특정 작업을 제거합니다.
private clearNotificationsForTask(taskId: string): void {
for (const [sessionID, tasks] of this.notifications.entries()) {
const filtered = tasks.filter((t) => t.id !== taskId)
if (filtered.length === 0) {
this.notifications.delete(sessionID)
} else {
this.notifications.set(sessionID, filtered)
}
}
}
이를 통해 다음을 보장합니다.
- 모든 부모 큐에서 작업이 제거됨
- 빈 큐는 삭제됨 (메모리 효율성)
- 중복 알림 방지
출처: src/features/background-agent/manager.ts L273-L282
백그라운드 도구와의 통합
background_output 도구
알림 메시지는 부모 에이전트에게 background_output을 사용하도록 명시적으로 지시합니다.
Use background_output with task_id="{id}" to get results.
이 도구는 두 가지 모드로 작동할 수 있습니다.
| 모드 | 매개변수 | 동작 |
|---|---|---|
| 비차단 (Non-blocking) | block=false (기본값) |
즉시 상태를 반환함 |
| 차단 (Blocking) | block=true |
완료될 때까지 폴링함 (드물게 필요함) |
알림이 자동으로 전송되므로, 에이전트는 일반적으로 알림을 받은 직후 결과를 즉시 가져오기 위해 비차단 모드를 사용합니다.
출처: src/tools/background-task/tools.ts L235-L298
알림 메시지 예시
작업이 완료되면 부모 세션은 다음을 수신합니다.
[BACKGROUND TASK COMPLETED] Task "Explore opencode in codebase" finished in 39s. Use background_output with task_id="bg_wzsdt60b" to get results.
그러면 에이전트는 다음을 실행합니다.
background_output({ task_id: "bg_wzsdt60b", block: false })
출처: src/features/background-agent/manager.ts L324
.opencode/background-tasks.json L1-L12
오류 처리 (Error Handling)
프롬프트 주입 실패
알림 중 session.prompt()가 실패하는 경우:
try {
await this.client.session.prompt({ ... })
this.clearNotificationsForTask(taskId)
log("[background-agent] Successfully sent prompt to parent session:", { ... })
} catch (error) {
log("[background-agent] prompt failed:", String(error))
} finally {
this.tasks.delete(taskId)
log("[background-agent] Removed completed task from memory:", taskId)
}
동작:
- 오류는 로그에 기록되지만 throw되지는 않음
- 작업은 여전히 메모리에서 제거됨 (
finally블록에서) - 알림은 큐에 남아 있음 (실패 시 지워지지 않음)
- 사용자는 여전히 토스트 알림을 볼 수 있음
출처: src/features/background-agent/manager.ts L339-L356
컨텍스트 확인 실패
메시지 디렉토리나 이전 메시지를 찾을 수 없는 경우:
const messageDir = getMessageDir(task.parentSessionID)
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null
const modelContext = task.parentModel ?? prevMessage?.model
const modelField = modelContext?.providerID && modelContext?.modelID
? { providerID: modelContext.providerID, modelID: modelContext.modelID }
: undefined
동작:
- 시작 시 캡처된
parentModel을 기본 소스로 사용함 - 메시지 디렉토리 조회를 폴백으로 사용함
- 둘 다 실패하면 명시적인 모델 필드 없이 알림을 보냄
- OpenCode는 세션의 기본 모델을 사용함
출처: src/features/background-agent/manager.ts L331-L337
구현 세부 사항 (Implementation Details)
BackgroundManager 알림 필드
export class BackgroundManager {
private tasks: Map<string, BackgroundTask>
private notifications: Map<string, BackgroundTask[]> // 알림 큐
private client: OpencodeClient
private directory: string
// ...
}
출처: src/features/background-agent/manager.ts L55-L67
BackgroundTask 알림 컨텍스트
export interface BackgroundTask {
id: string
sessionID: string
parentSessionID: string // 알림 대상
parentMessageID: string // 원본 요청 컨텍스트
description: string // 알림에 표시됨
agent: string
status: BackgroundTaskStatus
startedAt: Date
completedAt?: Date
parentModel?: { providerID: string; modelID: string } // 컨텍스트 보존
// ...
}
출처: src/features/background-agent/types.ts L15-L30
세션 상태 추적
알림 시스템은 중복 알림을 방지하기 위해 세션 상태 추적과 연동됩니다.
subagentSessionsSet은 어떤 세션이 백그라운드 작업인지 추적합니다.session.deleted이벤트는 추적에서 제거하고 알림을 방지합니다.- 완료된 작업은 성공적인 알림 전송 후 제거됩니다.