Documentation
¶
Overview ¶
Event bus implementation
Package events provides event system for sprout UI architecture ¶
Package events provides event system for sprout UI architecture ¶
Package events provides the rate-limited event payload and helper for publishing rate-limit signals to the WebUI.
Package events — shell approval event types and helpers.
Index ¶
- Constants
- func AgentMessageEvent(category, message string, extra map[string]interface{}) map[string]interface{}
- func AllowedPathHitEvent(sessionID, workflow, path, mode string) map[string]interface{}
- func AskUserCancelledEvent(requestID, clientID string) map[string]interface{}
- func AskUserRequestEvent(requestID string, req AskUserRequest, clientID string) map[string]interface{}
- func AutomateBudgetUpdateEvent(sessionID string, spentUSD, budgetUSD float64, fraction float64, iteration int) map[string]interface{}
- func AutomateOutputChunkEvent(sessionID string, offset int, chunk string) map[string]interface{}
- func AutomateSessionEndedEvent(sessionID, workflow, status string, totalCost float64) map[string]interface{}
- func AutomateSessionStartedEvent(sessionID, workflow, kind string) map[string]interface{}
- func BuildShellApprovalUnifiedView(parts []ShellApprovalPartArg) string
- func CompactCompletedEvent(source string, beforeCount, afterCount int, summaryChars int, err error) map[string]interface{}
- func CompactStartedEvent(source string, messageCount, checkpointCount int) map[string]interface{}
- func ContextManagementDiagnosticEvent(currentTokens, maxTokens, nativeMaxTokens int, ...) map[string]interface{}
- func DelegatedTaskOutcomeEvent(model, provider, role, taskType, outcome string, ...) map[string]interface{}
- func EditApprovalRequestEvent(requestID, path, unifiedDiff string, hunks []map[string]interface{}) map[string]interface{}
- func ErrorEvent(message string, err error) map[string]interface{}
- func FileChangedEvent(filePath, action string, content string) map[string]interface{}
- func FileContentChangedEvent(filePath string, modTime int64, size int64) map[string]interface{}
- func InputRequiredEvent(reason, requestID string) map[string]interface{}
- func LanguageGuardReplacementEvent(chatID, replacement, original, reason string) map[string]interface{}
- func MetricsUpdateEvent(totalTokens, contextTokens, maxContextTokens, iteration int, totalCost float64) map[string]interface{}
- func MetricsUpdateEventWithCategory(providerID, model string, ...) map[string]interface{}
- func OOMWatchdogAlertEvent(nodeCount int, totalRSSBytes uint64, thresholdNodeCount int, ...) map[string]interface{}
- func PasswordRequestEvent(requestID, command, prompt string) map[string]interface{}
- func ProviderNoCredentialEvent(providerID, message string) map[string]interface{}
- func QueryCompletedEvent(query, response string, tokensUsed int, cost float64, duration time.Duration) map[string]interface{}
- func QueryProgressEvent(message string, iteration int, tokensUsed int) map[string]interface{}
- func QueryStartedEvent(query, provider, model string) map[string]interface{}
- func QueryStartedEventWithDisplay(query, display, source, provider, model string) map[string]interface{}
- func SecurityApprovalRequestEvent(requestID, toolName, riskLevel, reasoning string, extras map[string]string) map[string]interface{}
- func SecurityPromptRequestEvent(requestID, prompt string, defaultResponse bool, extras map[string]string) map[string]interface{}
- func SecurityPromptResponseEvent(requestID, response bool) map[string]interface{}
- func ShellApprovalRequestEvent(requestID, command string, parts []ShellApprovalPartArg, unifiedView string, ...) map[string]interface{}
- func StreamChunkEvent(chunk string, contentType string) map[string]interface{}
- func SubagentActivityEvent(toolCallID, toolName, phase, message string, details map[string]interface{}) map[string]interface{}
- func SubagentClarificationRequestedEvent(subagentID, requestID, question string) map[string]interface{}
- func SubagentClarificationRespondedEvent(subagentID, requestID, response string) map[string]interface{}
- func TodoUpdateEvent(todos []map[string]interface{}) map[string]interface{}
- func ToolEndEvent(toolCallID, toolName, status, result, errorMessage string, ...) map[string]interface{}
- func ToolStartEvent(toolName, toolCallID, arguments, displayName, persona string, isSubagent bool, ...) map[string]interface{}
- func TruncateEventString(s string, limit int) string
- func ValidationEvent(filePath string, diagnostics []map[string]interface{}) map[string]interface{}
- func WorkspaceChangedEvent(daemonRoot, workspaceRoot, previousWorkspaceRoot string) map[string]interface{}
- func WorkspacePatchEvent(filePath, content, action string, seqNum int64, ...) map[string]interface{}
- type AskUserRequest
- type AskUserRequestOption
- type EventBus
- type PatchConflictInfo
- type ProgressCompleteData
- type ProgressMilestoneData
- type ProgressQuestionData
- type ProgressVerificationCheck
- type ProgressVerificationData
- type PublishHook
- type RateLimitedEvent
- type ShellApprovalPart
- type ShellApprovalPartArg
- type ShellApprovalRequestPayload
- type ShellApprovalResponsePayload
- type UIEvent
Constants ¶
const ( EventTypeQueryStarted = "query_started" EventTypeQueryProgress = "query_progress" EventTypeQueryCompleted = "query_completed" // EventTypeSteerDelivered marks the moment a steer message sent during a // run reaches the model: text streamed before it answers the earlier // input, text after it may answer the steer. EventTypeSteerDelivered = "steer_delivered" EventTypeError = "error" EventTypeToolStart = "tool_start" EventTypeToolEnd = "tool_end" EventTypeSubagentActivity = "subagent_activity" // EventTypeDelegatedTaskOutcome is published when a delegated (subagent) // task completes, recording the model that produced its final result, // the task type, the verification outcome (verified/escalated/failed), // and the tokens and cost. A host aggregates these events to see the // share of work a cheaper worker model handled and the credits saved. // No prompt text is carried. EventTypeDelegatedTaskOutcome = "delegated_task_outcome" EventTypeTodoUpdate = "todo_update" EventTypeFileChanged = "file_changed" EventTypeWorkspacePatch = "workspace_patch" EventTypeFileContentChanged = "file_content_changed" EventTypeStreamChunk = "stream_chunk" EventTypeMetricsUpdate = "metrics_update" EventTypeValidation = "validation" EventTypeSecurityApprovalRequest = "security_approval_request" EventTypeSecurityPromptRequest = "security_prompt_request" EventTypeAskUserRequest = "ask_user_request" // EventTypeEditApprovalRequest (SP-072-3) is published when the // per-hunk diff approval gate routes a proposed file edit through the // WebUI for interactive review. The payload carries the request ID, // file path, and the diff hunks with per-line change type so the // frontend can render a color-coded review panel with per-hunk // accept/reject toggles. EventTypeEditApprovalRequest = "edit_approval_request" // EventTypeShellApprovalRequest (SP-093-3) is published when a shell // command needs per-part approval. The payload carries the request ID, // the command, the split parts (with kind/semantic/risk), a unified-view // string, and the overall risk level. The WebUI renders a per-part // approval panel and POSTs back to /api/shell-approvals/{id}/decision. EventTypeShellApprovalRequest = "shell_approval_request" // EventTypePasswordRequest (SP-089-3) is published when a shell command // needs a password from the user (sudo, passwd, ssh-keygen passphrase, // etc.). The payload carries the request ID, the command, and the prompt // text detected on the child's stdout. The browser renders a password // input and POSTs the response to /api/password/{requestID}/respond. EventTypePasswordRequest = "password_request" // EventTypeInputRequired is published when the agent is blocked waiting // for human input — a security approval, an ask_user prompt, or any // other blocking interaction. This is a higher-level signal than the // specific security_approval_request / security_prompt_request / ask_user_request // events: it lets notification subscribers (CLI bell, browser notification) // listen to a single "the agent needs you" signal. EventTypeInputRequired = "input_required" EventTypeAgentMessage = "agent_message" // EventTypeProviderNoCredential is published when a provider change // would activate a provider that requires an API key but doesn't // have one configured. The frontend surfaces it as a sticky toast // pointing at Settings → Credentials, distinct from generic warning // messages that get inlined into the active assistant bubble. EventTypeProviderNoCredential = "provider_no_credential" EventTypeWorkspaceChanged = "workspace_changed" EventTypeSessionTerminated = "session_terminated" // EventTypeWorkspaceStopping warns connected clients that the workspace // is about to stop (idle timeout, runtime cap, drain). Payload: // {reason, at, deadline_seconds}. EventTypeWorkspaceStopping = "workspace_stopping" // EventTypeSessionChanged signals that a chat session's metadata // (name, pin state, active state) changed and tabs viewing that chat // should reconcile. SP-034-3e. EventTypeSessionChanged = "session_changed" // EventTypeDelegateClarificationRequested is published when a delegate // agent requests clarification from its parent agent. EventTypeDelegateClarificationRequested = "delegate_clarification_requested" // EventTypeDelegateClarificationResponded is published when a parent // agent responds to a delegate's clarification request. EventTypeDelegateClarificationResponded = "delegate_clarification_responded" // EventTypeCompactStarted fires immediately before a compaction // operation begins, whether triggered manually by /compact or // automatically by seed's structural compaction / context-limit // recovery. The payload's `source` field distinguishes the path. EventTypeCompactStarted = "compact_started" // EventTypeCompactCompleted fires after the compaction finishes, // successful or not. Subscribers (e.g. the auto-transcript snapshot // capture) use this to record the post-compact state. EventTypeCompactCompleted = "compact_completed" // EventTypeContextManagementDiagnostic (SP-066 Phase 1) reports the // effective context budget at each iteration so we can verify // substitution does the heavy lifting and the LLM fall-through // stays near zero. EventTypeContextManagementDiagnostic = "context_management_diagnostic" // EventTypeCommandOutput (SP-114 Phase 2c) is emitted for every chunk // of stdout captured from a safe slash command executed via // POST /api/command/execute. The chat session's WebSocket subscribers // fan out the chunk so the WebUI can stream the output in real time. // The HTTP response still returns the aggregated output for // non-WebSocket callers (backwards-compatible). EventTypeCommandOutput = "command_output" // EventTypeCommandOutputDropped (SP-114 Phase 2c) is emitted when // the bounded backpressure ring overflows and one or more chunks // had to be dropped. The payload's `dropped_bytes` field reports // how many bytes of command output were discarded since the last // warning. WebUI consumers should display a "some output was // dropped" indicator. EventTypeCommandOutputDropped = "command_output_dropped" // SP-127 Phase 2.7: AllowedPathHit is emitted when a file operation // lands under a session-allowlisted folder (workflow-declared allowed_paths // OR user clicked "Allow folder this session"). Distinct from the base // "allowed" action in the JSONL audit log — this discriminated event // lets the WebUI automations panel count per-run folder grants. EventTypeAllowedPathHit = "allowed_path_hit" // SP-065 Phase 2: Automate session lifecycle events EventTypeAutomateSessionStarted = "automate.session_started" EventTypeAutomateBudgetUpdate = "automate.budget_update" EventTypeAutomateOutputChunk = "automate.output_chunk" EventTypeAutomateSessionEnded = "automate.session_ended" // EventTypeSSHTunnelStatus signals that an SSH workspace tunnel has // changed state — disconnected, reconnecting, or reconnected. Clients // use this to show a banner or retry failed requests instead of // surfacing raw 502 errors during the reconnect window. EventTypeSSHTunnelStatus = "ssh_tunnel_status" // EventTypeWorkspaceConflict (SP-046-3) is published when a container // patch conflicts with unsynced browser edits. The container writes its // version as <path>.theirs instead of overwriting. The payload carries // path, theirs_path, hash_container, hash_browser, and modified_at. EventTypeWorkspaceConflict = "workspace.conflict_detected" // EventTypeWorkspaceHeartbeatLost (SP-046-4) is published when a session's // heartbeat has been missed for >60s, indicating the browser tab may have // been closed or the connection lost. The container will terminate the // running job after this event. The payload carries session_id and // last_heartbeat (time.RFC3339). EventTypeWorkspaceHeartbeatLost = "workspace.heartbeat_lost" // EventTypeWorkspaceSessionMoved (SP-046-5) is published when a user // takes over a session on a new device, causing the previous device's // WebSocket to be closed. The payload carries session_id and new_device_id. // The displaced browser surfaces "This session moved to another device." EventTypeWorkspaceSessionMoved = "workspace.session_moved" // EventTypeRateLimited is published when a tool or API call hits a // rate-limit response from the provider. The payload is a // *RateLimitedEvent. WebUI consumes this to show "rate-limited, // retrying…" and gate the input. EventTypeRateLimited = "rate_limited" // EventTypeOOMWatchdogAlert is published by the OOM watchdog when node // process count or total RSS exceeds configured thresholds. The payload // carries the current counts, thresholds, and which threshold(s) triggered. EventTypeOOMWatchdogAlert = "oom_watchdog_alert" // EventTypeLanguageGuardReplacement is // published when a streamed reply that was RELEASED by the streaming // hold-back (its start passed the language check) is re-checked at // completion and found to have switched language mid-stream. The reply // was already streamed to the client and cannot be un-streamed, so the // client is told to REPLACE the already-streamed assistant message. The // payload carries chat_id, the replacement notice, the original (full // switched content, for "view original"), and the reason. EventTypeLanguageGuardReplacement = "language_guard_replacement" // Progress events: structured run-progress signals // emitted by the runtime (NOT parsed from model text). Each payload // carries the stable correlation IDs (run, plan revision, scope // item) so consumers can de-duplicate and correlate. EventTypeProgressMilestone = "progress_milestone" EventTypeProgressQuestion = "progress_question" EventTypeProgressVerification = "progress_verification" EventTypeProgressComplete = "progress_complete" // EventTypePreviewPortRegistered is // published by the register_preview_port tool after it successfully // registers a preview port with the platform. In hosted workspaces the // agent runs in-process inside the webui server, so the webui server // subscribes to this on the shared event bus and records the returned // preview URL as the active hosted preview — the preview pane embeds // it instead of only printing it to the model. The payload carries // preview_url, port, and label. // // This is an in-process signal (agent -> webui server), not a // WebSocket-forwarded event, so it is intentionally absent from the // WS outbound registry (websocket_outbound_registry.go). EventTypePreviewPortRegistered = "preview_port_registered" )
Common event types
const ( // MilestonePhaseStarted marks a scope item that just began. MilestonePhaseStarted = "started" // MilestonePhaseFinished marks a scope item that just completed. MilestonePhaseFinished = "finished" )
Milestone phase values for ProgressMilestoneData.Phase ("a plan scope item started or finished").
const MaxToolEventArgsLength = 8192
ToolStartEvent creates a tool start event with rich metadata MaxToolEventArgsLength bounds the arguments string shipped in tool_start events. write_file / edit_file arguments embed the entire file payload, which otherwise fans out to every connected tab (and sits in the reattach replay buffer) only to be re-JSON.parsed and discarded by the UI. The head of the string is kept so leading keys like {"path": "..."} survive for consumers that extract the target path.
Variables ¶
This section is empty.
Functions ¶
func AgentMessageEvent ¶
func AgentMessageEvent(category, message string, extra map[string]interface{}) map[string]interface{}
AgentMessageEvent creates an agent system message event. category: "info", "warning", "error", "tool_log", "thought"
func AllowedPathHitEvent ¶ added in v0.17.7
AllowedPathHitEvent creates an allowed_path_hit event payload. SP-127 Phase 2.7: emitted when a file operation lands under a session-allowlisted folder so the WebUI automations panel can count per-run folder grants.
func AskUserCancelledEvent ¶ added in v0.17.5
AskUserCancelledEvent creates a payload for cancelling an in-flight ask_user request. The frontend uses the status field to dismiss the dialog without losing context — the same status pattern as "responded".
func AskUserRequestEvent ¶
func AskUserRequestEvent(requestID string, req AskUserRequest, clientID string) map[string]interface{}
AskUserRequestEvent creates an ask_user request event for the webui. Accepts any struct whose JSON shape matches AskUserRequest (the agent_tools package supplies one). Falls through fields onto the flat event payload so existing frontend consumers that only read "question" continue to work.
func AutomateBudgetUpdateEvent ¶ added in v0.16.4
func AutomateBudgetUpdateEvent(sessionID string, spentUSD, budgetUSD float64, fraction float64, iteration int) map[string]interface{}
AutomateBudgetUpdateEvent creates a budget_update event payload.
func AutomateOutputChunkEvent ¶ added in v0.16.4
AutomateOutputChunkEvent creates an output_chunk event payload. Note: we send chunk_len instead of the full chunk to avoid bloating WS frames.
func AutomateSessionEndedEvent ¶ added in v0.16.4
func AutomateSessionEndedEvent(sessionID, workflow, status string, totalCost float64) map[string]interface{}
AutomateSessionEndedEvent creates a session_ended event payload.
func AutomateSessionStartedEvent ¶ added in v0.16.4
AutomateSessionStartedEvent creates a session_started event payload.
func BuildShellApprovalUnifiedView ¶ added in v0.16.19
func BuildShellApprovalUnifiedView(parts []ShellApprovalPartArg) string
BuildShellApprovalUnifiedView creates a human-readable unified view string from the shell parts, suitable for display in the approval panel.
func CompactCompletedEvent ¶ added in v0.16.4
func CompactCompletedEvent(source string, beforeCount, afterCount int, summaryChars int, err error) map[string]interface{}
CompactCompletedEvent creates the payload for a compact_completed event. On success, err should be nil and after/summary fields describe the new state. On failure, err carries the reason and counts reflect the unchanged pre-compact totals.
func CompactStartedEvent ¶ added in v0.16.4
Helper functions for creating specific event types CompactStartedEvent creates the payload for a compact_started event. source is one of "manual" (slash command) or "auto_llm_summary" (seed structural compaction / context-limit recovery). messageCount and checkpointCount capture the pre-compact state for diagnostics.
func ContextManagementDiagnosticEvent ¶ added in v0.16.4
func ContextManagementDiagnosticEvent(currentTokens, maxTokens, nativeMaxTokens int, triggerFraction, reservedResponse, reservedThinking, reservedToolIO float64, iteration, messageCount int, cachedTokens, promptTokens, cacheWriteTokens int) map[string]interface{}
ContextManagementDiagnosticEvent (SP-066 Phase 1, SP-126) reports the model-aware context-budget math at a single iteration. Subscribers (WebUI metrics panel, telemetry pipelines) use it to verify substitution is doing the heavy lifting and the LLM fall-through stays approximately never.
Fields:
- current_tokens: tokenizer-estimated size of the prompt going to the model.
- max_tokens: the EFFECTIVE max — the smaller of the model's native window and the user's MaxContextTokens cap (SP-126). This is the value seed's budget math operates against. Renamed semantically from the SP-066 "hard context-window limit" wording because SP-126 makes the cap a first-class concept; pre-SP-126 the two were identical (no cap).
- native_max_tokens: the model's UNCAPPED native window. Equal to max_tokens when no user cap is set; larger than max_tokens when a cap is active. Lets subscribers render "X / 300K of 1M tokens" (effective vs native) distinctly in the metrics panel.
- effective_max: max_tokens minus reservation budget; substitution triggers when current_tokens exceeds trigger_fraction × max_tokens.
- trigger_fraction: share of max_tokens at which seed triggers compaction (1 − total_reserved_fraction).
- reserved_response / reserved_thinking / reserved_tool_io: the three reservation slices as fractions of max_tokens.
- iteration: current iteration number from seed's OnIteration callback.
- message_count: messages in the prepared prompt list.
- cached_tokens: cumulative prompt tokens served from the provider's prompt cache so far this session.
- prompt_tokens: cumulative prompt tokens charged so far this session.
- cache_write_tokens: cumulative tokens written to the provider's cache (Anthropic cache_create_input_tokens). May be 0 if not tracked.
- cache_hit_rate: cached_tokens / prompt_tokens, or 0 when prompt_tokens is 0. Lets the UI render cache effectiveness at a glance.
func DelegatedTaskOutcomeEvent ¶ added in v0.32.1
func DelegatedTaskOutcomeEvent(model, provider, role, taskType, outcome string, tokens, promptTokens, completionTokens int, cost float64) map[string]interface{}
DelegatedTaskOutcomeEvent creates the delegated_task_outcome event payload: the model that produced a delegated task's final result, its provider and role, the task type, the verification outcome, and the tokens and cost. A host aggregates these to see the share of work a cheaper worker model handled and the credits saved. No prompt text is carried.
func EditApprovalRequestEvent ¶ added in v0.16.17
func EditApprovalRequestEvent(requestID, path, unifiedDiff string, hunks []map[string]interface{}) map[string]interface{}
EditApprovalRequestEvent (SP-072-3) creates an edit_approval_request event payload for the per-hunk diff approval gate. requestID uniquely identifies the approval so the WebUI can POST a decision back to /api/edits/{requestID}/decision. path is the file being edited. hunks is a JSON-serializable representation of each diff hunk with its line-level change type (context/add/remove). unifiedDiff is the raw unified-diff string for display.
func ErrorEvent ¶
ErrorEvent creates an error event
func FileChangedEvent ¶
FileChangedEvent creates a file changed event.
The full file content is deliberately NOT transmitted. No consumer reads it — the WebUI's handler only uses file_path/action, and the editor refetches a file's bytes on demand (and gets disk-change notifications via the lean FileContentChangedEvent). Shipping whole-file content here made each event large, so a burst (bulk shell edits, many writes) filled the per-subscriber channel and the replay ring buffer fast — dropping file_changed events and spamming "EventBus Dropped file_changed event" logs. The `content` arg is retained for call-site compatibility but only its length is surfaced.
func FileContentChangedEvent ¶
FileContentChangedEvent creates an event indicating a file's content on disk has changed while it was open in the editor
func InputRequiredEvent ¶ added in v0.16.12
InputRequiredEvent creates an input_required event payload. reason is a human-readable description of why input is needed (e.g., "security_approval", "ask_user", "blocking_prompt"). requestID optionally links to the specific request event.
func LanguageGuardReplacementEvent ¶ added in v0.23.0
func LanguageGuardReplacementEvent(chatID, replacement, original, reason string) map[string]interface{}
LanguageGuardReplacementEvent creates a language_guard_replacement event payload. It is published when a streamed reply that was RELEASED by the streaming hold-back (its start passed the language check) is re-checked at completion and found to have switched language mid-stream. The reply was already streamed to the client and cannot be un-streamed, so the client is told to replace the already-streamed assistant message: `replacement` is what it shows (the localized notice), `original` is the full switched content (for "view original"), and `reason` names the trigger (e.g. "mid_stream_switch").
func MetricsUpdateEvent ¶
func MetricsUpdateEvent(totalTokens, contextTokens, maxContextTokens, iteration int, totalCost float64) map[string]interface{}
MetricsUpdateEvent creates a metrics update event
func MetricsUpdateEventWithCategory ¶ added in v0.16.19
func MetricsUpdateEventWithCategory(providerID, model string, totalTokens, contextTokens, maxContextTokens, iteration int, totalCost float64, errorCategory string) map[string]interface{}
MetricsUpdateEventWithCategory is the SP-094-6 variant that includes the most-recent error category label so the cost/status footer can render "rate-limited, retrying…" distinct from generic provider errors. The default MetricsUpdateEvent still exists for callers that don't have an error context.
provider and model are carried on every metrics payload (the footer and the sidebar log read them; without the keys a retry-time update renders as "Model: ? | Provider: ?" even though the agent knows both).
func OOMWatchdogAlertEvent ¶ added in v0.16.19
func OOMWatchdogAlertEvent(nodeCount int, totalRSSBytes uint64, thresholdNodeCount int, thresholdRSSBytes uint64, triggerReason string) map[string]interface{}
OOMWatchdogAlertEvent creates an oom_watchdog_alert event payload.
func PasswordRequestEvent ¶ added in v0.16.18
PasswordRequestEvent (SP-089-3) creates a password_request event payload. requestID uniquely identifies the request so the WebUI can POST a response to /api/password/{requestID}/respond. command is the shell command that triggered the prompt. prompt is the raw prompt text detected on the child's stdout/stderr (e.g., "[sudo] password for user:").
func ProviderNoCredentialEvent ¶
ProviderNoCredentialEvent creates an event signalling that the newly active provider requires an API key but doesn't have one configured. The frontend uses providerID to drive a toast that opens Settings → Credentials scoped to this provider.
func QueryCompletedEvent ¶
func QueryCompletedEvent(query, response string, tokensUsed int, cost float64, duration time.Duration) map[string]interface{}
QueryCompletedEvent creates a query completed event
func QueryProgressEvent ¶
QueryProgressEvent creates a query progress event
func QueryStartedEvent ¶
Helper functions for creating specific event types QueryStartedEvent creates a query started event
func QueryStartedEventWithDisplay ¶ added in v0.17.24
func QueryStartedEventWithDisplay(query, display, source, provider, model string) map[string]interface{}
QueryStartedEventWithDisplay creates a query started event with a separate user-facing bubble text. display is what the WebUI renders in the chat; query is the raw prompt sent to the model. Empty display falls back to query. source names the caller (agent package QuerySource* constants); "auto-resume" turns render as wakeup bubbles rather than user messages.
func SecurityApprovalRequestEvent ¶
func SecurityApprovalRequestEvent(requestID, toolName, riskLevel, reasoning string, extras map[string]string) map[string]interface{}
SecurityApprovalRequestEvent creates a security approval request event for the webui
func SecurityPromptRequestEvent ¶
func SecurityPromptRequestEvent(requestID, prompt string, defaultResponse bool, extras map[string]string) map[string]interface{}
SecurityPromptRequestEvent creates a security prompt request event for the webui
func SecurityPromptResponseEvent ¶
SecurityPromptResponseEvent creates a security prompt response event
func ShellApprovalRequestEvent ¶ added in v0.16.19
func ShellApprovalRequestEvent(requestID, command string, parts []ShellApprovalPartArg, unifiedView string, risk string) map[string]interface{}
ShellApprovalRequestEvent creates a shell_approval_request event payload. It mirrors the EditApprovalRequestEvent pattern: takes the agent-side data (passed as plain structs to avoid import cycles) and returns a flat map[string]interface{} for EventBus.Publish().
func StreamChunkEvent ¶
StreamChunkEvent creates a stream chunk event with content type
func SubagentActivityEvent ¶
func SubagentActivityEvent(toolCallID, toolName, phase, message string, details map[string]interface{}) map[string]interface{}
SubagentActivityEvent creates a structured subagent activity event. phase is typically "spawn", "output", or "complete".
func SubagentClarificationRequestedEvent ¶ added in v0.16.12
func SubagentClarificationRequestedEvent(subagentID, requestID, question string) map[string]interface{}
SubagentClarificationRequestedEvent creates a delegate_clarification_requested event payload.
func SubagentClarificationRespondedEvent ¶ added in v0.16.12
func SubagentClarificationRespondedEvent(subagentID, requestID, response string) map[string]interface{}
SubagentClarificationRespondedEvent creates a delegate_clarification_responded event payload.
func TodoUpdateEvent ¶
TodoUpdateEvent creates a todo update event
func ToolEndEvent ¶
func ToolEndEvent(toolCallID, toolName, status, result, errorMessage string, duration time.Duration) map[string]interface{}
ToolEndEvent creates a tool end event with result and status
func ToolStartEvent ¶
func TruncateEventString ¶ added in v0.17.25
TruncateEventString returns s unchanged when within limit, otherwise the first limit bytes plus a truncation marker.
func ValidationEvent ¶
ValidationEvent creates a validation event
func WorkspaceChangedEvent ¶
func WorkspaceChangedEvent(daemonRoot, workspaceRoot, previousWorkspaceRoot string) map[string]interface{}
WorkspaceChangedEvent creates a workspace changed event
func WorkspacePatchEvent ¶
func WorkspacePatchEvent(filePath, content, action string, seqNum int64, conflictInfo ...PatchConflictInfo) map[string]interface{}
WorkspacePatchEvent creates a workspace_patch event payload for real-time WorkspacePatchEvent creates a workspace_patch event payload for real-time file change notification from the agent to the browser.
The payload intentionally carries NO file content. The only in-tree consumer (the WebUI's handleWorkspacePatch) logs path/action/seq, and shipping whole files on every write/edit flooded every connected tab's main thread with multi-hundred-KB JSON.parse work. Consumers needing content must fetch the file via /api/files. size is computed from content before it is dropped so the UI can still show change magnitude.
Types ¶
type AskUserRequest ¶ added in v0.16.4
type AskUserRequest struct {
Question string `json:"question"`
Header string `json:"header,omitempty"`
Options []AskUserRequestOption `json:"options,omitempty"`
MultiSelect bool `json:"multi_select,omitempty"`
Default string `json:"default,omitempty"`
// Sensitive marks a credential request: the WebUI renders a masked
// input and the backend diverts the response to the credential store
// (the dialog's value must never be echoed into the conversation).
Sensitive bool `json:"sensitive,omitempty"`
// CredentialKey is where the response will be stored (e.g.
// "mcp/figma/FIGMA_TOKEN"). Not a secret; shown so the user knows
// where the value lands.
CredentialKey string `json:"credential_key,omitempty"`
}
AskUserRequest mirrors agent_tools.AskUserRequest in shape; declared here to avoid an import cycle (events is a leaf package). The event payload carries these fields verbatim so the WebUI can render options, header, and the multi-select / default affordances.
type AskUserRequestOption ¶ added in v0.16.4
type AskUserRequestOption struct {
Label string `json:"label"`
Value string `json:"value,omitempty"`
Description string `json:"description,omitempty"`
}
AskUserRequestOption is a single selectable choice in an ask_user prompt.
type EventBus ¶
type EventBus struct {
// contains filtered or unexported fields
}
EventBus manages event distribution between CLI and Web UI.
Delivery pipeline (SP-128): Publish -> sharedDeliveryQueue -> dispatcher goroutine -> per-subscriber inbox -> per-subscriber worker -> receive channel.
Why a queue + workers instead of the historical goroutine-per-publish fan-out: a streaming chat produces hundreds of token-level stream_chunk events per second, and each publish was spawning one goroutine per subscriber. On macOS the scheduler pressure from that churn interacts badly with the BSD-derived kernel: the WS write goroutine ends up parked in WriteMessage waiting on a full kernel send buffer while Publish keeps creating goroutines that can't be scheduled. The model is fine; the output pipeline stalls. Coalescing adjacent stream_chunk events at the dispatcher (collapsing token bursts into one delivery per worker) and giving each subscriber a persistent worker removes the goroutine creation rate from the hot path entirely.
Subscribers map retains the historical `map[string]chan UIEvent` shape so tests and any downstream code that inspects it see the same channel they always have. The worker plumbing (inbox + done) lives in a parallel `workers` map keyed by the same name.
func (*EventBus) Publish ¶
Publish broadcasts an event to all subscribers.
Publish itself does NO goroutine creation: it queues one eventDelivery onto the shared delivery queue (non-blocking for non-critical, brief bounded retry for critical) and returns. All fan-out, coalescing, and per-subscriber forwarding happens in the persistent dispatcher + per-subscriber workers started at bus creation and Subscribe time.
This eliminates the goroutine creation rate that caused the historical macOS streaming-freeze: hundreds of token-level chunks/sec no longer spawn hundreds of goroutines/sec.
func (*EventBus) SetPublishHook ¶ added in v0.32.0
func (eb *EventBus) SetPublishHook(hook PublishHook)
SetPublishHook installs (or, with nil, removes) the bus's publish hook.
func (*EventBus) Subscribe ¶
Subscribe adds a new subscriber to the event bus and returns its receive channel. Each Subscribe spawns a dedicated worker goroutine that reads from a private inbox fed by the dispatcher.
func (*EventBus) SubscriberCount ¶ added in v0.25.0
SubscriberCount reports the number of active subscribers. Test/diagnostic helper for asserting that a stream handler subscribes and, on disconnect, unsubscribes (no leaked subscriber/worker).
func (*EventBus) Unsubscribe ¶
Unsubscribe removes a subscriber. The worker is signalled first so it can drain its inbox and close the receive channel itself — that avoids the close-during-send race that the historical direct-close path could hit when a publish was in flight.
Double-Unsubscribe is a no-op. Closing w.done twice would panic, and two concurrent Unsubscribe callers race the channel close (one wins, one panics). We guard with sync.Once and re-check the map.
type PatchConflictInfo ¶
PatchConflictInfo holds optional conflict metadata for a workspace_patch event.
type ProgressCompleteData ¶ added in v0.23.0
type ProgressCompleteData struct {
RunID string `json:"run_id"`
PlanRevision int `json:"plan_revision"`
// Verified is true only when a passing verification result exists.
Verified bool `json:"verified,omitempty"`
// Verification is the final verification result; nil when verification
// is disabled or was not run.
Verification *ProgressVerificationData `json:"verification,omitempty"`
// NotVerifiedReason explains why the run is not verified (empty when
// verification is disabled).
NotVerifiedReason string `json:"not_verified_reason,omitempty"`
}
ProgressCompleteData is the payload for progress_complete: the run finished. A run is "verified" only when a passing verification result exists. NotVerifiedReason is set only when verification is enabled and the turn was not verified (e.g. "no code changes this turn"); when verification is disabled (the CLI default) both Verification and NotVerifiedReason are absent and the payload carries just run_id, so the default UI is unchanged.
type ProgressMilestoneData ¶ added in v0.23.0
type ProgressMilestoneData struct {
// RunID is the stable run identifier that correlates every event of
// one run.
RunID string `json:"run_id"`
// PlanRevision is the revision of the plan the scope item
// belongs to (0 when the run has no active plan).
PlanRevision int `json:"plan_revision"`
// ScopeID is the id of the scope item this milestone reports
// (plancontract.ScopeItem.ID); empty when there is no plan scope.
ScopeID string `json:"scope_id,omitempty"`
// ScopeTitle is the scope item's title, for display.
ScopeTitle string `json:"scope_title,omitempty"`
// Phase is the milestone phase: MilestonePhaseStarted or
// MilestonePhaseFinished.
Phase string `json:"phase"`
// FilesTouched is how many files the scope item changed.
FilesTouched int `json:"files_touched,omitempty"`
// ElapsedMs is the scope item's elapsed wall time in milliseconds
// (wire-friendly; consumers render it as a duration).
ElapsedMs int64 `json:"elapsed_ms"`
}
ProgressMilestoneData is the payload for progress_milestone: a plan scope item started or finished, with the files-touched count and the scope item's elapsed wall time.
type ProgressQuestionData ¶ added in v0.23.0
type ProgressQuestionData struct {
RunID string `json:"run_id"`
PlanRevision int `json:"plan_revision"`
// ScopeID is the scope item the question belongs to (empty when the
// question is not scoped to one).
ScopeID string `json:"scope_id,omitempty"`
// Question is the decision being requested.
Question string `json:"question"`
// Header is a short categorizing label rendered above the question.
Header string `json:"header,omitempty"`
// Options are the selectable choices, reusing the ask_user option
// shape; nil for freeform questions.
Options []AskUserRequestOption `json:"options,omitempty"`
// WhyItMatters explains why the decision matters.
WhyItMatters string `json:"why_it_matters,omitempty"`
}
ProgressQuestionData is the payload for progress_question: the agent needs a decision. It carries the question, the options if any, and why the decision matters, complementing ask_user_request with plan context (which scope item and plan revision it belongs to).
type ProgressVerificationCheck ¶ added in v0.23.0
type ProgressVerificationCheck struct {
// Kind is the plancontract check kind ("build", "test", ...).
Kind string `json:"kind"`
// Items are the ids of the plan acceptance items this check covers.
Items []string `json:"items,omitempty"`
// Command is the trusted command that ran ("" when skipped).
Command string `json:"command,omitempty"`
// Skipped marks a check that did not run (no trusted command, or the
// run was cancelled). Skipped checks are listed, never hidden.
Skipped bool `json:"skipped,omitempty"`
// Passed reports whether the check passed (false for a skipped check).
Passed bool `json:"passed,omitempty"`
// Reason explains a skipped or abnormal check (timeout, cancellation).
// Empty for a clean pass or a normal non-zero exit (the excerpt is the
// evidence there).
Reason string `json:"reason,omitempty"`
// Excerpt is a bounded excerpt of the check's output (evidence).
Excerpt string `json:"excerpt,omitempty"`
}
ProgressVerificationCheck is the compact evidence a single verification check carries. It mirrors the consumer-facing fields of verify.Check and deliberately drops its execution metadata (routes, screenshots, steps, duration) to keep the event small: the full result stays server-side, the event is for webhooks and embedding UIs.
type ProgressVerificationData ¶ added in v0.23.0
type ProgressVerificationData struct {
RunID string `json:"run_id"`
PlanRevision int `json:"plan_revision"`
// Baseline is true when the run happened without an active
// plan.
Baseline bool `json:"baseline,omitempty"`
// Passed reports whether the run passed: nothing failed and at least
// one check actually ran.
Passed bool `json:"passed,omitempty"`
// Checks are the individual check outcomes, in run order.
Checks []ProgressVerificationCheck `json:"checks"`
// Errors are run-level findings that prevented some commands from
// resolving; they are reported, never swallowed.
Errors []string `json:"errors,omitempty"`
}
ProgressVerificationData is the payload for progress_verification: the verification result — the checks, pass/fail, and evidence references.
type PublishHook ¶ added in v0.32.0
PublishHook rewrites an event's payload before it is delivered. It runs synchronously on the publishing goroutine, outside the bus locks, and its return value replaces the payload every subscriber sees.
type RateLimitedEvent ¶ added in v0.16.19
type RateLimitedEvent struct {
Provider string `json:"provider"`
Attempt int `json:"attempt"`
MaxAttempts int `json:"max_attempts"`
RetryAfterMS int `json:"retry_after_ms"`
Message string `json:"message"`
SessionID string `json:"session_id,omitempty"`
}
RateLimitedEvent is the payload for EventTypeRateLimited. The approval broker publishes this when a tool call returns a RateLimitError so the WebUI can show "rate-limited, retrying…" and disable the input until the backoff elapses.
func RateLimitedEventFromError ¶ added in v0.16.19
func RateLimitedEventFromError(provider string, attempt, maxAttempts, retryAfterMS int, sessionID string, err error) *RateLimitedEvent
RateLimitedEventFromError builds a RateLimitedEvent from a typed error and a backoff context. If the cause is not a rate-limit error, returns nil — caller should check before publishing.
type ShellApprovalPart ¶ added in v0.16.19
type ShellApprovalPart struct {
ID string `json:"id"`
Text string `json:"text"`
Kind string `json:"kind"`
Semantic string `json:"semantic"`
Risk string `json:"risk"`
}
ShellApprovalPart represents a single logical segment of a shell command in the approval payload. Declared here (not in agent) to avoid an import cycle — agent imports events, so events cannot import agent.
type ShellApprovalPartArg ¶ added in v0.16.19
ShellApprovalPartArg is a plain-struct mirror of agent.ShellPart so ShellApprovalRequestEvent can accept agent-side data without importing the agent package (which would create a cycle).
Callers in pkg/agent construct the parts slice from ShellProposal.Parts:
parts := make([]events.ShellApprovalPartArg, len(proposal.Parts))
for i, p := range proposal.Parts {
parts[i] = events.ShellApprovalPartArg{
ID: p.ID, Text: p.Text,
Kind: string(p.Kind), Semantic: p.Semantic,
Risk: string(proposal.RiskLevel),
}
}
type ShellApprovalRequestPayload ¶ added in v0.16.19
type ShellApprovalRequestPayload struct {
RequestID string `json:"request_id"`
Command string `json:"command"`
Parts []ShellApprovalPart `json:"parts"`
UnifiedView string `json:"unified_view"`
RiskLevel string `json:"risk_level"`
Timestamp string `json:"timestamp"`
}
ShellApprovalRequestPayload is the JSON shape of a shell_approval_request event. The frontend consumes this to render the per-part approval panel.
type ShellApprovalResponsePayload ¶ added in v0.16.19
type ShellApprovalResponsePayload struct {
RequestID string `json:"request_id"`
Decisions map[string]bool `json:"decisions"`
}
ShellApprovalResponsePayload is the JSON shape the WebUI POSTs back to /api/shell-approvals/{id}/decision. It maps each part ID to an approved boolean.
type UIEvent ¶
type UIEvent struct {
ID string `json:"id"`
Type string `json:"type"`
Timestamp time.Time `json:"timestamp"`
Data any `json:"data"`
}
UIEvent represents an event that can be forwarded between CLI and Web UI.
@ts-generated webui/src/types/generated.ts::UIEvent SP-034-5b: the EventType* constants below are mirrored as the ServerEventType string-literal union in generated.ts. The outbound registry in pkg/webui/websocket_outbound_registry.go covers the same surface (a test asserts they stay in sync).