Skip to main content

roder_api/
events.rs

1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3
4use crate::artifacts::{ContextArtifact, ContextArtifactId};
5use crate::automations::{
6    AutomationCompleted, AutomationCreated, AutomationDeleted, AutomationDue, AutomationFailed,
7    AutomationLeaseExpired, AutomationLeased, AutomationQueued, AutomationSkipped,
8    AutomationStarted, AutomationUpdated,
9};
10use crate::code_index::{
11    CodeIndexChunked, CodeIndexEmbedded, CodeIndexFailed, CodeIndexProofFilteredResultDropped,
12    CodeIndexReady, CodeIndexStale, CodeIndexingStarted,
13};
14use crate::discovery::{
15    DiscoveryAuthRequired, DiscoveryCatalogBuilt, DiscoveryItemPromoted, DiscoveryItemRead,
16    DiscoveryItemUpdated, DiscoveryPromotionExpired, DiscoveryPromotionReused,
17    DiscoveryWarmCacheHit,
18};
19use crate::dynamic_workflows::{
20    WorkflowAgentCompleted, WorkflowAgentFailed, WorkflowAgentQueued, WorkflowAgentStarted,
21    WorkflowApprovalRequested, WorkflowCheckpointRecorded, WorkflowOutputRecorded,
22    WorkflowPhaseCompleted, WorkflowPhaseStarted, WorkflowRunApproved, WorkflowRunCompleted,
23    WorkflowRunDenied, WorkflowRunDrafted, WorkflowRunFailed, WorkflowRunPaused, WorkflowRunQueued,
24    WorkflowRunResumed, WorkflowRunStarted, WorkflowRunStopped,
25};
26use crate::extension::{ExtensionId, InferenceEngineId};
27use crate::goals::{ThreadGoalCleared, ThreadGoalUpdated};
28use crate::inference::{
29    InferenceEvent, ModelSelection, ReasoningConfig, RuntimeProfile, SpeedPolicyDecision,
30    TokenUsage,
31};
32use crate::inference_routing::InferenceRoutingDecision;
33use crate::knowledge::{KnowledgeDocId, KnowledgeDocSummary, KnowledgeLinkType};
34use crate::lifecycle::TurnLifecycleRecord;
35use crate::media::{MediaArtifact, MediaArtifactId, MediaPreview};
36use crate::memory::{MemoryCitation, MemoryId, MemoryProviderSelection, MemoryRecord, MemoryScope};
37use crate::plan_review::{
38    HunkId, HunkRecord, PlanComment, PlanReview, PlanReviewId, PlanReviewStatus, PlanRewrite,
39};
40use crate::processes::{
41    ProcessExited, ProcessFailed, ProcessOutput, ProcessStarted, ProcessStopped, ProcessStopping,
42};
43use crate::reliability::{
44    ReliabilityFailureRecorded, ReliabilityLimitRecorded, ReliabilityMetricRecorded,
45    ReliabilityRetryRecorded,
46};
47use crate::retrieval::{
48    RetrievalDiscoveryItemPromoted, RetrievalPromotionSkipped, RetrievalResultUsed,
49    RetrievalRouteAccepted, RetrievalRouteFailed, RetrievalRouteIgnored, RetrievalRoutePlanned,
50};
51use crate::review::{ReviewId, ReviewOutput, ReviewPublishSkip, ReviewPublisherId, ReviewTarget};
52use crate::skills::{
53    SkillActivationResolved, SkillAutoActivated, SkillConfigApplied, SkillIndexRendered,
54    SkillInvoked, SkillSkipped, SkillsCatalogLoaded,
55};
56use crate::subagents::{
57    AgentSwarmCompleted, AgentSwarmModeChanged, AgentSwarmProgress, AgentSwarmStarted,
58    SubagentExitReason, UltraModeChanged,
59};
60use crate::task_ledger::TaskLedgerItem;
61use crate::teams::{
62    AgentTeamDisplayMode, TeamId, TeamMemberDescriptor, TeamMemberId, TeamMemberStatus,
63    TeamTaskDescriptor,
64};
65use crate::trace::{
66    ParentTurnRef, SubagentTraceDelta, SubagentTraceId, SubagentTraceStatus, SubagentTraceSummary,
67};
68use crate::transcript::TranscriptItem;
69use crate::workflow::{WorkflowImportDecision, WorkflowImportError, WorkflowImportItem};
70use crate::workspace_changes::WorkspaceChangeObservation;
71
72pub use crate::policy_mode::{
73    PolicyBypassActive, PolicyDecisionRecorded, PolicyExitPlanRequested, PolicyExitPlanResolved,
74    PolicyModeChanged,
75};
76pub use crate::tasks::{TaskCancelled, TaskCompleted, TaskFailed, TaskOutput, TaskStarted};
77
78pub type ThreadId = String;
79pub type TurnId = String;
80pub type EventId = String;
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83pub enum EventSource {
84    Runtime,
85    Core,
86    Provider,
87    Tool,
88    AppServer,
89    Tui,
90    Extension,
91    System,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct RuntimeStarted {
96    #[serde(with = "time::serde::rfc3339")]
97    pub timestamp: OffsetDateTime,
98}
99
100/// A local project hook execution. Hook output is truncated before recording.
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct HookRunRecorded {
103    pub thread_id: ThreadId,
104    pub turn_id: TurnId,
105    pub tool_id: String,
106    pub tool_name: String,
107    pub hook_event_name: String,
108    pub handler_type: String,
109    pub status: String,
110    pub detail: String,
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub output: Option<String>,
113    #[serde(with = "time::serde::rfc3339")]
114    pub timestamp: OffsetDateTime,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct ExtensionRegistered {
119    pub extension_id: ExtensionId,
120    #[serde(with = "time::serde::rfc3339")]
121    pub timestamp: OffsetDateTime,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ThreadCreated {
126    pub thread_id: ThreadId,
127    #[serde(with = "time::serde::rfc3339")]
128    pub timestamp: OffsetDateTime,
129}
130
131/// A conversation fork into a worktree-backed child thread was requested.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct ThreadForkRequested {
134    pub parent_thread_id: ThreadId,
135    pub name: String,
136    #[serde(with = "time::serde::rfc3339")]
137    pub timestamp: OffsetDateTime,
138}
139
140/// A child thread was created with its workspace fork.
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct ThreadForked {
143    pub parent_thread_id: ThreadId,
144    pub child_thread_id: ThreadId,
145    pub fork: crate::forks::WorkspaceFork,
146    #[serde(with = "time::serde::rfc3339")]
147    pub timestamp: OffsetDateTime,
148}
149
150/// A requested conversation fork failed (worktree or thread creation).
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct ThreadForkFailed {
153    pub parent_thread_id: ThreadId,
154    pub name: String,
155    pub message: String,
156    #[serde(with = "time::serde::rfc3339")]
157    pub timestamp: OffsetDateTime,
158}
159
160/// A fork's worktree was explicitly removed.
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct ThreadForkRemoved {
163    pub thread_id: ThreadId,
164    pub fork_id: String,
165    pub worktree_path: String,
166    #[serde(with = "time::serde::rfc3339")]
167    pub timestamp: OffsetDateTime,
168}
169
170/// A typed event emitted by an extension (e.g. a process-hosted child)
171/// through the extension-owned event channel. Payloads are redacted and
172/// schema-versioned by the emitter; the host enforces a size cap.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct ExtensionEventEmitted {
175    pub extension_id: String,
176    pub event_kind: String,
177    pub schema_version: u32,
178    pub payload: serde_json::Value,
179    #[serde(with = "time::serde::rfc3339")]
180    pub timestamp: OffsetDateTime,
181}
182
183/// An event sink failed or timed out while handling an envelope. The
184/// message is redacted; sink-failure events are never re-dispatched to
185/// sinks, so a broken sink cannot create an event loop.
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct EventSinkFailed {
188    pub sink_id: String,
189    pub event_kind: String,
190    pub message: String,
191    #[serde(with = "time::serde::rfc3339")]
192    pub timestamp: OffsetDateTime,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct ThreadLoaded {
197    pub thread_id: ThreadId,
198    #[serde(with = "time::serde::rfc3339")]
199    pub timestamp: OffsetDateTime,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct TurnStarted {
204    pub thread_id: ThreadId,
205    pub turn_id: TurnId,
206    #[serde(default)]
207    pub runtime_profile: RuntimeProfile,
208    #[serde(with = "time::serde::rfc3339")]
209    pub timestamp: OffsetDateTime,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct ContextAssemblyStarted {
214    pub thread_id: ThreadId,
215    pub turn_id: TurnId,
216    #[serde(with = "time::serde::rfc3339")]
217    pub timestamp: OffsetDateTime,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct ContextBlockAdded {
222    pub thread_id: ThreadId,
223    pub turn_id: TurnId,
224    pub block_type: String,
225    #[serde(default)]
226    pub byte_count: u64,
227    #[serde(default)]
228    pub estimated_tokens: u32,
229    #[serde(default)]
230    pub priority: i32,
231    #[serde(with = "time::serde::rfc3339")]
232    pub timestamp: OffsetDateTime,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct ContextAssemblyCompleted {
237    pub thread_id: ThreadId,
238    pub turn_id: TurnId,
239    #[serde(default)]
240    pub block_count: u64,
241    #[serde(default)]
242    pub total_byte_count: u64,
243    #[serde(default)]
244    pub estimated_tokens: u32,
245    #[serde(default)]
246    pub prompt_estimated_tokens: u32,
247    #[serde(default, skip_serializing_if = "Option::is_none")]
248    pub token_budget: Option<u32>,
249    #[serde(with = "time::serde::rfc3339")]
250    pub timestamp: OffsetDateTime,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct ContextEntrypointCandidatesInjected {
255    pub thread_id: ThreadId,
256    pub turn_id: TurnId,
257    pub candidate_count: u64,
258    pub block_byte_count: u64,
259    pub estimated_tokens: u32,
260    #[serde(with = "time::serde::rfc3339")]
261    pub timestamp: OffsetDateTime,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct ContextCompactionStarted {
266    pub thread_id: ThreadId,
267    pub turn_id: TurnId,
268    pub original_item_count: u64,
269    pub original_estimated_tokens: u32,
270    #[serde(with = "time::serde::rfc3339")]
271    pub timestamp: OffsetDateTime,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct ContextCompactionRecorded {
276    pub thread_id: ThreadId,
277    pub turn_id: TurnId,
278    pub original_item_count: u64,
279    pub original_estimated_tokens: u32,
280    pub compacted_item_count: u64,
281    pub compacted_estimated_tokens: u32,
282    pub file_backed: bool,
283    #[serde(default, skip_serializing_if = "Option::is_none")]
284    pub strategy: Option<String>,
285    #[serde(default, skip_serializing_if = "Option::is_none")]
286    pub pruned_tool_count: Option<u32>,
287    /// Wall-clock duration of the compaction pass, when measured.
288    #[serde(default, skip_serializing_if = "Option::is_none")]
289    pub duration_ms: Option<u64>,
290    #[serde(with = "time::serde::rfc3339")]
291    pub timestamp: OffsetDateTime,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct ContextCompactionSkipped {
296    pub thread_id: ThreadId,
297    pub turn_id: TurnId,
298    pub reason: String,
299    pub estimated_tokens: u32,
300    #[serde(default, skip_serializing_if = "Option::is_none")]
301    pub threshold: Option<u32>,
302    #[serde(default, skip_serializing_if = "Option::is_none")]
303    pub pruned_tool_count: Option<u32>,
304    #[serde(with = "time::serde::rfc3339")]
305    pub timestamp: OffsetDateTime,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct InferenceStarted {
310    pub thread_id: ThreadId,
311    pub turn_id: TurnId,
312    pub engine_id: InferenceEngineId,
313    #[serde(default = "default_model_selection")]
314    pub model: ModelSelection,
315    #[serde(default)]
316    pub reasoning: ReasoningConfig,
317    #[serde(default, skip_serializing_if = "Option::is_none")]
318    pub speed_policy: Option<SpeedPolicyDecision>,
319    #[serde(default, skip_serializing_if = "Option::is_none")]
320    pub deadline_remaining_seconds: Option<u64>,
321    #[serde(with = "time::serde::rfc3339")]
322    pub timestamp: OffsetDateTime,
323}
324
325fn default_model_selection() -> ModelSelection {
326    ModelSelection {
327        provider: String::new(),
328        model: String::new(),
329    }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
333pub struct InferenceRoutingDecisionEvent {
334    pub thread_id: ThreadId,
335    pub turn_id: TurnId,
336    #[serde(default)]
337    pub round_index: u32,
338    pub default_selection: ModelSelection,
339    pub selected_selection: ModelSelection,
340    pub decision: InferenceRoutingDecision,
341    #[serde(with = "time::serde::rfc3339")]
342    pub timestamp: OffsetDateTime,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct InferenceEventReceived {
347    pub thread_id: ThreadId,
348    pub turn_id: TurnId,
349    pub event: InferenceEvent,
350    #[serde(with = "time::serde::rfc3339")]
351    pub timestamp: OffsetDateTime,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct ToolCallRequested {
356    pub thread_id: ThreadId,
357    pub turn_id: TurnId,
358    pub tool_id: String,
359    pub tool_name: String,
360    #[serde(default, skip_serializing_if = "Option::is_none")]
361    pub display_payload: Option<serde_json::Value>,
362    #[serde(with = "time::serde::rfc3339")]
363    pub timestamp: OffsetDateTime,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
367#[serde(rename_all = "snake_case")]
368pub enum ToolCallValidationFailureClass {
369    InvalidJson,
370    UnknownTool,
371    MissingRequired,
372    UnexpectedProperty,
373    WrongType,
374    EmptyRequiredString,
375    SchemaRepairApplied,
376    SchemaRepairRejected,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
380#[serde(rename_all = "snake_case")]
381pub enum ToolCallValidationRepairStatus {
382    NotNeeded,
383    Applied,
384    Rejected,
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct ToolCallValidationRecorded {
389    pub thread_id: ThreadId,
390    pub turn_id: TurnId,
391    pub tool_id: String,
392    pub tool_name: String,
393    pub failure_class: ToolCallValidationFailureClass,
394    pub repair_status: ToolCallValidationRepairStatus,
395    pub message: String,
396    #[serde(with = "time::serde::rfc3339")]
397    pub timestamp: OffsetDateTime,
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct ApprovalRequested {
402    pub thread_id: ThreadId,
403    pub turn_id: TurnId,
404    pub approval_id: String,
405    pub tool_id: String,
406    pub tool_name: String,
407    pub reason: Option<String>,
408    #[serde(with = "time::serde::rfc3339")]
409    pub timestamp: OffsetDateTime,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct ApprovalResolved {
414    pub thread_id: ThreadId,
415    pub turn_id: TurnId,
416    pub approval_id: String,
417    pub tool_id: String,
418    pub tool_name: String,
419    pub approved: bool,
420    #[serde(with = "time::serde::rfc3339")]
421    pub timestamp: OffsetDateTime,
422}
423
424#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
425#[serde(rename_all = "camelCase")]
426pub enum ExternalToolCallOutcome {
427    Resolved,
428    TimedOut,
429    Cancelled,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct ExternalToolCallRequested {
434    pub thread_id: ThreadId,
435    pub turn_id: TurnId,
436    pub request_id: String,
437    pub tool_id: String,
438    pub tool_name: String,
439    pub arguments: serde_json::Value,
440    #[serde(with = "time::serde::rfc3339")]
441    pub timestamp: OffsetDateTime,
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize)]
445pub struct ExternalToolCallResolved {
446    pub thread_id: ThreadId,
447    pub turn_id: TurnId,
448    pub request_id: String,
449    pub tool_id: String,
450    pub tool_name: String,
451    pub outcome: ExternalToolCallOutcome,
452    pub is_error: bool,
453    #[serde(with = "time::serde::rfc3339")]
454    pub timestamp: OffsetDateTime,
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct UserInputRequested {
459    pub thread_id: ThreadId,
460    pub turn_id: TurnId,
461    pub request_id: String,
462    pub questions: serde_json::Value,
463    #[serde(with = "time::serde::rfc3339")]
464    pub timestamp: OffsetDateTime,
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct UserInputResolved {
469    pub thread_id: ThreadId,
470    pub turn_id: TurnId,
471    pub request_id: String,
472    pub answers: serde_json::Value,
473    #[serde(with = "time::serde::rfc3339")]
474    pub timestamp: OffsetDateTime,
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct TaskLedgerUpdated {
479    pub thread_id: ThreadId,
480    pub turn_id: TurnId,
481    pub tasks: Vec<TaskLedgerItem>,
482    pub completed_count: u64,
483    #[serde(with = "time::serde::rfc3339")]
484    pub timestamp: OffsetDateTime,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct VerificationRequired {
489    pub thread_id: ThreadId,
490    pub turn_id: TurnId,
491    pub reason: String,
492    pub changed_files: Vec<String>,
493    pub tool_evidence: Vec<String>,
494    pub tests_run: Vec<String>,
495    pub open_gaps: Vec<String>,
496    #[serde(with = "time::serde::rfc3339")]
497    pub timestamp: OffsetDateTime,
498}
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct VerificationCompleted {
502    pub thread_id: ThreadId,
503    pub turn_id: TurnId,
504    pub passed: bool,
505    pub changed_files: Vec<String>,
506    pub tool_evidence: Vec<String>,
507    pub tests_run: Vec<String>,
508    pub open_gaps: Vec<String>,
509    #[serde(with = "time::serde::rfc3339")]
510    pub timestamp: OffsetDateTime,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize)]
514pub struct VerificationSkipped {
515    pub thread_id: ThreadId,
516    pub turn_id: TurnId,
517    pub reason: String,
518    #[serde(with = "time::serde::rfc3339")]
519    pub timestamp: OffsetDateTime,
520}
521
522#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct ToolCallStarted {
524    pub thread_id: ThreadId,
525    pub turn_id: TurnId,
526    pub tool_id: String,
527    #[serde(default, skip_serializing_if = "Option::is_none")]
528    pub tool_name: Option<String>,
529    #[serde(default, skip_serializing_if = "Option::is_none")]
530    pub display_payload: Option<serde_json::Value>,
531    #[serde(with = "time::serde::rfc3339")]
532    pub timestamp: OffsetDateTime,
533}
534
535#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct ToolCallCompleted {
537    pub thread_id: ThreadId,
538    pub turn_id: TurnId,
539    pub tool_id: String,
540    #[serde(default, skip_serializing_if = "Option::is_none")]
541    pub tool_name: Option<String>,
542    #[serde(default, skip_serializing_if = "Option::is_none")]
543    pub display_payload: Option<serde_json::Value>,
544    #[serde(default)]
545    pub is_error: bool,
546    #[serde(default)]
547    pub output: Option<String>,
548    #[serde(with = "time::serde::rfc3339")]
549    pub timestamp: OffsetDateTime,
550}
551
552#[derive(Debug, Clone, Serialize, Deserialize)]
553pub struct ToolOutputTruncated {
554    pub thread_id: ThreadId,
555    pub turn_id: TurnId,
556    pub tool_id: String,
557    #[serde(default, skip_serializing_if = "Option::is_none")]
558    pub tool_name: Option<String>,
559    pub original_line_count: u64,
560    pub original_char_count: u64,
561    pub inline_char_count: u64,
562    pub artifact_backed: bool,
563    #[serde(with = "time::serde::rfc3339")]
564    pub timestamp: OffsetDateTime,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
568pub struct SubagentStarted {
569    pub thread_id: ThreadId,
570    pub turn_id: TurnId,
571    pub parent_thread_id: ThreadId,
572    pub parent_turn_id: TurnId,
573    pub agent_type: String,
574    pub description: String,
575    pub model: Option<String>,
576    #[serde(with = "time::serde::rfc3339")]
577    pub timestamp: OffsetDateTime,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct SubagentMessage {
582    pub thread_id: ThreadId,
583    pub turn_id: TurnId,
584    pub parent_thread_id: ThreadId,
585    pub parent_turn_id: TurnId,
586    pub agent_type: String,
587    pub text: String,
588    #[serde(with = "time::serde::rfc3339")]
589    pub timestamp: OffsetDateTime,
590}
591
592#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct SubagentToolCall {
594    pub thread_id: ThreadId,
595    pub turn_id: TurnId,
596    pub parent_thread_id: ThreadId,
597    pub parent_turn_id: TurnId,
598    pub agent_type: String,
599    pub tool_id: String,
600    pub tool_name: String,
601    #[serde(with = "time::serde::rfc3339")]
602    pub timestamp: OffsetDateTime,
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize)]
606pub struct SubagentCompleted {
607    pub thread_id: ThreadId,
608    pub turn_id: TurnId,
609    pub parent_thread_id: ThreadId,
610    pub parent_turn_id: TurnId,
611    pub agent_type: String,
612    pub exit_reason: SubagentExitReason,
613    #[serde(with = "time::serde::rfc3339")]
614    pub timestamp: OffsetDateTime,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize)]
618pub struct SubagentFailed {
619    pub thread_id: ThreadId,
620    pub turn_id: TurnId,
621    pub parent_thread_id: ThreadId,
622    pub parent_turn_id: TurnId,
623    pub agent_type: String,
624    pub error: String,
625    #[serde(with = "time::serde::rfc3339")]
626    pub timestamp: OffsetDateTime,
627}
628
629#[derive(Debug, Clone, Serialize, Deserialize)]
630pub struct SubagentTraceCreated {
631    pub summary: SubagentTraceSummary,
632    #[serde(with = "time::serde::rfc3339")]
633    pub timestamp: OffsetDateTime,
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize)]
637pub struct SubagentTraceDeltaEvent {
638    pub delta: SubagentTraceDelta,
639    #[serde(with = "time::serde::rfc3339")]
640    pub timestamp: OffsetDateTime,
641}
642
643#[derive(Debug, Clone, Serialize, Deserialize)]
644pub struct SubagentTraceStatusChanged {
645    pub trace_id: SubagentTraceId,
646    pub parent: ParentTurnRef,
647    pub status: SubagentTraceStatus,
648    #[serde(default, skip_serializing_if = "Option::is_none")]
649    pub detail: Option<String>,
650    #[serde(with = "time::serde::rfc3339")]
651    pub timestamp: OffsetDateTime,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize)]
655pub struct SubagentTraceCompleted {
656    pub summary: SubagentTraceSummary,
657    #[serde(with = "time::serde::rfc3339")]
658    pub timestamp: OffsetDateTime,
659}
660
661#[derive(Debug, Clone, Serialize, Deserialize)]
662pub struct SubagentTraceFailed {
663    pub summary: SubagentTraceSummary,
664    pub error: String,
665    #[serde(with = "time::serde::rfc3339")]
666    pub timestamp: OffsetDateTime,
667}
668
669#[derive(Debug, Clone, Serialize, Deserialize)]
670#[serde(rename_all = "camelCase")]
671pub struct PlanReviewCreated {
672    pub review: PlanReview,
673    #[serde(with = "time::serde::rfc3339")]
674    pub timestamp: OffsetDateTime,
675}
676
677#[derive(Debug, Clone, Serialize, Deserialize)]
678#[serde(rename_all = "camelCase")]
679pub struct PlanReviewStatusChanged {
680    pub thread_id: ThreadId,
681    pub turn_id: TurnId,
682    pub review_id: PlanReviewId,
683    pub status: PlanReviewStatus,
684    #[serde(default, skip_serializing_if = "Option::is_none")]
685    pub detail: Option<String>,
686    #[serde(with = "time::serde::rfc3339")]
687    pub timestamp: OffsetDateTime,
688}
689
690#[derive(Debug, Clone, Serialize, Deserialize)]
691#[serde(rename_all = "camelCase")]
692pub struct PlanReviewCommentAdded {
693    pub thread_id: ThreadId,
694    pub turn_id: TurnId,
695    pub review_id: PlanReviewId,
696    pub comment: PlanComment,
697    #[serde(with = "time::serde::rfc3339")]
698    pub timestamp: OffsetDateTime,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
702#[serde(rename_all = "camelCase")]
703pub struct PlanReviewRewritten {
704    pub thread_id: ThreadId,
705    pub turn_id: TurnId,
706    pub review_id: PlanReviewId,
707    pub rewrite: PlanRewrite,
708    #[serde(with = "time::serde::rfc3339")]
709    pub timestamp: OffsetDateTime,
710}
711
712#[derive(Debug, Clone, Serialize, Deserialize)]
713#[serde(rename_all = "camelCase")]
714pub struct PlanReviewApproved {
715    pub thread_id: ThreadId,
716    pub turn_id: TurnId,
717    pub review_id: PlanReviewId,
718    #[serde(with = "time::serde::rfc3339")]
719    pub timestamp: OffsetDateTime,
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
723#[serde(rename_all = "camelCase")]
724pub struct PlanReviewRejected {
725    pub thread_id: ThreadId,
726    pub turn_id: TurnId,
727    pub review_id: PlanReviewId,
728    #[serde(default, skip_serializing_if = "Option::is_none")]
729    pub reason: Option<String>,
730    #[serde(with = "time::serde::rfc3339")]
731    pub timestamp: OffsetDateTime,
732}
733
734#[derive(Debug, Clone, Serialize, Deserialize)]
735#[serde(rename_all = "camelCase")]
736pub struct HunkRecorded {
737    pub hunk: HunkRecord,
738    #[serde(with = "time::serde::rfc3339")]
739    pub timestamp: OffsetDateTime,
740}
741
742#[derive(Debug, Clone, Serialize, Deserialize)]
743#[serde(rename_all = "camelCase")]
744pub struct WorkspaceChangeObserved {
745    pub change: WorkspaceChangeObservation,
746    #[serde(with = "time::serde::rfc3339")]
747    pub timestamp: OffsetDateTime,
748}
749
750#[derive(Debug, Clone, Serialize, Deserialize)]
751#[serde(rename_all = "camelCase")]
752pub struct HunkRollbackRequested {
753    pub thread_id: ThreadId,
754    pub turn_id: TurnId,
755    pub hunk_id: HunkId,
756    #[serde(with = "time::serde::rfc3339")]
757    pub timestamp: OffsetDateTime,
758}
759
760#[derive(Debug, Clone, Serialize, Deserialize)]
761#[serde(rename_all = "camelCase")]
762pub struct HunkRollbackCompleted {
763    pub thread_id: ThreadId,
764    pub turn_id: TurnId,
765    pub hunk_id: HunkId,
766    #[serde(default, skip_serializing_if = "Option::is_none")]
767    pub error: Option<String>,
768    #[serde(with = "time::serde::rfc3339")]
769    pub timestamp: OffsetDateTime,
770}
771
772#[derive(Debug, Clone, Serialize, Deserialize)]
773#[serde(rename_all = "camelCase")]
774pub struct WorkflowImportsDetected {
775    pub workspace: String,
776    pub items: Vec<WorkflowImportItem>,
777    #[serde(default, skip_serializing_if = "Vec::is_empty")]
778    pub errors: Vec<WorkflowImportError>,
779    #[serde(with = "time::serde::rfc3339")]
780    pub timestamp: OffsetDateTime,
781}
782
783#[derive(Debug, Clone, Serialize, Deserialize)]
784#[serde(rename_all = "camelCase")]
785pub struct WorkflowImportPreviewed {
786    pub item: WorkflowImportItem,
787    #[serde(with = "time::serde::rfc3339")]
788    pub timestamp: OffsetDateTime,
789}
790
791#[derive(Debug, Clone, Serialize, Deserialize)]
792#[serde(rename_all = "camelCase")]
793pub struct WorkflowImportEnabled {
794    pub item: WorkflowImportItem,
795    pub decision: WorkflowImportDecision,
796    #[serde(with = "time::serde::rfc3339")]
797    pub timestamp: OffsetDateTime,
798}
799
800#[derive(Debug, Clone, Serialize, Deserialize)]
801#[serde(rename_all = "camelCase")]
802pub struct WorkflowImportDisabled {
803    pub item_id: String,
804    pub decision: WorkflowImportDecision,
805    #[serde(with = "time::serde::rfc3339")]
806    pub timestamp: OffsetDateTime,
807}
808
809#[derive(Debug, Clone, Serialize, Deserialize)]
810#[serde(rename_all = "camelCase")]
811pub struct WorkflowImportStale {
812    pub item: WorkflowImportItem,
813    pub previous_hash: String,
814    #[serde(with = "time::serde::rfc3339")]
815    pub timestamp: OffsetDateTime,
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize)]
819#[serde(rename_all = "camelCase")]
820pub struct WorkflowImportFailed {
821    pub item_id: Option<String>,
822    pub error: WorkflowImportError,
823    #[serde(with = "time::serde::rfc3339")]
824    pub timestamp: OffsetDateTime,
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize)]
828#[serde(rename_all = "camelCase")]
829pub struct MediaArtifactCreated {
830    pub thread_id: ThreadId,
831    pub turn_id: TurnId,
832    pub artifact: MediaArtifact,
833    #[serde(with = "time::serde::rfc3339")]
834    pub timestamp: OffsetDateTime,
835}
836
837#[derive(Debug, Clone, Serialize, Deserialize)]
838#[serde(rename_all = "camelCase")]
839pub struct MediaArtifactUpdated {
840    pub thread_id: ThreadId,
841    pub turn_id: TurnId,
842    pub artifact: MediaArtifact,
843    #[serde(with = "time::serde::rfc3339")]
844    pub timestamp: OffsetDateTime,
845}
846
847#[derive(Debug, Clone, Serialize, Deserialize)]
848#[serde(rename_all = "camelCase")]
849pub struct MediaArtifactDeleted {
850    pub artifact_id: MediaArtifactId,
851    #[serde(with = "time::serde::rfc3339")]
852    pub timestamp: OffsetDateTime,
853}
854
855#[derive(Debug, Clone, Serialize, Deserialize)]
856#[serde(rename_all = "camelCase")]
857pub struct MediaPreviewReady {
858    pub thread_id: ThreadId,
859    pub turn_id: TurnId,
860    pub preview: MediaPreview,
861    #[serde(with = "time::serde::rfc3339")]
862    pub timestamp: OffsetDateTime,
863}
864
865#[derive(Debug, Clone, Serialize, Deserialize)]
866#[serde(rename_all = "camelCase")]
867pub struct ContextArtifactCreated {
868    pub thread_id: ThreadId,
869    pub turn_id: TurnId,
870    pub artifact: ContextArtifact,
871    #[serde(with = "time::serde::rfc3339")]
872    pub timestamp: OffsetDateTime,
873}
874
875#[derive(Debug, Clone, Serialize, Deserialize)]
876#[serde(rename_all = "camelCase")]
877pub struct ContextArtifactAppended {
878    pub thread_id: ThreadId,
879    pub turn_id: TurnId,
880    pub artifact_id: ContextArtifactId,
881    pub appended_bytes: u64,
882    pub byte_count: u64,
883    pub line_count: u64,
884    #[serde(with = "time::serde::rfc3339")]
885    pub timestamp: OffsetDateTime,
886}
887
888#[derive(Debug, Clone, Serialize, Deserialize)]
889#[serde(rename_all = "camelCase")]
890pub struct ContextArtifactCapped {
891    pub thread_id: ThreadId,
892    pub turn_id: TurnId,
893    pub artifact_id: ContextArtifactId,
894    pub inline_byte_count: u64,
895    pub original_byte_count: u64,
896    #[serde(with = "time::serde::rfc3339")]
897    pub timestamp: OffsetDateTime,
898}
899
900#[derive(Debug, Clone, Serialize, Deserialize)]
901#[serde(rename_all = "camelCase")]
902pub struct ContextArtifactDeleted {
903    pub thread_id: ThreadId,
904    pub artifact_id: ContextArtifactId,
905    #[serde(with = "time::serde::rfc3339")]
906    pub timestamp: OffsetDateTime,
907}
908
909#[derive(Debug, Clone, Serialize, Deserialize)]
910#[serde(rename_all = "camelCase")]
911pub struct ContextArtifactRetentionExpired {
912    pub thread_id: ThreadId,
913    pub artifact_id: ContextArtifactId,
914    #[serde(with = "time::serde::rfc3339")]
915    pub timestamp: OffsetDateTime,
916}
917
918#[derive(Debug, Clone, Serialize, Deserialize)]
919pub struct FileChanged {
920    pub thread_id: ThreadId,
921    pub turn_id: TurnId,
922    pub path: String,
923    pub change_type: String,
924    #[serde(with = "time::serde::rfc3339")]
925    pub timestamp: OffsetDateTime,
926}
927
928#[derive(Debug, Clone, Serialize, Deserialize)]
929pub struct FileChangePreviewReady {
930    pub thread_id: ThreadId,
931    pub turn_id: TurnId,
932    pub tool_id: String,
933    pub tool_name: String,
934    pub path: String,
935    pub change_type: String,
936    pub before: Option<String>,
937    pub after: String,
938    pub supports_partial: bool,
939    #[serde(with = "time::serde::rfc3339")]
940    pub timestamp: OffsetDateTime,
941}
942
943#[derive(Debug, Clone, Serialize, Deserialize)]
944pub struct TranscriptItemAppended {
945    pub thread_id: ThreadId,
946    pub turn_id: TurnId,
947    pub item_type: String,
948    #[serde(default, skip_serializing_if = "Option::is_none")]
949    pub item_index: Option<usize>,
950    /// Full transcript item for runtime appends. `None` means the record carries
951    /// append metadata without an embedded transcript item.
952    #[serde(default, skip_serializing_if = "Option::is_none")]
953    pub item: Option<TranscriptItem>,
954    #[serde(with = "time::serde::rfc3339")]
955    pub timestamp: OffsetDateTime,
956}
957
958#[derive(Debug, Clone, Serialize, Deserialize)]
959pub struct TurnCompleted {
960    pub thread_id: ThreadId,
961    pub turn_id: TurnId,
962    #[serde(default, skip_serializing_if = "Option::is_none")]
963    pub usage: Option<TokenUsage>,
964    /**
965     * Normalized stop reason of the turn's terminal inference step; see
966     * `crate::inference::finish_reason_from_stop_reason` for the vocabulary.
967     */
968    #[serde(default, skip_serializing_if = "Option::is_none")]
969    pub finish_reason: Option<String>,
970    #[serde(with = "time::serde::rfc3339")]
971    pub timestamp: OffsetDateTime,
972}
973
974#[derive(Debug, Clone, Serialize, Deserialize)]
975pub struct TurnFailed {
976    pub thread_id: ThreadId,
977    pub turn_id: TurnId,
978    pub error: String,
979    #[serde(default, skip_serializing_if = "Option::is_none")]
980    pub error_kind: Option<String>,
981    #[serde(default, skip_serializing_if = "Option::is_none")]
982    pub usage: Option<TokenUsage>,
983    #[serde(with = "time::serde::rfc3339")]
984    pub timestamp: OffsetDateTime,
985}
986
987#[derive(Debug, Clone, Serialize, Deserialize)]
988pub struct TurnPartialResult {
989    pub thread_id: ThreadId,
990    pub turn_id: TurnId,
991    pub summary: String,
992    #[serde(with = "time::serde::rfc3339")]
993    pub timestamp: OffsetDateTime,
994}
995
996#[derive(Debug, Clone, Serialize, Deserialize)]
997pub struct TurnDeadlineExceeded {
998    pub thread_id: ThreadId,
999    pub turn_id: TurnId,
1000    #[serde(with = "time::serde::rfc3339")]
1001    pub deadline: OffsetDateTime,
1002    pub partial_result: String,
1003    #[serde(with = "time::serde::rfc3339")]
1004    pub timestamp: OffsetDateTime,
1005}
1006
1007#[derive(Debug, Clone, Serialize, Deserialize)]
1008pub struct TurnInterrupted {
1009    pub thread_id: ThreadId,
1010    pub turn_id: TurnId,
1011    #[serde(with = "time::serde::rfc3339")]
1012    pub timestamp: OffsetDateTime,
1013}
1014
1015#[derive(Debug, Clone, Serialize, Deserialize)]
1016pub struct TurnSteered {
1017    pub thread_id: ThreadId,
1018    pub turn_id: TurnId,
1019    pub message: String,
1020    #[serde(with = "time::serde::rfc3339")]
1021    pub timestamp: OffsetDateTime,
1022}
1023
1024#[derive(Debug, Clone, Serialize, Deserialize)]
1025pub struct RunnerLifecycle {
1026    pub destination_id: String,
1027    pub provider_id: String,
1028    pub state: String,
1029    #[serde(default, skip_serializing_if = "Option::is_none")]
1030    pub session_id: Option<String>,
1031    #[serde(with = "time::serde::rfc3339")]
1032    pub timestamp: OffsetDateTime,
1033}
1034
1035#[derive(Debug, Clone, Serialize, Deserialize)]
1036pub struct TeamStarted {
1037    pub team_id: TeamId,
1038    pub lead_thread_id: ThreadId,
1039    pub display_mode: AgentTeamDisplayMode,
1040    pub members: Vec<TeamMemberDescriptor>,
1041    pub tasks: Vec<TeamTaskDescriptor>,
1042    #[serde(with = "time::serde::rfc3339")]
1043    pub timestamp: OffsetDateTime,
1044}
1045
1046#[derive(Debug, Clone, Serialize, Deserialize)]
1047pub struct TeamMemberStarted {
1048    pub team_id: TeamId,
1049    pub member: TeamMemberDescriptor,
1050    #[serde(with = "time::serde::rfc3339")]
1051    pub timestamp: OffsetDateTime,
1052}
1053
1054#[derive(Debug, Clone, Serialize, Deserialize)]
1055pub struct TeamMemberStatusChanged {
1056    pub team_id: TeamId,
1057    pub member_id: TeamMemberId,
1058    pub member_thread_id: ThreadId,
1059    pub status: TeamMemberStatus,
1060    #[serde(with = "time::serde::rfc3339")]
1061    pub timestamp: OffsetDateTime,
1062}
1063
1064#[derive(Debug, Clone, Serialize, Deserialize)]
1065pub struct TeamMemberMessageDelta {
1066    pub team_id: TeamId,
1067    pub member_id: TeamMemberId,
1068    pub member_thread_id: ThreadId,
1069    pub turn_id: TurnId,
1070    pub delta: String,
1071    #[serde(with = "time::serde::rfc3339")]
1072    pub timestamp: OffsetDateTime,
1073}
1074
1075#[derive(Debug, Clone, Serialize, Deserialize)]
1076pub struct TeamMemberCompleted {
1077    pub team_id: TeamId,
1078    pub member_id: TeamMemberId,
1079    pub member_thread_id: ThreadId,
1080    pub turn_id: Option<TurnId>,
1081    pub status: TeamMemberStatus,
1082    #[serde(default, skip_serializing_if = "Option::is_none")]
1083    pub final_message: Option<String>,
1084    #[serde(default, skip_serializing_if = "Option::is_none")]
1085    pub error: Option<String>,
1086    #[serde(with = "time::serde::rfc3339")]
1087    pub timestamp: OffsetDateTime,
1088}
1089
1090#[derive(Debug, Clone, Serialize, Deserialize)]
1091pub struct TeamDisplayModeChanged {
1092    pub team_id: TeamId,
1093    pub display_mode: AgentTeamDisplayMode,
1094    #[serde(with = "time::serde::rfc3339")]
1095    pub timestamp: OffsetDateTime,
1096}
1097
1098#[derive(Debug, Clone, Serialize, Deserialize)]
1099pub struct TeamTaskChanged {
1100    pub team_id: TeamId,
1101    pub task: TeamTaskDescriptor,
1102    #[serde(with = "time::serde::rfc3339")]
1103    pub timestamp: OffsetDateTime,
1104}
1105
1106#[derive(Debug, Clone, Serialize, Deserialize)]
1107pub struct TeamCleanupCompleted {
1108    pub team_id: TeamId,
1109    pub forced: bool,
1110    #[serde(with = "time::serde::rfc3339")]
1111    pub timestamp: OffsetDateTime,
1112}
1113
1114#[derive(Debug, Clone, Serialize, Deserialize)]
1115pub struct MemorySaved {
1116    pub memory: MemoryRecord,
1117    #[serde(with = "time::serde::rfc3339")]
1118    pub timestamp: OffsetDateTime,
1119}
1120
1121#[derive(Debug, Clone, Serialize, Deserialize)]
1122pub struct MemoryUpdated {
1123    pub memory: MemoryRecord,
1124    #[serde(with = "time::serde::rfc3339")]
1125    pub timestamp: OffsetDateTime,
1126}
1127
1128#[derive(Debug, Clone, Serialize, Deserialize)]
1129pub struct MemoryDeleted {
1130    pub memory_id: MemoryId,
1131    #[serde(with = "time::serde::rfc3339")]
1132    pub timestamp: OffsetDateTime,
1133}
1134
1135#[derive(Debug, Clone, Serialize, Deserialize)]
1136pub struct MemoryQueried {
1137    pub scope: Option<MemoryScope>,
1138    pub query: String,
1139    pub result_count: usize,
1140    #[serde(with = "time::serde::rfc3339")]
1141    pub timestamp: OffsetDateTime,
1142}
1143
1144#[derive(Debug, Clone, Serialize, Deserialize)]
1145pub struct MemoryRecallReady {
1146    pub thread_id: ThreadId,
1147    pub turn_id: TurnId,
1148    pub citations: Vec<MemoryCitation>,
1149    #[serde(with = "time::serde::rfc3339")]
1150    pub timestamp: OffsetDateTime,
1151}
1152
1153#[derive(Debug, Clone, Serialize, Deserialize)]
1154pub struct MemoryReembedQueued {
1155    pub scope: Option<MemoryScope>,
1156    pub provider: MemoryProviderSelection,
1157    #[serde(with = "time::serde::rfc3339")]
1158    pub timestamp: OffsetDateTime,
1159}
1160
1161#[derive(Debug, Clone, Serialize, Deserialize)]
1162pub struct MemoryProviderChanged {
1163    pub provider: MemoryProviderSelection,
1164    #[serde(with = "time::serde::rfc3339")]
1165    pub timestamp: OffsetDateTime,
1166}
1167
1168#[derive(Debug, Clone, Serialize, Deserialize)]
1169pub struct MemoryObservationRecorded {
1170    pub thread_id: ThreadId,
1171    pub turn_id: TurnId,
1172    pub memory_id: MemoryId,
1173    #[serde(with = "time::serde::rfc3339")]
1174    pub timestamp: OffsetDateTime,
1175}
1176
1177#[derive(Debug, Clone, Serialize, Deserialize)]
1178pub struct KnowledgeSaved {
1179    pub document: KnowledgeDocSummary,
1180    #[serde(with = "time::serde::rfc3339")]
1181    pub timestamp: OffsetDateTime,
1182}
1183
1184#[derive(Debug, Clone, Serialize, Deserialize)]
1185pub struct KnowledgeUpdated {
1186    pub document: KnowledgeDocSummary,
1187    #[serde(with = "time::serde::rfc3339")]
1188    pub timestamp: OffsetDateTime,
1189}
1190
1191#[derive(Debug, Clone, Serialize, Deserialize)]
1192pub struct KnowledgeArchived {
1193    pub doc_id: KnowledgeDocId,
1194    #[serde(with = "time::serde::rfc3339")]
1195    pub timestamp: OffsetDateTime,
1196}
1197
1198#[derive(Debug, Clone, Serialize, Deserialize)]
1199pub struct KnowledgeLinked {
1200    pub from: KnowledgeDocId,
1201    pub to: KnowledgeDocId,
1202    pub link_type: KnowledgeLinkType,
1203    pub removed: bool,
1204    #[serde(with = "time::serde::rfc3339")]
1205    pub timestamp: OffsetDateTime,
1206}
1207
1208#[derive(Debug, Clone, Serialize, Deserialize)]
1209pub struct RemoteServerStarted {
1210    pub listen_addr: String,
1211    pub connect_urls: Vec<String>,
1212    pub token_preview: String,
1213    #[serde(with = "time::serde::rfc3339")]
1214    pub timestamp: OffsetDateTime,
1215}
1216
1217#[derive(Debug, Clone, Serialize, Deserialize)]
1218pub struct RemoteServerStopped {
1219    pub listen_addr: String,
1220    #[serde(with = "time::serde::rfc3339")]
1221    pub timestamp: OffsetDateTime,
1222}
1223
1224#[derive(Debug, Clone, Serialize, Deserialize)]
1225pub struct RemoteAuthFailed {
1226    pub remote_addr: Option<String>,
1227    #[serde(with = "time::serde::rfc3339")]
1228    pub timestamp: OffsetDateTime,
1229}
1230
1231#[derive(Debug, Clone, Serialize, Deserialize)]
1232pub struct RemoteClientConnected {
1233    pub remote_addr: Option<String>,
1234    #[serde(with = "time::serde::rfc3339")]
1235    pub timestamp: OffsetDateTime,
1236}
1237
1238#[derive(Debug, Clone, Serialize, Deserialize)]
1239pub struct RemoteClientDisconnected {
1240    pub remote_addr: Option<String>,
1241    #[serde(with = "time::serde::rfc3339")]
1242    pub timestamp: OffsetDateTime,
1243}
1244
1245#[derive(Debug, Clone, Serialize, Deserialize)]
1246pub struct RoadmapChanged {
1247    pub event_kind: String,
1248    pub path: String,
1249    pub task_id: Option<String>,
1250    pub thread_id: Option<String>,
1251    #[serde(with = "time::serde::rfc3339")]
1252    pub timestamp: OffsetDateTime,
1253}
1254
1255/// A review runs in its own detached thread, so `thread_id` is the thread the
1256/// review was requested from, `review_thread_id` is the detached thread, and
1257/// `turn_id` is the review turn inside it.
1258#[derive(Debug, Clone, Serialize, Deserialize)]
1259#[serde(rename_all = "camelCase")]
1260pub struct ReviewStarted {
1261    pub thread_id: ThreadId,
1262    pub turn_id: TurnId,
1263    pub review_id: ReviewId,
1264    pub review_thread_id: ThreadId,
1265    pub target: ReviewTarget,
1266    pub label: String,
1267    #[serde(with = "time::serde::rfc3339")]
1268    pub timestamp: OffsetDateTime,
1269}
1270
1271#[derive(Debug, Clone, Serialize, Deserialize)]
1272#[serde(rename_all = "camelCase")]
1273pub struct ReviewCompleted {
1274    pub thread_id: ThreadId,
1275    pub turn_id: TurnId,
1276    pub review_id: ReviewId,
1277    pub review_thread_id: ThreadId,
1278    pub output: ReviewOutput,
1279    #[serde(with = "time::serde::rfc3339")]
1280    pub timestamp: OffsetDateTime,
1281}
1282
1283#[derive(Debug, Clone, Serialize, Deserialize)]
1284#[serde(rename_all = "camelCase")]
1285pub struct ReviewFailed {
1286    pub thread_id: ThreadId,
1287    /// Absent when the review failed before its turn was admitted.
1288    #[serde(default, skip_serializing_if = "Option::is_none")]
1289    pub turn_id: Option<TurnId>,
1290    pub review_id: ReviewId,
1291    #[serde(default, skip_serializing_if = "Option::is_none")]
1292    pub review_thread_id: Option<ThreadId>,
1293    pub error: String,
1294    #[serde(with = "time::serde::rfc3339")]
1295    pub timestamp: OffsetDateTime,
1296}
1297
1298#[derive(Debug, Clone, Serialize, Deserialize)]
1299#[serde(rename_all = "camelCase")]
1300pub struct ReviewPublished {
1301    #[serde(default, skip_serializing_if = "Option::is_none")]
1302    pub thread_id: Option<ThreadId>,
1303    pub review_id: ReviewId,
1304    pub publisher_id: ReviewPublisherId,
1305    #[serde(default, skip_serializing_if = "Option::is_none")]
1306    pub url: Option<String>,
1307    pub published_findings: usize,
1308    #[serde(default)]
1309    pub skipped: Vec<ReviewPublishSkip>,
1310    pub dry_run: bool,
1311    #[serde(with = "time::serde::rfc3339")]
1312    pub timestamp: OffsetDateTime,
1313}
1314
1315#[derive(Debug, Clone, Serialize, Deserialize)]
1316pub enum RoderEvent {
1317    RuntimeStarted(RuntimeStarted),
1318    ExtensionRegistered(ExtensionRegistered),
1319    ExtensionEventEmitted(ExtensionEventEmitted),
1320    EventSinkFailed(EventSinkFailed),
1321    ThreadCreated(ThreadCreated),
1322    ThreadLoaded(ThreadLoaded),
1323    ThreadForkRequested(ThreadForkRequested),
1324    ThreadForked(ThreadForked),
1325    ThreadForkFailed(ThreadForkFailed),
1326    ThreadForkRemoved(ThreadForkRemoved),
1327    TurnStarted(TurnStarted),
1328    ContextAssemblyStarted(ContextAssemblyStarted),
1329    ContextBlockAdded(ContextBlockAdded),
1330    ContextAssemblyCompleted(ContextAssemblyCompleted),
1331    ContextEntrypointCandidatesInjected(ContextEntrypointCandidatesInjected),
1332    ContextCompactionStarted(ContextCompactionStarted),
1333    ContextCompactionRecorded(ContextCompactionRecorded),
1334    ContextCompactionSkipped(ContextCompactionSkipped),
1335    InferenceRoutingDecision(InferenceRoutingDecisionEvent),
1336    InferenceStarted(InferenceStarted),
1337    InferenceEventReceived(InferenceEventReceived),
1338    ToolCallRequested(ToolCallRequested),
1339    HookRunRecorded(HookRunRecorded),
1340    ToolCallValidationRecorded(ToolCallValidationRecorded),
1341    ReliabilityFailureRecorded(ReliabilityFailureRecorded),
1342    ReliabilityRetryRecorded(ReliabilityRetryRecorded),
1343    ReliabilityLimitRecorded(ReliabilityLimitRecorded),
1344    ReliabilityMetricRecorded(ReliabilityMetricRecorded),
1345    CodeIndexingStarted(CodeIndexingStarted),
1346    CodeIndexChunked(CodeIndexChunked),
1347    CodeIndexEmbedded(CodeIndexEmbedded),
1348    CodeIndexReady(CodeIndexReady),
1349    CodeIndexStale(CodeIndexStale),
1350    CodeIndexFailed(CodeIndexFailed),
1351    CodeIndexProofFilteredResultDropped(CodeIndexProofFilteredResultDropped),
1352    ApprovalRequested(ApprovalRequested),
1353    ApprovalResolved(ApprovalResolved),
1354    ExternalToolCallRequested(ExternalToolCallRequested),
1355    ExternalToolCallResolved(ExternalToolCallResolved),
1356    UserInputRequested(UserInputRequested),
1357    UserInputResolved(UserInputResolved),
1358    TaskLedgerUpdated(TaskLedgerUpdated),
1359    VerificationRequired(VerificationRequired),
1360    VerificationCompleted(VerificationCompleted),
1361    VerificationSkipped(VerificationSkipped),
1362    PolicyDecisionRecorded(PolicyDecisionRecorded),
1363    PolicyBypassActive(PolicyBypassActive),
1364    PolicyModeChanged(PolicyModeChanged),
1365    AgentSwarmModeChanged(AgentSwarmModeChanged),
1366    AgentSwarmStarted(AgentSwarmStarted),
1367    AgentSwarmProgress(AgentSwarmProgress),
1368    AgentSwarmCompleted(AgentSwarmCompleted),
1369    UltraModeChanged(UltraModeChanged),
1370    PolicyExitPlanRequested(PolicyExitPlanRequested),
1371    PolicyExitPlanResolved(PolicyExitPlanResolved),
1372    ToolCallStarted(ToolCallStarted),
1373    ToolCallCompleted(ToolCallCompleted),
1374    ToolOutputTruncated(ToolOutputTruncated),
1375    SubagentStarted(SubagentStarted),
1376    SubagentMessage(SubagentMessage),
1377    SubagentToolCall(SubagentToolCall),
1378    SubagentCompleted(SubagentCompleted),
1379    SubagentFailed(SubagentFailed),
1380    SubagentTraceCreated(SubagentTraceCreated),
1381    SubagentTraceDelta(SubagentTraceDeltaEvent),
1382    SubagentTraceStatusChanged(SubagentTraceStatusChanged),
1383    SubagentTraceCompleted(SubagentTraceCompleted),
1384    SubagentTraceFailed(SubagentTraceFailed),
1385    PlanReviewCreated(PlanReviewCreated),
1386    PlanReviewStatusChanged(PlanReviewStatusChanged),
1387    PlanReviewCommentAdded(PlanReviewCommentAdded),
1388    PlanReviewRewritten(PlanReviewRewritten),
1389    PlanReviewApproved(PlanReviewApproved),
1390    PlanReviewRejected(PlanReviewRejected),
1391    HunkRecorded(HunkRecorded),
1392    WorkspaceChangeObserved(WorkspaceChangeObserved),
1393    HunkRollbackRequested(HunkRollbackRequested),
1394    HunkRollbackCompleted(HunkRollbackCompleted),
1395    WorkflowImportsDetected(WorkflowImportsDetected),
1396    WorkflowImportPreviewed(WorkflowImportPreviewed),
1397    WorkflowImportEnabled(WorkflowImportEnabled),
1398    WorkflowImportDisabled(WorkflowImportDisabled),
1399    WorkflowImportStale(WorkflowImportStale),
1400    WorkflowImportFailed(WorkflowImportFailed),
1401    WorkflowRunDrafted(WorkflowRunDrafted),
1402    WorkflowApprovalRequested(WorkflowApprovalRequested),
1403    WorkflowRunApproved(WorkflowRunApproved),
1404    WorkflowRunDenied(WorkflowRunDenied),
1405    WorkflowRunQueued(WorkflowRunQueued),
1406    WorkflowRunStarted(WorkflowRunStarted),
1407    WorkflowPhaseStarted(WorkflowPhaseStarted),
1408    WorkflowPhaseCompleted(WorkflowPhaseCompleted),
1409    WorkflowAgentQueued(WorkflowAgentQueued),
1410    WorkflowAgentStarted(WorkflowAgentStarted),
1411    WorkflowAgentCompleted(WorkflowAgentCompleted),
1412    WorkflowAgentFailed(WorkflowAgentFailed),
1413    WorkflowOutputRecorded(WorkflowOutputRecorded),
1414    WorkflowCheckpointRecorded(WorkflowCheckpointRecorded),
1415    WorkflowRunPaused(WorkflowRunPaused),
1416    WorkflowRunResumed(WorkflowRunResumed),
1417    WorkflowRunStopped(WorkflowRunStopped),
1418    WorkflowRunCompleted(WorkflowRunCompleted),
1419    WorkflowRunFailed(WorkflowRunFailed),
1420    MediaArtifactCreated(MediaArtifactCreated),
1421    MediaArtifactUpdated(MediaArtifactUpdated),
1422    MediaArtifactDeleted(MediaArtifactDeleted),
1423    MediaPreviewReady(MediaPreviewReady),
1424    ContextArtifactCreated(ContextArtifactCreated),
1425    ContextArtifactAppended(ContextArtifactAppended),
1426    ContextArtifactCapped(ContextArtifactCapped),
1427    ContextArtifactDeleted(ContextArtifactDeleted),
1428    ContextArtifactRetentionExpired(ContextArtifactRetentionExpired),
1429    DiscoveryCatalogBuilt(DiscoveryCatalogBuilt),
1430    DiscoveryItemUpdated(DiscoveryItemUpdated),
1431    DiscoveryAuthRequired(DiscoveryAuthRequired),
1432    DiscoveryItemRead(DiscoveryItemRead),
1433    DiscoveryItemPromoted(DiscoveryItemPromoted),
1434    DiscoveryPromotionReused(DiscoveryPromotionReused),
1435    DiscoveryWarmCacheHit(DiscoveryWarmCacheHit),
1436    DiscoveryPromotionExpired(DiscoveryPromotionExpired),
1437    RetrievalRoutePlanned(RetrievalRoutePlanned),
1438    RetrievalRouteAccepted(RetrievalRouteAccepted),
1439    RetrievalRouteIgnored(RetrievalRouteIgnored),
1440    RetrievalRouteFailed(RetrievalRouteFailed),
1441    RetrievalResultUsed(RetrievalResultUsed),
1442    RetrievalDiscoveryItemPromoted(RetrievalDiscoveryItemPromoted),
1443    RetrievalPromotionSkipped(RetrievalPromotionSkipped),
1444    MemorySaved(MemorySaved),
1445    MemoryUpdated(MemoryUpdated),
1446    MemoryDeleted(MemoryDeleted),
1447    MemoryQueried(MemoryQueried),
1448    MemoryRecallReady(MemoryRecallReady),
1449    MemoryReembedQueued(MemoryReembedQueued),
1450    MemoryProviderChanged(MemoryProviderChanged),
1451    MemoryObservationRecorded(MemoryObservationRecorded),
1452    KnowledgeSaved(KnowledgeSaved),
1453    KnowledgeUpdated(KnowledgeUpdated),
1454    KnowledgeArchived(KnowledgeArchived),
1455    KnowledgeLinked(KnowledgeLinked),
1456    RemoteServerStarted(RemoteServerStarted),
1457    RemoteServerStopped(RemoteServerStopped),
1458    RemoteAuthFailed(RemoteAuthFailed),
1459    RemoteClientConnected(RemoteClientConnected),
1460    RemoteClientDisconnected(RemoteClientDisconnected),
1461    ThreadGoalUpdated(ThreadGoalUpdated),
1462    ThreadGoalCleared(ThreadGoalCleared),
1463    RoadmapChanged(RoadmapChanged),
1464    AutomationCreated(AutomationCreated),
1465    AutomationUpdated(AutomationUpdated),
1466    AutomationDeleted(AutomationDeleted),
1467    AutomationDue(AutomationDue),
1468    AutomationLeased(AutomationLeased),
1469    AutomationQueued(AutomationQueued),
1470    AutomationStarted(AutomationStarted),
1471    AutomationCompleted(AutomationCompleted),
1472    AutomationFailed(AutomationFailed),
1473    AutomationSkipped(AutomationSkipped),
1474    AutomationLeaseExpired(AutomationLeaseExpired),
1475    SkillsCatalogLoaded(SkillsCatalogLoaded),
1476    SkillConfigApplied(SkillConfigApplied),
1477    SkillActivationResolved(SkillActivationResolved),
1478    SkillIndexRendered(SkillIndexRendered),
1479    SkillInvoked(SkillInvoked),
1480    SkillAutoActivated(SkillAutoActivated),
1481    SkillSkipped(SkillSkipped),
1482    TaskStarted(TaskStarted),
1483    TaskOutput(TaskOutput),
1484    TaskCompleted(TaskCompleted),
1485    TaskFailed(TaskFailed),
1486    TaskCancelled(TaskCancelled),
1487    ProcessStarted(ProcessStarted),
1488    ProcessOutput(ProcessOutput),
1489    ProcessExited(ProcessExited),
1490    ProcessStopping(ProcessStopping),
1491    ProcessStopped(ProcessStopped),
1492    ProcessFailed(ProcessFailed),
1493    FileChangePreviewReady(FileChangePreviewReady),
1494    FileChanged(FileChanged),
1495    TranscriptItemAppended(TranscriptItemAppended),
1496    TurnCompleted(TurnCompleted),
1497    TurnFailed(TurnFailed),
1498    TurnPartialResult(TurnPartialResult),
1499    TurnDeadlineExceeded(TurnDeadlineExceeded),
1500    TurnLifecycleUpdated(TurnLifecycleRecord),
1501    TurnInterrupted(TurnInterrupted),
1502    TurnSteered(TurnSteered),
1503    RunnerLifecycle(RunnerLifecycle),
1504    TeamStarted(TeamStarted),
1505    TeamMemberStarted(TeamMemberStarted),
1506    TeamMemberStatusChanged(TeamMemberStatusChanged),
1507    TeamMemberMessageDelta(TeamMemberMessageDelta),
1508    TeamMemberCompleted(TeamMemberCompleted),
1509    TeamDisplayModeChanged(TeamDisplayModeChanged),
1510    TeamTaskChanged(TeamTaskChanged),
1511    TeamCleanupCompleted(TeamCleanupCompleted),
1512    ReviewStarted(ReviewStarted),
1513    ReviewCompleted(ReviewCompleted),
1514    ReviewFailed(ReviewFailed),
1515    ReviewPublished(ReviewPublished),
1516}
1517
1518impl RoderEvent {
1519    pub fn kind(&self) -> &'static str {
1520        match self {
1521            RoderEvent::RuntimeStarted(_) => "runtime.started",
1522            RoderEvent::ExtensionRegistered(_) => "extension.registered",
1523            RoderEvent::ExtensionEventEmitted(_) => "extension.event",
1524            RoderEvent::EventSinkFailed(_) => "extension.event_sink_failed",
1525            RoderEvent::ThreadCreated(_) => "thread.created",
1526            RoderEvent::ThreadLoaded(_) => "thread.loaded",
1527            RoderEvent::ThreadForkRequested(_) => "thread.fork_requested",
1528            RoderEvent::ThreadForked(_) => "thread.forked",
1529            RoderEvent::ThreadForkFailed(_) => "thread.fork_failed",
1530            RoderEvent::ThreadForkRemoved(_) => "thread.fork_removed",
1531            RoderEvent::TurnStarted(_) => "turn.started",
1532            RoderEvent::ContextAssemblyStarted(_) => "context.assembly_started",
1533            RoderEvent::ContextBlockAdded(_) => "context.block_added",
1534            RoderEvent::ContextAssemblyCompleted(_) => "context.assembly_completed",
1535            RoderEvent::ContextEntrypointCandidatesInjected(_) => {
1536                "context.entrypoint_candidates_injected"
1537            }
1538            RoderEvent::ContextCompactionStarted(_) => "context.compaction_started",
1539            RoderEvent::ContextCompactionRecorded(_) => "context.compaction_recorded",
1540            RoderEvent::ContextCompactionSkipped(_) => "context.compaction_skipped",
1541            RoderEvent::InferenceRoutingDecision(_) => "inference.routing_decision",
1542            RoderEvent::InferenceStarted(_) => "inference.started",
1543            RoderEvent::InferenceEventReceived(_) => "inference.event_received",
1544            RoderEvent::ToolCallRequested(_) => "tool.call_requested",
1545            RoderEvent::HookRunRecorded(_) => "hook.run_recorded",
1546            RoderEvent::ToolCallValidationRecorded(_) => "tool.call_validation",
1547            RoderEvent::ReliabilityFailureRecorded(_) => "reliability.failure",
1548            RoderEvent::ReliabilityRetryRecorded(_) => "reliability.retry",
1549            RoderEvent::ReliabilityLimitRecorded(_) => "reliability.limit",
1550            RoderEvent::ReliabilityMetricRecorded(_) => "reliability.metric",
1551            RoderEvent::CodeIndexingStarted(_) => "code_index.started",
1552            RoderEvent::CodeIndexChunked(_) => "code_index.chunked",
1553            RoderEvent::CodeIndexEmbedded(_) => "code_index.embedded",
1554            RoderEvent::CodeIndexReady(_) => "code_index.ready",
1555            RoderEvent::CodeIndexStale(_) => "code_index.stale",
1556            RoderEvent::CodeIndexFailed(_) => "code_index.failed",
1557            RoderEvent::CodeIndexProofFilteredResultDropped(_) => {
1558                "code_index.proof_filtered_result_dropped"
1559            }
1560            RoderEvent::ApprovalRequested(_) => "approval.requested",
1561            RoderEvent::ApprovalResolved(_) => "approval.resolved",
1562            RoderEvent::ExternalToolCallRequested(_) => "external_tool.requested",
1563            RoderEvent::ExternalToolCallResolved(_) => "external_tool.resolved",
1564            RoderEvent::UserInputRequested(_) => "user_input.requested",
1565            RoderEvent::UserInputResolved(_) => "user_input.resolved",
1566            RoderEvent::TaskLedgerUpdated(_) => "task_ledger.updated",
1567            RoderEvent::VerificationRequired(_) => "verification.required",
1568            RoderEvent::VerificationCompleted(_) => "verification.completed",
1569            RoderEvent::VerificationSkipped(_) => "verification.skipped",
1570            RoderEvent::PolicyDecisionRecorded(_) => "policy.decision",
1571            RoderEvent::PolicyBypassActive(_) => "policy.bypass_active",
1572            RoderEvent::PolicyModeChanged(_) => "policy.mode_changed",
1573            RoderEvent::AgentSwarmModeChanged(_) => "agent_swarm.mode_changed",
1574            RoderEvent::AgentSwarmStarted(_) => "agent_swarm.started",
1575            RoderEvent::AgentSwarmProgress(_) => "agent_swarm.progress",
1576            RoderEvent::AgentSwarmCompleted(_) => "agent_swarm.completed",
1577            RoderEvent::UltraModeChanged(_) => "ultra.mode_changed",
1578            RoderEvent::PolicyExitPlanRequested(_) => "policy.exit_plan_requested",
1579            RoderEvent::PolicyExitPlanResolved(_) => "policy.exit_plan_resolved",
1580            RoderEvent::ToolCallStarted(_) => "tool.call_started",
1581            RoderEvent::ToolCallCompleted(_) => "tool.call_completed",
1582            RoderEvent::ToolOutputTruncated(_) => "tool.output_truncated",
1583            RoderEvent::SubagentStarted(_) => "subagent.started",
1584            RoderEvent::SubagentMessage(_) => "subagent.message",
1585            RoderEvent::SubagentToolCall(_) => "subagent.tool_call",
1586            RoderEvent::SubagentCompleted(_) => "subagent.completed",
1587            RoderEvent::SubagentFailed(_) => "subagent.failed",
1588            RoderEvent::SubagentTraceCreated(_) => "turn/subagentTraceCreated",
1589            RoderEvent::SubagentTraceDelta(_) => "turn/subagentTraceDelta",
1590            RoderEvent::SubagentTraceStatusChanged(_) => "turn/subagentTraceStatusChanged",
1591            RoderEvent::SubagentTraceCompleted(_) => "turn/subagentTraceCompleted",
1592            RoderEvent::SubagentTraceFailed(_) => "turn/subagentTraceFailed",
1593            RoderEvent::PlanReviewCreated(_) => "plan/reviewCreated",
1594            RoderEvent::PlanReviewStatusChanged(_) => "plan/reviewStatusChanged",
1595            RoderEvent::PlanReviewCommentAdded(_) => "plan/reviewCommentAdded",
1596            RoderEvent::PlanReviewRewritten(_) => "plan/reviewRewritten",
1597            RoderEvent::PlanReviewApproved(_) => "plan/reviewApproved",
1598            RoderEvent::PlanReviewRejected(_) => "plan/reviewRejected",
1599            RoderEvent::HunkRecorded(_) => "hunk/recorded",
1600            RoderEvent::WorkspaceChangeObserved(_) => "workspace/changeObserved",
1601            RoderEvent::HunkRollbackRequested(_) => "hunk/rollbackRequested",
1602            RoderEvent::HunkRollbackCompleted(_) => "hunk/rollbackCompleted",
1603            RoderEvent::WorkflowImportsDetected(_) => "workflow/importsDetected",
1604            RoderEvent::WorkflowImportPreviewed(_) => "workflow/importPreviewed",
1605            RoderEvent::WorkflowImportEnabled(_) => "workflow/importEnabled",
1606            RoderEvent::WorkflowImportDisabled(_) => "workflow/importDisabled",
1607            RoderEvent::WorkflowImportStale(_) => "workflow/importStale",
1608            RoderEvent::WorkflowImportFailed(_) => "workflow/importFailed",
1609            RoderEvent::WorkflowRunDrafted(_) => "workflows/drafted",
1610            RoderEvent::WorkflowApprovalRequested(_) => "workflows/approvalRequested",
1611            RoderEvent::WorkflowRunApproved(_) => "workflows/approved",
1612            RoderEvent::WorkflowRunDenied(_) => "workflows/denied",
1613            RoderEvent::WorkflowRunQueued(_) => "workflows/queued",
1614            RoderEvent::WorkflowRunStarted(_) => "workflows/started",
1615            RoderEvent::WorkflowPhaseStarted(_) => "workflows/phaseStarted",
1616            RoderEvent::WorkflowPhaseCompleted(_) => "workflows/phaseCompleted",
1617            RoderEvent::WorkflowAgentQueued(_) => "workflows/agentQueued",
1618            RoderEvent::WorkflowAgentStarted(_) => "workflows/agentStarted",
1619            RoderEvent::WorkflowAgentCompleted(_) => "workflows/agentCompleted",
1620            RoderEvent::WorkflowAgentFailed(_) => "workflows/agentFailed",
1621            RoderEvent::WorkflowOutputRecorded(_) => "workflows/outputRecorded",
1622            RoderEvent::WorkflowCheckpointRecorded(_) => "workflows/checkpointRecorded",
1623            RoderEvent::WorkflowRunPaused(_) => "workflows/paused",
1624            RoderEvent::WorkflowRunResumed(_) => "workflows/resumed",
1625            RoderEvent::WorkflowRunStopped(_) => "workflows/stopped",
1626            RoderEvent::WorkflowRunCompleted(_) => "workflows/completed",
1627            RoderEvent::WorkflowRunFailed(_) => "workflows/failed",
1628            RoderEvent::MediaArtifactCreated(_) => "media/artifactCreated",
1629            RoderEvent::MediaArtifactUpdated(_) => "media/artifactUpdated",
1630            RoderEvent::MediaArtifactDeleted(_) => "media/artifactDeleted",
1631            RoderEvent::MediaPreviewReady(_) => "media/previewReady",
1632            RoderEvent::ContextArtifactCreated(_) => "artifact/created",
1633            RoderEvent::ContextArtifactAppended(_) => "artifact/appended",
1634            RoderEvent::ContextArtifactCapped(_) => "artifact/capped",
1635            RoderEvent::ContextArtifactDeleted(_) => "artifact/deleted",
1636            RoderEvent::ContextArtifactRetentionExpired(_) => "artifact/retentionExpired",
1637            RoderEvent::DiscoveryCatalogBuilt(_) => "discovery/catalogBuilt",
1638            RoderEvent::DiscoveryItemUpdated(_) => "discovery/itemUpdated",
1639            RoderEvent::DiscoveryAuthRequired(_) => "discovery/authRequired",
1640            RoderEvent::DiscoveryItemRead(_) => "discovery/itemRead",
1641            RoderEvent::DiscoveryItemPromoted(_) => "discovery/itemPromoted",
1642            RoderEvent::DiscoveryPromotionReused(_) => "discovery/promotionReused",
1643            RoderEvent::DiscoveryWarmCacheHit(_) => "discovery/warmCacheHit",
1644            RoderEvent::DiscoveryPromotionExpired(_) => "discovery/promotionExpired",
1645            RoderEvent::RetrievalRoutePlanned(_) => "retrieval/routePlanned",
1646            RoderEvent::RetrievalRouteAccepted(_) => "retrieval/routeAccepted",
1647            RoderEvent::RetrievalRouteIgnored(_) => "retrieval/routeIgnored",
1648            RoderEvent::RetrievalRouteFailed(_) => "retrieval/routeFailed",
1649            RoderEvent::RetrievalResultUsed(_) => "retrieval/resultUsed",
1650            RoderEvent::RetrievalDiscoveryItemPromoted(_) => "retrieval/discoveryItemPromoted",
1651            RoderEvent::RetrievalPromotionSkipped(_) => "retrieval/promotionSkipped",
1652            RoderEvent::MemorySaved(_) => "memory/saved",
1653            RoderEvent::MemoryUpdated(_) => "memory/updated",
1654            RoderEvent::MemoryDeleted(_) => "memory/deleted",
1655            RoderEvent::MemoryQueried(_) => "memory/queried",
1656            RoderEvent::MemoryRecallReady(_) => "memory/recallReady",
1657            RoderEvent::MemoryReembedQueued(_) => "memory/reembedQueued",
1658            RoderEvent::MemoryProviderChanged(_) => "memory/providerChanged",
1659            RoderEvent::MemoryObservationRecorded(_) => "memory/observationRecorded",
1660            RoderEvent::KnowledgeSaved(_) => "knowledge/saved",
1661            RoderEvent::KnowledgeUpdated(_) => "knowledge/updated",
1662            RoderEvent::KnowledgeArchived(_) => "knowledge/archived",
1663            RoderEvent::KnowledgeLinked(_) => "knowledge/linked",
1664            RoderEvent::RemoteServerStarted(_) => "remote/serverStarted",
1665            RoderEvent::RemoteServerStopped(_) => "remote/serverStopped",
1666            RoderEvent::RemoteAuthFailed(_) => "remote/authFailed",
1667            RoderEvent::RemoteClientConnected(_) => "remote/clientConnected",
1668            RoderEvent::RemoteClientDisconnected(_) => "remote/clientDisconnected",
1669            RoderEvent::ThreadGoalUpdated(_) => "thread/goal/updated",
1670            RoderEvent::ThreadGoalCleared(_) => "thread/goal/cleared",
1671            RoderEvent::RoadmapChanged(_) => "roadmap.changed",
1672            RoderEvent::AutomationCreated(_) => "automations/created",
1673            RoderEvent::AutomationUpdated(_) => "automations/updated",
1674            RoderEvent::AutomationDeleted(_) => "automations/deleted",
1675            RoderEvent::AutomationDue(_) => "automations/due",
1676            RoderEvent::AutomationLeased(_) => "automations/leased",
1677            RoderEvent::AutomationQueued(_) => "automations/queued",
1678            RoderEvent::AutomationStarted(_) => "automations/started",
1679            RoderEvent::AutomationCompleted(_) => "automations/completed",
1680            RoderEvent::AutomationFailed(_) => "automations/failed",
1681            RoderEvent::AutomationSkipped(_) => "automations/skipped",
1682            RoderEvent::AutomationLeaseExpired(_) => "automations/leaseExpired",
1683            RoderEvent::SkillsCatalogLoaded(_) => "skills/catalogLoaded",
1684            RoderEvent::SkillConfigApplied(_) => "skills/configApplied",
1685            RoderEvent::SkillActivationResolved(_) => "skills/activationResolved",
1686            RoderEvent::SkillIndexRendered(_) => "skills/indexRendered",
1687            RoderEvent::SkillInvoked(_) => "skills/invoked",
1688            RoderEvent::SkillAutoActivated(_) => "skills/autoActivated",
1689            RoderEvent::SkillSkipped(_) => "skills/skipped",
1690            RoderEvent::TaskStarted(_) => "task.started",
1691            RoderEvent::TaskOutput(_) => "task.output",
1692            RoderEvent::TaskCompleted(_) => "task.completed",
1693            RoderEvent::TaskFailed(_) => "task.failed",
1694            RoderEvent::TaskCancelled(_) => "task.cancelled",
1695            RoderEvent::ProcessStarted(_) => "process.started",
1696            RoderEvent::ProcessOutput(_) => "process.output",
1697            RoderEvent::ProcessExited(_) => "process.exited",
1698            RoderEvent::ProcessStopping(_) => "process.stopping",
1699            RoderEvent::ProcessStopped(_) => "process.stopped",
1700            RoderEvent::ProcessFailed(_) => "process.failed",
1701            RoderEvent::FileChangePreviewReady(_) => "file.change_preview_ready",
1702            RoderEvent::FileChanged(_) => "file.changed",
1703            RoderEvent::TranscriptItemAppended(_) => "turn.transcript_item_appended",
1704            RoderEvent::TurnCompleted(_) => "turn.completed",
1705            RoderEvent::TurnFailed(_) => "turn.failed",
1706            RoderEvent::TurnPartialResult(_) => "turn.partial_result",
1707            RoderEvent::TurnDeadlineExceeded(_) => "turn.deadline_exceeded",
1708            RoderEvent::TurnLifecycleUpdated(_) => "turn.lifecycle_updated",
1709            RoderEvent::TurnInterrupted(_) => "turn.interrupted",
1710            RoderEvent::TurnSteered(_) => "turn.steered",
1711            RoderEvent::RunnerLifecycle(_) => "runner.lifecycle",
1712            RoderEvent::TeamStarted(_) => "team.started",
1713            RoderEvent::TeamMemberStarted(_) => "team.member_started",
1714            RoderEvent::TeamMemberStatusChanged(_) => "team.member_status_changed",
1715            RoderEvent::TeamMemberMessageDelta(_) => "team.member_message_delta",
1716            RoderEvent::TeamMemberCompleted(_) => "team.member_completed",
1717            RoderEvent::TeamDisplayModeChanged(_) => "team.display_mode_changed",
1718            RoderEvent::TeamTaskChanged(_) => "team.task_changed",
1719            RoderEvent::TeamCleanupCompleted(_) => "team.cleanup_completed",
1720            RoderEvent::ReviewStarted(_) => "review.started",
1721            RoderEvent::ReviewCompleted(_) => "review.completed",
1722            RoderEvent::ReviewFailed(_) => "review.failed",
1723            RoderEvent::ReviewPublished(_) => "review.published",
1724        }
1725    }
1726
1727    pub fn source(&self) -> EventSource {
1728        match self {
1729            RoderEvent::InferenceEventReceived(_) | RoderEvent::InferenceStarted(_) => {
1730                EventSource::Provider
1731            }
1732            RoderEvent::InferenceRoutingDecision(_) => EventSource::Core,
1733            RoderEvent::ReliabilityRetryRecorded(_) => EventSource::Provider,
1734            RoderEvent::ReliabilityFailureRecorded(_)
1735            | RoderEvent::ReliabilityLimitRecorded(_)
1736            | RoderEvent::ReliabilityMetricRecorded(_) => EventSource::Core,
1737            RoderEvent::ToolCallRequested(_)
1738            | RoderEvent::ToolCallValidationRecorded(_)
1739            | RoderEvent::ToolCallStarted(_)
1740            | RoderEvent::ToolCallCompleted(_) => EventSource::Tool,
1741            RoderEvent::SubagentStarted(_)
1742            | RoderEvent::SubagentMessage(_)
1743            | RoderEvent::SubagentToolCall(_)
1744            | RoderEvent::SubagentCompleted(_)
1745            | RoderEvent::SubagentFailed(_)
1746            | RoderEvent::SubagentTraceCreated(_)
1747            | RoderEvent::SubagentTraceDelta(_)
1748            | RoderEvent::SubagentTraceStatusChanged(_)
1749            | RoderEvent::SubagentTraceCompleted(_)
1750            | RoderEvent::SubagentTraceFailed(_)
1751            | RoderEvent::PlanReviewCreated(_)
1752            | RoderEvent::PlanReviewStatusChanged(_)
1753            | RoderEvent::PlanReviewCommentAdded(_)
1754            | RoderEvent::PlanReviewRewritten(_)
1755            | RoderEvent::PlanReviewApproved(_)
1756            | RoderEvent::PlanReviewRejected(_)
1757            | RoderEvent::HunkRecorded(_)
1758            | RoderEvent::WorkspaceChangeObserved(_)
1759            | RoderEvent::HunkRollbackRequested(_)
1760            | RoderEvent::HunkRollbackCompleted(_)
1761            | RoderEvent::WorkflowImportsDetected(_)
1762            | RoderEvent::WorkflowImportPreviewed(_)
1763            | RoderEvent::WorkflowImportEnabled(_)
1764            | RoderEvent::WorkflowImportDisabled(_)
1765            | RoderEvent::WorkflowImportStale(_)
1766            | RoderEvent::WorkflowImportFailed(_)
1767            | RoderEvent::MediaArtifactCreated(_)
1768            | RoderEvent::MediaArtifactUpdated(_)
1769            | RoderEvent::MediaArtifactDeleted(_)
1770            | RoderEvent::MediaPreviewReady(_)
1771            | RoderEvent::ContextArtifactCreated(_)
1772            | RoderEvent::ContextArtifactAppended(_)
1773            | RoderEvent::ContextArtifactCapped(_)
1774            | RoderEvent::ContextArtifactDeleted(_)
1775            | RoderEvent::ContextArtifactRetentionExpired(_)
1776            | RoderEvent::DiscoveryCatalogBuilt(_)
1777            | RoderEvent::DiscoveryItemUpdated(_)
1778            | RoderEvent::DiscoveryAuthRequired(_)
1779            | RoderEvent::DiscoveryItemRead(_)
1780            | RoderEvent::DiscoveryItemPromoted(_)
1781            | RoderEvent::DiscoveryPromotionReused(_)
1782            | RoderEvent::DiscoveryWarmCacheHit(_)
1783            | RoderEvent::DiscoveryPromotionExpired(_)
1784            | RoderEvent::RetrievalRoutePlanned(_)
1785            | RoderEvent::RetrievalRouteAccepted(_)
1786            | RoderEvent::RetrievalRouteIgnored(_)
1787            | RoderEvent::RetrievalRouteFailed(_)
1788            | RoderEvent::RetrievalResultUsed(_)
1789            | RoderEvent::RetrievalDiscoveryItemPromoted(_)
1790            | RoderEvent::RetrievalPromotionSkipped(_)
1791            | RoderEvent::MemorySaved(_)
1792            | RoderEvent::MemoryUpdated(_)
1793            | RoderEvent::MemoryDeleted(_)
1794            | RoderEvent::MemoryQueried(_)
1795            | RoderEvent::MemoryRecallReady(_)
1796            | RoderEvent::MemoryReembedQueued(_)
1797            | RoderEvent::MemoryProviderChanged(_)
1798            | RoderEvent::MemoryObservationRecorded(_)
1799            | RoderEvent::TaskStarted(_)
1800            | RoderEvent::TaskOutput(_)
1801            | RoderEvent::TaskCompleted(_)
1802            | RoderEvent::TaskFailed(_)
1803            | RoderEvent::TaskCancelled(_)
1804            | RoderEvent::ProcessStarted(_)
1805            | RoderEvent::ProcessOutput(_)
1806            | RoderEvent::ProcessExited(_)
1807            | RoderEvent::ProcessStopping(_)
1808            | RoderEvent::ProcessStopped(_)
1809            | RoderEvent::ProcessFailed(_) => EventSource::Extension,
1810            RoderEvent::RemoteServerStarted(_)
1811            | RoderEvent::RemoteServerStopped(_)
1812            | RoderEvent::RemoteAuthFailed(_)
1813            | RoderEvent::RemoteClientConnected(_)
1814            | RoderEvent::RemoteClientDisconnected(_) => EventSource::AppServer,
1815            RoderEvent::RoadmapChanged(_)
1816            | RoderEvent::ThreadGoalUpdated(_)
1817            | RoderEvent::ThreadGoalCleared(_)
1818            | RoderEvent::AutomationCreated(_)
1819            | RoderEvent::AutomationUpdated(_)
1820            | RoderEvent::AutomationDeleted(_)
1821            | RoderEvent::AutomationDue(_)
1822            | RoderEvent::AutomationLeased(_)
1823            | RoderEvent::AutomationQueued(_)
1824            | RoderEvent::AutomationStarted(_)
1825            | RoderEvent::AutomationCompleted(_)
1826            | RoderEvent::AutomationFailed(_)
1827            | RoderEvent::AutomationSkipped(_)
1828            | RoderEvent::AutomationLeaseExpired(_)
1829            | RoderEvent::SkillsCatalogLoaded(_)
1830            | RoderEvent::SkillConfigApplied(_)
1831            | RoderEvent::SkillActivationResolved(_)
1832            | RoderEvent::SkillIndexRendered(_)
1833            | RoderEvent::SkillInvoked(_)
1834            | RoderEvent::SkillAutoActivated(_)
1835            | RoderEvent::SkillSkipped(_) => EventSource::Core,
1836            RoderEvent::FileChangePreviewReady(_) => EventSource::Tool,
1837            RoderEvent::UserInputRequested(_)
1838            | RoderEvent::UserInputResolved(_)
1839            | RoderEvent::TaskLedgerUpdated(_)
1840            | RoderEvent::TurnPartialResult(_)
1841            | RoderEvent::TurnDeadlineExceeded(_)
1842            | RoderEvent::TurnLifecycleUpdated(_)
1843            | RoderEvent::VerificationRequired(_)
1844            | RoderEvent::VerificationCompleted(_)
1845            | RoderEvent::VerificationSkipped(_) => EventSource::Core,
1846            RoderEvent::ExtensionRegistered(_) => EventSource::Extension,
1847            RoderEvent::RunnerLifecycle(_) => EventSource::Extension,
1848            RoderEvent::TeamStarted(_)
1849            | RoderEvent::TeamMemberStarted(_)
1850            | RoderEvent::TeamMemberStatusChanged(_)
1851            | RoderEvent::TeamMemberMessageDelta(_)
1852            | RoderEvent::TeamMemberCompleted(_)
1853            | RoderEvent::TeamDisplayModeChanged(_)
1854            | RoderEvent::TeamTaskChanged(_)
1855            | RoderEvent::TeamCleanupCompleted(_) => EventSource::Core,
1856            RoderEvent::ReviewStarted(_)
1857            | RoderEvent::ReviewCompleted(_)
1858            | RoderEvent::ReviewFailed(_) => EventSource::Core,
1859            RoderEvent::ReviewPublished(_) => EventSource::Extension,
1860            _ => EventSource::Core,
1861        }
1862    }
1863
1864    pub fn thread_id(&self) -> Option<&ThreadId> {
1865        match self {
1866            RoderEvent::ExtensionEventEmitted(_) | RoderEvent::EventSinkFailed(_) => None,
1867            RoderEvent::ThreadCreated(e) => Some(&e.thread_id),
1868            RoderEvent::ThreadLoaded(e) => Some(&e.thread_id),
1869            RoderEvent::ThreadForkRequested(e) => Some(&e.parent_thread_id),
1870            RoderEvent::ThreadForked(e) => Some(&e.child_thread_id),
1871            RoderEvent::ThreadForkFailed(e) => Some(&e.parent_thread_id),
1872            RoderEvent::ThreadForkRemoved(e) => Some(&e.thread_id),
1873            RoderEvent::TurnStarted(e) => Some(&e.thread_id),
1874            RoderEvent::ContextAssemblyStarted(e) => Some(&e.thread_id),
1875            RoderEvent::ContextBlockAdded(e) => Some(&e.thread_id),
1876            RoderEvent::ContextAssemblyCompleted(e) => Some(&e.thread_id),
1877            RoderEvent::ContextEntrypointCandidatesInjected(e) => Some(&e.thread_id),
1878            RoderEvent::ContextCompactionStarted(e) => Some(&e.thread_id),
1879            RoderEvent::ContextCompactionRecorded(e) => Some(&e.thread_id),
1880            RoderEvent::ContextCompactionSkipped(e) => Some(&e.thread_id),
1881            RoderEvent::InferenceRoutingDecision(e) => Some(&e.thread_id),
1882            RoderEvent::InferenceStarted(e) => Some(&e.thread_id),
1883            RoderEvent::InferenceEventReceived(e) => Some(&e.thread_id),
1884            RoderEvent::ToolCallRequested(e) => Some(&e.thread_id),
1885            RoderEvent::HookRunRecorded(e) => Some(&e.thread_id),
1886            RoderEvent::ToolCallValidationRecorded(e) => Some(&e.thread_id),
1887            RoderEvent::ReliabilityFailureRecorded(e) => Some(&e.context.thread_id),
1888            RoderEvent::ReliabilityRetryRecorded(e) => Some(&e.context.thread_id),
1889            RoderEvent::ReliabilityLimitRecorded(e) => Some(&e.context.thread_id),
1890            RoderEvent::ReliabilityMetricRecorded(e) => Some(&e.context.thread_id),
1891            RoderEvent::CodeIndexingStarted(e) => e.context.thread_id.as_ref(),
1892            RoderEvent::CodeIndexChunked(e) => e.context.thread_id.as_ref(),
1893            RoderEvent::CodeIndexEmbedded(e) => e.context.thread_id.as_ref(),
1894            RoderEvent::CodeIndexReady(_) => None,
1895            RoderEvent::CodeIndexStale(e) => e.context.thread_id.as_ref(),
1896            RoderEvent::CodeIndexFailed(e) => e.context.thread_id.as_ref(),
1897            RoderEvent::CodeIndexProofFilteredResultDropped(e) => e.context.thread_id.as_ref(),
1898            RoderEvent::ApprovalRequested(e) => Some(&e.thread_id),
1899            RoderEvent::ApprovalResolved(e) => Some(&e.thread_id),
1900            RoderEvent::ExternalToolCallRequested(e) => Some(&e.thread_id),
1901            RoderEvent::ExternalToolCallResolved(e) => Some(&e.thread_id),
1902            RoderEvent::UserInputRequested(e) => Some(&e.thread_id),
1903            RoderEvent::UserInputResolved(e) => Some(&e.thread_id),
1904            RoderEvent::TaskLedgerUpdated(e) => Some(&e.thread_id),
1905            RoderEvent::VerificationRequired(e) => Some(&e.thread_id),
1906            RoderEvent::VerificationCompleted(e) => Some(&e.thread_id),
1907            RoderEvent::VerificationSkipped(e) => Some(&e.thread_id),
1908            RoderEvent::PolicyDecisionRecorded(e) => Some(&e.thread_id),
1909            RoderEvent::PolicyBypassActive(e) => Some(&e.thread_id),
1910            RoderEvent::PolicyModeChanged(e) => Some(&e.thread_id),
1911            RoderEvent::AgentSwarmModeChanged(e) => Some(&e.thread_id),
1912            RoderEvent::AgentSwarmStarted(e) => Some(&e.thread_id),
1913            RoderEvent::AgentSwarmProgress(e) => Some(&e.thread_id),
1914            RoderEvent::AgentSwarmCompleted(e) => Some(&e.thread_id),
1915            RoderEvent::UltraModeChanged(e) => Some(&e.thread_id),
1916            RoderEvent::PolicyExitPlanRequested(e) => Some(&e.thread_id),
1917            RoderEvent::PolicyExitPlanResolved(e) => Some(&e.thread_id),
1918            RoderEvent::ToolCallStarted(e) => Some(&e.thread_id),
1919            RoderEvent::ToolCallCompleted(e) => Some(&e.thread_id),
1920            RoderEvent::ToolOutputTruncated(e) => Some(&e.thread_id),
1921            RoderEvent::SubagentStarted(e) => Some(&e.thread_id),
1922            RoderEvent::SubagentMessage(e) => Some(&e.thread_id),
1923            RoderEvent::SubagentToolCall(e) => Some(&e.thread_id),
1924            RoderEvent::SubagentCompleted(e) => Some(&e.thread_id),
1925            RoderEvent::SubagentFailed(e) => Some(&e.thread_id),
1926            RoderEvent::SubagentTraceCreated(e) => Some(&e.summary.parent.thread_id),
1927            RoderEvent::SubagentTraceDelta(e) => Some(&e.delta.parent.thread_id),
1928            RoderEvent::SubagentTraceStatusChanged(e) => Some(&e.parent.thread_id),
1929            RoderEvent::SubagentTraceCompleted(e) => Some(&e.summary.parent.thread_id),
1930            RoderEvent::SubagentTraceFailed(e) => Some(&e.summary.parent.thread_id),
1931            RoderEvent::PlanReviewCreated(e) => Some(&e.review.thread_id),
1932            RoderEvent::PlanReviewStatusChanged(e) => Some(&e.thread_id),
1933            RoderEvent::PlanReviewCommentAdded(e) => Some(&e.thread_id),
1934            RoderEvent::PlanReviewRewritten(e) => Some(&e.thread_id),
1935            RoderEvent::PlanReviewApproved(e) => Some(&e.thread_id),
1936            RoderEvent::PlanReviewRejected(e) => Some(&e.thread_id),
1937            RoderEvent::HunkRecorded(e) => Some(&e.hunk.thread_id),
1938            RoderEvent::WorkspaceChangeObserved(e) => Some(&e.change.thread_id),
1939            RoderEvent::HunkRollbackRequested(e) => Some(&e.thread_id),
1940            RoderEvent::HunkRollbackCompleted(e) => Some(&e.thread_id),
1941            RoderEvent::MediaArtifactCreated(e) => Some(&e.thread_id),
1942            RoderEvent::MediaArtifactUpdated(e) => Some(&e.thread_id),
1943            RoderEvent::MediaPreviewReady(e) => Some(&e.thread_id),
1944            RoderEvent::ContextArtifactCreated(e) => Some(&e.thread_id),
1945            RoderEvent::ContextArtifactAppended(e) => Some(&e.thread_id),
1946            RoderEvent::ContextArtifactCapped(e) => Some(&e.thread_id),
1947            RoderEvent::ContextArtifactDeleted(e) => Some(&e.thread_id),
1948            RoderEvent::ContextArtifactRetentionExpired(e) => Some(&e.thread_id),
1949            RoderEvent::DiscoveryItemRead(e) => Some(&e.thread_id),
1950            RoderEvent::DiscoveryItemPromoted(e) => Some(&e.record.thread_id),
1951            RoderEvent::DiscoveryPromotionReused(e) => Some(&e.record.thread_id),
1952            RoderEvent::DiscoveryWarmCacheHit(e) => Some(&e.record.thread_id),
1953            RoderEvent::DiscoveryPromotionExpired(e) => Some(&e.record.thread_id),
1954            RoderEvent::RetrievalRoutePlanned(e) => Some(&e.plan.thread_id),
1955            RoderEvent::RetrievalRouteAccepted(e) => Some(&e.thread_id),
1956            RoderEvent::RetrievalRouteIgnored(e) => Some(&e.thread_id),
1957            RoderEvent::RetrievalRouteFailed(e) => Some(&e.thread_id),
1958            RoderEvent::RetrievalResultUsed(e) => Some(&e.thread_id),
1959            RoderEvent::RetrievalDiscoveryItemPromoted(e) => Some(&e.thread_id),
1960            RoderEvent::RetrievalPromotionSkipped(e) => Some(&e.thread_id),
1961            RoderEvent::MemoryRecallReady(e) => Some(&e.thread_id),
1962            RoderEvent::MemoryObservationRecorded(e) => Some(&e.thread_id),
1963            RoderEvent::TaskStarted(e) => e.thread_id.as_ref(),
1964            RoderEvent::TaskOutput(e) => e.thread_id.as_ref(),
1965            RoderEvent::TaskCompleted(e) => e.thread_id.as_ref(),
1966            RoderEvent::TaskFailed(e) => e.thread_id.as_ref(),
1967            RoderEvent::TaskCancelled(e) => e.thread_id.as_ref(),
1968            RoderEvent::FileChangePreviewReady(e) => Some(&e.thread_id),
1969            RoderEvent::FileChanged(e) => Some(&e.thread_id),
1970            RoderEvent::TranscriptItemAppended(e) => Some(&e.thread_id),
1971            RoderEvent::TurnCompleted(e) => Some(&e.thread_id),
1972            RoderEvent::TurnFailed(e) => Some(&e.thread_id),
1973            RoderEvent::TurnPartialResult(e) => Some(&e.thread_id),
1974            RoderEvent::TurnDeadlineExceeded(e) => Some(&e.thread_id),
1975            RoderEvent::TurnLifecycleUpdated(e) => Some(&e.thread_id),
1976            RoderEvent::TurnInterrupted(e) => Some(&e.thread_id),
1977            RoderEvent::TurnSteered(e) => Some(&e.thread_id),
1978            RoderEvent::ThreadGoalUpdated(e) => Some(&e.thread_id),
1979            RoderEvent::ThreadGoalCleared(e) => Some(&e.thread_id),
1980            RoderEvent::TeamStarted(e) => Some(&e.lead_thread_id),
1981            RoderEvent::TeamMemberStarted(e) => Some(&e.member.thread_id),
1982            RoderEvent::TeamMemberStatusChanged(e) => Some(&e.member_thread_id),
1983            RoderEvent::TeamMemberMessageDelta(e) => Some(&e.member_thread_id),
1984            RoderEvent::TeamMemberCompleted(e) => Some(&e.member_thread_id),
1985            RoderEvent::SkillActivationResolved(e) => Some(&e.thread_id),
1986            RoderEvent::SkillIndexRendered(e) => Some(&e.thread_id),
1987            RoderEvent::SkillInvoked(e) => Some(&e.thread_id),
1988            RoderEvent::SkillAutoActivated(e) => Some(&e.thread_id),
1989            RoderEvent::SkillSkipped(e) => Some(&e.thread_id),
1990            RoderEvent::RuntimeStarted(_)
1991            | RoderEvent::ExtensionRegistered(_)
1992            | RoderEvent::WorkflowImportsDetected(_)
1993            | RoderEvent::WorkflowImportPreviewed(_)
1994            | RoderEvent::WorkflowImportEnabled(_)
1995            | RoderEvent::WorkflowImportDisabled(_)
1996            | RoderEvent::WorkflowImportStale(_)
1997            | RoderEvent::WorkflowImportFailed(_)
1998            | RoderEvent::MediaArtifactDeleted(_)
1999            | RoderEvent::DiscoveryCatalogBuilt(_)
2000            | RoderEvent::DiscoveryItemUpdated(_)
2001            | RoderEvent::DiscoveryAuthRequired(_)
2002            | RoderEvent::MemorySaved(_)
2003            | RoderEvent::MemoryUpdated(_)
2004            | RoderEvent::MemoryDeleted(_)
2005            | RoderEvent::MemoryQueried(_)
2006            | RoderEvent::MemoryReembedQueued(_)
2007            | RoderEvent::MemoryProviderChanged(_)
2008            | RoderEvent::KnowledgeSaved(_)
2009            | RoderEvent::KnowledgeUpdated(_)
2010            | RoderEvent::KnowledgeArchived(_)
2011            | RoderEvent::KnowledgeLinked(_)
2012            | RoderEvent::RemoteServerStarted(_)
2013            | RoderEvent::RemoteServerStopped(_)
2014            | RoderEvent::RemoteAuthFailed(_)
2015            | RoderEvent::RemoteClientConnected(_)
2016            | RoderEvent::RemoteClientDisconnected(_)
2017            | RoderEvent::RoadmapChanged(_)
2018            | RoderEvent::AutomationCreated(_)
2019            | RoderEvent::AutomationUpdated(_)
2020            | RoderEvent::AutomationDeleted(_)
2021            | RoderEvent::AutomationDue(_)
2022            | RoderEvent::AutomationLeased(_)
2023            | RoderEvent::AutomationQueued(_)
2024            | RoderEvent::AutomationSkipped(_)
2025            | RoderEvent::AutomationLeaseExpired(_)
2026            | RoderEvent::SkillsCatalogLoaded(_)
2027            | RoderEvent::SkillConfigApplied(_)
2028            | RoderEvent::RunnerLifecycle(_)
2029            | RoderEvent::TeamDisplayModeChanged(_)
2030            | RoderEvent::TeamTaskChanged(_)
2031            | RoderEvent::TeamCleanupCompleted(_) => None,
2032            RoderEvent::ProcessStarted(e) => e.process.thread_id.as_ref(),
2033            RoderEvent::ProcessOutput(e) => e.thread_id.as_ref(),
2034            RoderEvent::ProcessExited(e) => e.process.thread_id.as_ref(),
2035            RoderEvent::ProcessStopping(_) => None,
2036            RoderEvent::ProcessStopped(e) => e.process.thread_id.as_ref(),
2037            RoderEvent::ProcessFailed(e) => e.process.thread_id.as_ref(),
2038            RoderEvent::AutomationStarted(e) => e.run.thread_id.as_ref(),
2039            RoderEvent::AutomationCompleted(e) => e.run.thread_id.as_ref(),
2040            RoderEvent::AutomationFailed(e) => e.run.thread_id.as_ref(),
2041            RoderEvent::WorkflowRunDrafted(e) => e.thread_id.as_ref(),
2042            RoderEvent::WorkflowApprovalRequested(e) => e.thread_id.as_ref(),
2043            RoderEvent::WorkflowRunApproved(e) => e.thread_id.as_ref(),
2044            RoderEvent::WorkflowRunDenied(e) => e.thread_id.as_ref(),
2045            RoderEvent::WorkflowRunQueued(e) => e.thread_id.as_ref(),
2046            RoderEvent::WorkflowRunStarted(e) => e.thread_id.as_ref(),
2047            RoderEvent::WorkflowPhaseStarted(e) => e.thread_id.as_ref(),
2048            RoderEvent::WorkflowPhaseCompleted(e) => e.thread_id.as_ref(),
2049            RoderEvent::WorkflowAgentQueued(e) => e.thread_id.as_ref(),
2050            RoderEvent::WorkflowAgentStarted(e) => e.thread_id.as_ref(),
2051            RoderEvent::WorkflowAgentCompleted(e) => e.thread_id.as_ref(),
2052            RoderEvent::WorkflowAgentFailed(e) => e.thread_id.as_ref(),
2053            RoderEvent::WorkflowOutputRecorded(e) => e.thread_id.as_ref(),
2054            RoderEvent::WorkflowCheckpointRecorded(e) => e.thread_id.as_ref(),
2055            RoderEvent::WorkflowRunPaused(e) => e.thread_id.as_ref(),
2056            RoderEvent::WorkflowRunResumed(e) => e.thread_id.as_ref(),
2057            RoderEvent::WorkflowRunStopped(e) => e.thread_id.as_ref(),
2058            RoderEvent::WorkflowRunCompleted(e) => e.thread_id.as_ref(),
2059            RoderEvent::WorkflowRunFailed(e) => e.thread_id.as_ref(),
2060            RoderEvent::ReviewStarted(e) => Some(&e.thread_id),
2061            RoderEvent::ReviewCompleted(e) => Some(&e.thread_id),
2062            RoderEvent::ReviewFailed(e) => Some(&e.thread_id),
2063            RoderEvent::ReviewPublished(e) => e.thread_id.as_ref(),
2064        }
2065    }
2066
2067    pub fn turn_id(&self) -> Option<&TurnId> {
2068        match self {
2069            RoderEvent::ExtensionEventEmitted(_) | RoderEvent::EventSinkFailed(_) => None,
2070            RoderEvent::ThreadForkRequested(_)
2071            | RoderEvent::ThreadForked(_)
2072            | RoderEvent::ThreadForkFailed(_)
2073            | RoderEvent::ThreadForkRemoved(_) => None,
2074            RoderEvent::TurnStarted(e) => Some(&e.turn_id),
2075            RoderEvent::ContextAssemblyStarted(e) => Some(&e.turn_id),
2076            RoderEvent::ContextBlockAdded(e) => Some(&e.turn_id),
2077            RoderEvent::ContextAssemblyCompleted(e) => Some(&e.turn_id),
2078            RoderEvent::ContextEntrypointCandidatesInjected(e) => Some(&e.turn_id),
2079            RoderEvent::ContextCompactionStarted(e) => Some(&e.turn_id),
2080            RoderEvent::ContextCompactionRecorded(e) => Some(&e.turn_id),
2081            RoderEvent::ContextCompactionSkipped(e) => Some(&e.turn_id),
2082            RoderEvent::InferenceRoutingDecision(e) => Some(&e.turn_id),
2083            RoderEvent::InferenceStarted(e) => Some(&e.turn_id),
2084            RoderEvent::InferenceEventReceived(e) => Some(&e.turn_id),
2085            RoderEvent::ToolCallRequested(e) => Some(&e.turn_id),
2086            RoderEvent::HookRunRecorded(e) => Some(&e.turn_id),
2087            RoderEvent::ToolCallValidationRecorded(e) => Some(&e.turn_id),
2088            RoderEvent::ReliabilityFailureRecorded(e) => Some(&e.context.turn_id),
2089            RoderEvent::ReliabilityRetryRecorded(e) => Some(&e.context.turn_id),
2090            RoderEvent::ReliabilityLimitRecorded(e) => Some(&e.context.turn_id),
2091            RoderEvent::ReliabilityMetricRecorded(e) => Some(&e.context.turn_id),
2092            RoderEvent::CodeIndexingStarted(e) => e.context.turn_id.as_ref(),
2093            RoderEvent::CodeIndexChunked(e) => e.context.turn_id.as_ref(),
2094            RoderEvent::CodeIndexEmbedded(e) => e.context.turn_id.as_ref(),
2095            RoderEvent::CodeIndexReady(_) => None,
2096            RoderEvent::CodeIndexStale(e) => e.context.turn_id.as_ref(),
2097            RoderEvent::CodeIndexFailed(e) => e.context.turn_id.as_ref(),
2098            RoderEvent::CodeIndexProofFilteredResultDropped(e) => e.context.turn_id.as_ref(),
2099            RoderEvent::ApprovalRequested(e) => Some(&e.turn_id),
2100            RoderEvent::ApprovalResolved(e) => Some(&e.turn_id),
2101            RoderEvent::ExternalToolCallRequested(e) => Some(&e.turn_id),
2102            RoderEvent::ExternalToolCallResolved(e) => Some(&e.turn_id),
2103            RoderEvent::UserInputRequested(e) => Some(&e.turn_id),
2104            RoderEvent::UserInputResolved(e) => Some(&e.turn_id),
2105            RoderEvent::TaskLedgerUpdated(e) => Some(&e.turn_id),
2106            RoderEvent::VerificationRequired(e) => Some(&e.turn_id),
2107            RoderEvent::VerificationCompleted(e) => Some(&e.turn_id),
2108            RoderEvent::VerificationSkipped(e) => Some(&e.turn_id),
2109            RoderEvent::PolicyDecisionRecorded(e) => Some(&e.turn_id),
2110            RoderEvent::PolicyBypassActive(e) => Some(&e.turn_id),
2111            RoderEvent::PolicyModeChanged(e) => e.turn_id.as_ref(),
2112            RoderEvent::AgentSwarmModeChanged(e) => e.turn_id.as_ref(),
2113            RoderEvent::AgentSwarmStarted(e) => Some(&e.turn_id),
2114            RoderEvent::AgentSwarmProgress(e) => Some(&e.turn_id),
2115            RoderEvent::AgentSwarmCompleted(e) => Some(&e.turn_id),
2116            RoderEvent::UltraModeChanged(e) => e.turn_id.as_ref(),
2117            RoderEvent::PolicyExitPlanRequested(e) => Some(&e.turn_id),
2118            RoderEvent::PolicyExitPlanResolved(e) => Some(&e.turn_id),
2119            RoderEvent::ToolCallStarted(e) => Some(&e.turn_id),
2120            RoderEvent::ToolCallCompleted(e) => Some(&e.turn_id),
2121            RoderEvent::ToolOutputTruncated(e) => Some(&e.turn_id),
2122            RoderEvent::SubagentStarted(e) => Some(&e.turn_id),
2123            RoderEvent::SubagentMessage(e) => Some(&e.turn_id),
2124            RoderEvent::SubagentToolCall(e) => Some(&e.turn_id),
2125            RoderEvent::SubagentCompleted(e) => Some(&e.turn_id),
2126            RoderEvent::SubagentFailed(e) => Some(&e.turn_id),
2127            RoderEvent::SubagentTraceCreated(e) => Some(&e.summary.parent.turn_id),
2128            RoderEvent::SubagentTraceDelta(e) => Some(&e.delta.parent.turn_id),
2129            RoderEvent::SubagentTraceStatusChanged(e) => Some(&e.parent.turn_id),
2130            RoderEvent::SubagentTraceCompleted(e) => Some(&e.summary.parent.turn_id),
2131            RoderEvent::SubagentTraceFailed(e) => Some(&e.summary.parent.turn_id),
2132            RoderEvent::PlanReviewCreated(e) => Some(&e.review.turn_id),
2133            RoderEvent::PlanReviewStatusChanged(e) => Some(&e.turn_id),
2134            RoderEvent::PlanReviewCommentAdded(e) => Some(&e.turn_id),
2135            RoderEvent::PlanReviewRewritten(e) => Some(&e.turn_id),
2136            RoderEvent::PlanReviewApproved(e) => Some(&e.turn_id),
2137            RoderEvent::PlanReviewRejected(e) => Some(&e.turn_id),
2138            RoderEvent::HunkRecorded(e) => Some(&e.hunk.turn_id),
2139            RoderEvent::WorkspaceChangeObserved(e) => Some(&e.change.turn_id),
2140            RoderEvent::HunkRollbackRequested(e) => Some(&e.turn_id),
2141            RoderEvent::HunkRollbackCompleted(e) => Some(&e.turn_id),
2142            RoderEvent::MediaArtifactCreated(e) => Some(&e.turn_id),
2143            RoderEvent::MediaArtifactUpdated(e) => Some(&e.turn_id),
2144            RoderEvent::MediaPreviewReady(e) => Some(&e.turn_id),
2145            RoderEvent::ContextArtifactCreated(e) => Some(&e.turn_id),
2146            RoderEvent::ContextArtifactAppended(e) => Some(&e.turn_id),
2147            RoderEvent::ContextArtifactCapped(e) => Some(&e.turn_id),
2148            RoderEvent::DiscoveryItemRead(e) => Some(&e.turn_id),
2149            RoderEvent::DiscoveryItemPromoted(e) => e.record.turn_id.as_ref(),
2150            RoderEvent::DiscoveryPromotionReused(e) => e.record.turn_id.as_ref(),
2151            RoderEvent::DiscoveryWarmCacheHit(e) => e.record.turn_id.as_ref(),
2152            RoderEvent::DiscoveryPromotionExpired(e) => e.record.turn_id.as_ref(),
2153            RoderEvent::RetrievalRoutePlanned(e) => Some(&e.plan.turn_id),
2154            RoderEvent::RetrievalRouteAccepted(e) => Some(&e.turn_id),
2155            RoderEvent::RetrievalRouteIgnored(e) => Some(&e.turn_id),
2156            RoderEvent::RetrievalRouteFailed(e) => Some(&e.turn_id),
2157            RoderEvent::RetrievalResultUsed(e) => Some(&e.turn_id),
2158            RoderEvent::RetrievalDiscoveryItemPromoted(e) => Some(&e.turn_id),
2159            RoderEvent::RetrievalPromotionSkipped(e) => Some(&e.turn_id),
2160            RoderEvent::MemoryRecallReady(e) => Some(&e.turn_id),
2161            RoderEvent::MemoryObservationRecorded(e) => Some(&e.turn_id),
2162            RoderEvent::TaskStarted(e) => e.turn_id.as_ref(),
2163            RoderEvent::TaskOutput(e) => e.turn_id.as_ref(),
2164            RoderEvent::TaskCompleted(e) => e.turn_id.as_ref(),
2165            RoderEvent::TaskFailed(e) => e.turn_id.as_ref(),
2166            RoderEvent::TaskCancelled(e) => e.turn_id.as_ref(),
2167            RoderEvent::FileChangePreviewReady(e) => Some(&e.turn_id),
2168            RoderEvent::FileChanged(e) => Some(&e.turn_id),
2169            RoderEvent::TranscriptItemAppended(e) => Some(&e.turn_id),
2170            RoderEvent::TurnCompleted(e) => Some(&e.turn_id),
2171            RoderEvent::TurnFailed(e) => Some(&e.turn_id),
2172            RoderEvent::TurnPartialResult(e) => Some(&e.turn_id),
2173            RoderEvent::TurnDeadlineExceeded(e) => Some(&e.turn_id),
2174            RoderEvent::TurnLifecycleUpdated(e) => Some(&e.turn_id),
2175            RoderEvent::TurnInterrupted(e) => Some(&e.turn_id),
2176            RoderEvent::TurnSteered(e) => Some(&e.turn_id),
2177            RoderEvent::TeamMemberMessageDelta(e) => Some(&e.turn_id),
2178            RoderEvent::TeamMemberCompleted(e) => e.turn_id.as_ref(),
2179            RoderEvent::SkillActivationResolved(e) => Some(&e.turn_id),
2180            RoderEvent::SkillIndexRendered(e) => Some(&e.turn_id),
2181            RoderEvent::SkillInvoked(e) => Some(&e.turn_id),
2182            RoderEvent::SkillAutoActivated(e) => Some(&e.turn_id),
2183            RoderEvent::SkillSkipped(e) => Some(&e.turn_id),
2184            RoderEvent::RuntimeStarted(_)
2185            | RoderEvent::ExtensionRegistered(_)
2186            | RoderEvent::ThreadCreated(_)
2187            | RoderEvent::ThreadLoaded(_)
2188            | RoderEvent::WorkflowImportsDetected(_)
2189            | RoderEvent::WorkflowImportPreviewed(_)
2190            | RoderEvent::WorkflowImportEnabled(_)
2191            | RoderEvent::WorkflowImportDisabled(_)
2192            | RoderEvent::WorkflowImportStale(_)
2193            | RoderEvent::WorkflowImportFailed(_)
2194            | RoderEvent::MediaArtifactDeleted(_)
2195            | RoderEvent::ContextArtifactDeleted(_)
2196            | RoderEvent::ContextArtifactRetentionExpired(_)
2197            | RoderEvent::DiscoveryCatalogBuilt(_)
2198            | RoderEvent::DiscoveryItemUpdated(_)
2199            | RoderEvent::DiscoveryAuthRequired(_)
2200            | RoderEvent::MemorySaved(_)
2201            | RoderEvent::MemoryUpdated(_)
2202            | RoderEvent::MemoryDeleted(_)
2203            | RoderEvent::MemoryQueried(_)
2204            | RoderEvent::MemoryReembedQueued(_)
2205            | RoderEvent::MemoryProviderChanged(_)
2206            | RoderEvent::KnowledgeSaved(_)
2207            | RoderEvent::KnowledgeUpdated(_)
2208            | RoderEvent::KnowledgeArchived(_)
2209            | RoderEvent::KnowledgeLinked(_)
2210            | RoderEvent::RemoteServerStarted(_)
2211            | RoderEvent::RemoteServerStopped(_)
2212            | RoderEvent::RemoteAuthFailed(_)
2213            | RoderEvent::RemoteClientConnected(_)
2214            | RoderEvent::RemoteClientDisconnected(_)
2215            | RoderEvent::ThreadGoalUpdated(_)
2216            | RoderEvent::ThreadGoalCleared(_)
2217            | RoderEvent::RoadmapChanged(_)
2218            | RoderEvent::AutomationCreated(_)
2219            | RoderEvent::AutomationUpdated(_)
2220            | RoderEvent::AutomationDeleted(_)
2221            | RoderEvent::AutomationDue(_)
2222            | RoderEvent::AutomationLeased(_)
2223            | RoderEvent::AutomationQueued(_)
2224            | RoderEvent::AutomationSkipped(_)
2225            | RoderEvent::AutomationLeaseExpired(_)
2226            | RoderEvent::SkillsCatalogLoaded(_)
2227            | RoderEvent::SkillConfigApplied(_)
2228            | RoderEvent::RunnerLifecycle(_)
2229            | RoderEvent::TeamStarted(_)
2230            | RoderEvent::TeamMemberStarted(_)
2231            | RoderEvent::TeamMemberStatusChanged(_)
2232            | RoderEvent::TeamDisplayModeChanged(_)
2233            | RoderEvent::TeamTaskChanged(_)
2234            | RoderEvent::TeamCleanupCompleted(_) => None,
2235            RoderEvent::ProcessStarted(e) => e.process.turn_id.as_ref(),
2236            RoderEvent::ProcessOutput(e) => e.turn_id.as_ref(),
2237            RoderEvent::ProcessExited(e) => e.process.turn_id.as_ref(),
2238            RoderEvent::ProcessStopping(_) => None,
2239            RoderEvent::ProcessStopped(e) => e.process.turn_id.as_ref(),
2240            RoderEvent::ProcessFailed(e) => e.process.turn_id.as_ref(),
2241            RoderEvent::AutomationStarted(e) => e.run.turn_id.as_ref(),
2242            RoderEvent::AutomationCompleted(e) => e.run.turn_id.as_ref(),
2243            RoderEvent::AutomationFailed(e) => e.run.turn_id.as_ref(),
2244            RoderEvent::WorkflowRunDrafted(e) => e.turn_id.as_ref(),
2245            RoderEvent::WorkflowApprovalRequested(e) => e.turn_id.as_ref(),
2246            RoderEvent::WorkflowRunApproved(e) => e.turn_id.as_ref(),
2247            RoderEvent::WorkflowRunDenied(e) => e.turn_id.as_ref(),
2248            RoderEvent::WorkflowRunQueued(e) => e.turn_id.as_ref(),
2249            RoderEvent::WorkflowRunStarted(e) => e.turn_id.as_ref(),
2250            RoderEvent::WorkflowPhaseStarted(e) => e.turn_id.as_ref(),
2251            RoderEvent::WorkflowPhaseCompleted(e) => e.turn_id.as_ref(),
2252            RoderEvent::WorkflowAgentQueued(e) => e.turn_id.as_ref(),
2253            RoderEvent::WorkflowAgentStarted(e) => e.turn_id.as_ref(),
2254            RoderEvent::WorkflowAgentCompleted(e) => e.turn_id.as_ref(),
2255            RoderEvent::WorkflowAgentFailed(e) => e.turn_id.as_ref(),
2256            RoderEvent::WorkflowOutputRecorded(e) => e.turn_id.as_ref(),
2257            RoderEvent::WorkflowCheckpointRecorded(e) => e.turn_id.as_ref(),
2258            RoderEvent::WorkflowRunPaused(e) => e.turn_id.as_ref(),
2259            RoderEvent::WorkflowRunResumed(e) => e.turn_id.as_ref(),
2260            RoderEvent::WorkflowRunStopped(e) => e.turn_id.as_ref(),
2261            RoderEvent::WorkflowRunCompleted(e) => e.turn_id.as_ref(),
2262            RoderEvent::WorkflowRunFailed(e) => e.turn_id.as_ref(),
2263            RoderEvent::ReviewStarted(e) => Some(&e.turn_id),
2264            RoderEvent::ReviewCompleted(e) => Some(&e.turn_id),
2265            RoderEvent::ReviewFailed(e) => e.turn_id.as_ref(),
2266            RoderEvent::ReviewPublished(_) => None,
2267        }
2268    }
2269}
2270
2271#[derive(Debug, Clone, Serialize, Deserialize)]
2272pub struct EventEnvelope {
2273    pub event_id: EventId,
2274    pub seq: u64,
2275    #[serde(with = "time::serde::rfc3339")]
2276    pub timestamp: OffsetDateTime,
2277    pub source: EventSource,
2278    pub kind: String,
2279    pub thread_id: Option<ThreadId>,
2280    pub turn_id: Option<TurnId>,
2281    pub event: RoderEvent,
2282}
2283
2284impl EventEnvelope {
2285    pub fn matches_filter(&self, filter: &EventFilter) -> bool {
2286        filter.matches(self)
2287    }
2288}
2289
2290#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
2291pub struct EventFilter {
2292    pub thread_id: Option<ThreadId>,
2293    pub turn_id: Option<TurnId>,
2294    pub source: Option<EventSource>,
2295    pub kinds: Vec<String>,
2296}
2297
2298impl EventFilter {
2299    pub fn for_thread(thread_id: impl Into<ThreadId>) -> Self {
2300        Self {
2301            thread_id: Some(thread_id.into()),
2302            ..Self::default()
2303        }
2304    }
2305
2306    pub fn for_turn(thread_id: impl Into<ThreadId>, turn_id: impl Into<TurnId>) -> Self {
2307        Self {
2308            thread_id: Some(thread_id.into()),
2309            turn_id: Some(turn_id.into()),
2310            ..Self::default()
2311        }
2312    }
2313
2314    pub fn with_source(mut self, source: EventSource) -> Self {
2315        self.source = Some(source);
2316        self
2317    }
2318
2319    pub fn with_kind(mut self, kind: impl Into<String>) -> Self {
2320        self.kinds.push(kind.into());
2321        self
2322    }
2323
2324    pub fn matches(&self, envelope: &EventEnvelope) -> bool {
2325        if self
2326            .thread_id
2327            .as_ref()
2328            .is_some_and(|thread_id| envelope.thread_id.as_ref() != Some(thread_id))
2329        {
2330            return false;
2331        }
2332        if self
2333            .turn_id
2334            .as_ref()
2335            .is_some_and(|turn_id| envelope.turn_id.as_ref() != Some(turn_id))
2336        {
2337            return false;
2338        }
2339        if self
2340            .source
2341            .as_ref()
2342            .is_some_and(|source| &envelope.source != source)
2343        {
2344            return false;
2345        }
2346        if !self.kinds.is_empty() && !self.kinds.iter().any(|kind| kind == &envelope.kind) {
2347            return false;
2348        }
2349        true
2350    }
2351}
2352
2353#[cfg(test)]
2354mod tests {
2355    use super::*;
2356
2357    fn envelope(thread_id: Option<&str>, turn_id: Option<&str>, kind: &str) -> EventEnvelope {
2358        EventEnvelope {
2359            event_id: "event-1".to_string(),
2360            seq: 1,
2361            timestamp: OffsetDateTime::UNIX_EPOCH,
2362            source: EventSource::Core,
2363            kind: kind.to_string(),
2364            thread_id: thread_id.map(str::to_string),
2365            turn_id: turn_id.map(str::to_string),
2366            event: RoderEvent::TurnStarted(TurnStarted {
2367                thread_id: thread_id.unwrap_or("thread-a").to_string(),
2368                turn_id: turn_id.unwrap_or("turn-a").to_string(),
2369                runtime_profile: RuntimeProfile::Interactive,
2370                timestamp: OffsetDateTime::UNIX_EPOCH,
2371            }),
2372        }
2373    }
2374
2375    #[test]
2376    fn event_filter_matches_thread_turn_source_and_kind() {
2377        let envelope = envelope(Some("thread-a"), Some("turn-a"), "turn.started");
2378        let filter = EventFilter::for_turn("thread-a", "turn-a")
2379            .with_source(EventSource::Core)
2380            .with_kind("turn.started");
2381
2382        assert!(filter.matches(&envelope));
2383        assert!(envelope.matches_filter(&filter));
2384        assert!(!EventFilter::for_thread("thread-b").matches(&envelope));
2385        assert!(!EventFilter::for_turn("thread-a", "turn-b").matches(&envelope));
2386        assert!(
2387            !EventFilter::default()
2388                .with_source(EventSource::Provider)
2389                .matches(&envelope)
2390        );
2391        assert!(
2392            !EventFilter::default()
2393                .with_kind("turn.completed")
2394                .matches(&envelope)
2395        );
2396    }
2397
2398    #[test]
2399    fn empty_event_filter_matches_everything() {
2400        assert!(EventFilter::default().matches(&envelope(None, None, "runtime.started")));
2401    }
2402
2403    #[test]
2404    fn code_index_event_kind_and_scope_are_visible() {
2405        let event = RoderEvent::CodeIndexProofFilteredResultDropped(
2406            crate::code_index::CodeIndexProofFilteredResultDropped {
2407                context: crate::code_index::CodeIndexEventContext {
2408                    workspace_root: std::path::PathBuf::from("/repo"),
2409                    generation_id: Some("gen-1".to_string()),
2410                    thread_id: Some("thread-1".to_string()),
2411                    turn_id: Some("turn-1".to_string()),
2412                },
2413                drop: crate::code_index::ProofFilteredDrop {
2414                    query_id: "query-1".to_string(),
2415                    path_hash: "path-hash".to_string(),
2416                    content_hash: "content-hash".to_string(),
2417                    reason: "proof missing".to_string(),
2418                },
2419                timestamp: OffsetDateTime::UNIX_EPOCH,
2420            },
2421        );
2422
2423        assert_eq!(event.kind(), "code_index.proof_filtered_result_dropped");
2424        assert_eq!(event.thread_id().map(String::as_str), Some("thread-1"));
2425        assert_eq!(event.turn_id().map(String::as_str), Some("turn-1"));
2426        assert_eq!(event.source(), EventSource::Core);
2427    }
2428
2429    #[test]
2430    fn workflow_event_kind_scope_and_agent_metadata_are_visible() {
2431        let agent = crate::dynamic_workflows::WorkflowAgentRun {
2432            agent_id: "agent-1".to_string(),
2433            phase_id: "phase-1".to_string(),
2434            description: "Review findings".to_string(),
2435            status: crate::dynamic_workflows::WorkflowAgentStatus::Completed,
2436            lane: Some(crate::subagents::SubagentLane::Reviewer),
2437            model: Some("mock-model".to_string()),
2438            thread_id: Some("child-thread".to_string()),
2439            turn_id: Some("child-turn".to_string()),
2440            usage: Some(crate::inference::TokenUsage::new(10, 5, 15)),
2441            exit_reason: None,
2442            error: None,
2443            started_at: Some(OffsetDateTime::UNIX_EPOCH),
2444            completed_at: Some(OffsetDateTime::UNIX_EPOCH),
2445        };
2446        let event = RoderEvent::WorkflowAgentCompleted(WorkflowAgentCompleted {
2447            run_id: "run-1".to_string(),
2448            thread_id: Some("thread-1".to_string()),
2449            turn_id: Some("turn-1".to_string()),
2450            agent,
2451            timestamp: OffsetDateTime::UNIX_EPOCH,
2452        });
2453
2454        assert_eq!(event.kind(), "workflows/agentCompleted");
2455        assert_eq!(event.source(), EventSource::Core);
2456        assert_eq!(event.thread_id().map(String::as_str), Some("thread-1"));
2457        assert_eq!(event.turn_id().map(String::as_str), Some("turn-1"));
2458
2459        let value = serde_json::to_value(&event).unwrap();
2460        let event_value = &value["WorkflowAgentCompleted"];
2461        assert_eq!(event_value["runId"], "run-1");
2462        assert_eq!(event_value["agent"]["phaseId"], "phase-1");
2463        assert_eq!(event_value["agent"]["agentId"], "agent-1");
2464        assert_eq!(event_value["agent"]["status"], "completed");
2465        assert_eq!(event_value["agent"]["usage"]["total_tokens"], 15);
2466    }
2467
2468    #[test]
2469    fn event_timestamps_serialize_as_rfc3339_strings() {
2470        let value =
2471            serde_json::to_value(envelope(Some("thread-a"), Some("turn-a"), "turn.started"))
2472                .unwrap();
2473
2474        assert_eq!(value["timestamp"], "1970-01-01T00:00:00Z");
2475        assert_eq!(
2476            value["event"]["TurnStarted"]["timestamp"],
2477            "1970-01-01T00:00:00Z"
2478        );
2479    }
2480
2481    #[test]
2482    fn subagent_event_envelope_round_trips_parent_ids() {
2483        let event = RoderEvent::SubagentStarted(SubagentStarted {
2484            thread_id: "child-thread".to_string(),
2485            turn_id: "child-turn".to_string(),
2486            parent_thread_id: "parent-thread".to_string(),
2487            parent_turn_id: "parent-turn".to_string(),
2488            agent_type: "explore".to_string(),
2489            description: "Inspect repository".to_string(),
2490            model: Some("test-model".to_string()),
2491            timestamp: OffsetDateTime::UNIX_EPOCH,
2492        });
2493        let envelope = EventEnvelope {
2494            event_id: "event-subagent-started".to_string(),
2495            seq: 7,
2496            timestamp: OffsetDateTime::UNIX_EPOCH,
2497            source: event.source(),
2498            kind: event.kind().to_string(),
2499            thread_id: event.thread_id().cloned(),
2500            turn_id: event.turn_id().cloned(),
2501            event,
2502        };
2503
2504        let serialized = serde_json::to_string(&envelope).unwrap();
2505        let round_trip: EventEnvelope = serde_json::from_str(&serialized).unwrap();
2506
2507        assert_eq!(round_trip.kind, "subagent.started");
2508        assert_eq!(round_trip.source, EventSource::Extension);
2509        assert_eq!(round_trip.thread_id.as_deref(), Some("child-thread"));
2510        assert_eq!(round_trip.turn_id.as_deref(), Some("child-turn"));
2511
2512        match round_trip.event {
2513            RoderEvent::SubagentStarted(started) => {
2514                assert_eq!(started.parent_thread_id, "parent-thread");
2515                assert_eq!(started.parent_turn_id, "parent-turn");
2516            }
2517            other => panic!("unexpected event: {other:?}"),
2518        }
2519    }
2520
2521    #[test]
2522    fn subagent_trace_event_uses_parent_turn_for_filtering() {
2523        let summary = SubagentTraceSummary {
2524            trace_id: "trace-1".to_string(),
2525            parent: ParentTurnRef {
2526                thread_id: "parent-thread".to_string(),
2527                turn_id: "parent-turn".to_string(),
2528            },
2529            child_thread_id: "child-thread".to_string(),
2530            child_turn_id: "child-turn".to_string(),
2531            title: "Inspect repository".to_string(),
2532            role: "explorer".to_string(),
2533            model: Some("test-model".to_string()),
2534            lane: None,
2535            status: SubagentTraceStatus::Running,
2536            elapsed_ms: 10,
2537            usage: None,
2538            destination: None,
2539            latest_activity: None,
2540            error_summary: None,
2541            exit_reason: None,
2542        };
2543        let event = RoderEvent::SubagentTraceCreated(SubagentTraceCreated {
2544            summary,
2545            timestamp: OffsetDateTime::UNIX_EPOCH,
2546        });
2547
2548        assert_eq!(event.kind(), "turn/subagentTraceCreated");
2549        assert_eq!(event.source(), EventSource::Extension);
2550        assert_eq!(event.thread_id().map(String::as_str), Some("parent-thread"));
2551        assert_eq!(event.turn_id().map(String::as_str), Some("parent-turn"));
2552    }
2553
2554    #[test]
2555    fn task_events_round_trip_with_replay_ids() {
2556        let event = RoderEvent::TaskOutput(TaskOutput {
2557            task_id: "task-1".to_string(),
2558            stream: crate::tasks::TaskOutputStream::Stdout,
2559            chunk: "building\n".to_string(),
2560            dropped_bytes: 0,
2561            thread_id: Some("thread-a".to_string()),
2562            turn_id: Some("turn-a".to_string()),
2563            timestamp: OffsetDateTime::UNIX_EPOCH,
2564        });
2565        let envelope = EventEnvelope {
2566            event_id: "event-task-output".to_string(),
2567            seq: 9,
2568            timestamp: OffsetDateTime::UNIX_EPOCH,
2569            source: event.source(),
2570            kind: event.kind().to_string(),
2571            thread_id: event.thread_id().cloned(),
2572            turn_id: event.turn_id().cloned(),
2573            event,
2574        };
2575
2576        let serialized = serde_json::to_string(&envelope).unwrap();
2577        let round_trip: EventEnvelope = serde_json::from_str(&serialized).unwrap();
2578
2579        assert_eq!(round_trip.kind, "task.output");
2580        assert_eq!(round_trip.source, EventSource::Extension);
2581        assert_eq!(round_trip.thread_id.as_deref(), Some("thread-a"));
2582        assert_eq!(round_trip.turn_id.as_deref(), Some("turn-a"));
2583        match round_trip.event {
2584            RoderEvent::TaskOutput(output) => {
2585                assert_eq!(output.task_id, "task-1");
2586                assert_eq!(output.stream, crate::tasks::TaskOutputStream::Stdout);
2587                assert_eq!(output.chunk, "building\n");
2588            }
2589            other => panic!("unexpected event: {other:?}"),
2590        }
2591    }
2592
2593    #[test]
2594    fn automations_event_exposes_kind_source_and_replay_scope() {
2595        let run = crate::automations::AutomationRunSummary {
2596            run_id: "run-1".to_string(),
2597            automation_id: "automation-1".to_string(),
2598            occurrence_key: "automation-1:1970-01-01T00:00:00Z".to_string(),
2599            state: crate::automations::AutomationRunState::Completed,
2600            scheduled_for: OffsetDateTime::UNIX_EPOCH,
2601            queued_at: Some(OffsetDateTime::UNIX_EPOCH),
2602            started_at: Some(OffsetDateTime::UNIX_EPOCH),
2603            finished_at: Some(OffsetDateTime::UNIX_EPOCH),
2604            thread_id: Some("thread-a".to_string()),
2605            turn_id: Some("turn-a".to_string()),
2606            task_id: Some("task-1".to_string()),
2607            server_id: Some("desktop-main".to_string()),
2608            server_role: Some("desktop".to_string()),
2609            exit_code: Some(0),
2610            error: None,
2611            skip_reason: None,
2612        };
2613        let event = RoderEvent::AutomationCompleted(crate::automations::AutomationCompleted {
2614            run,
2615            timestamp: OffsetDateTime::UNIX_EPOCH,
2616        });
2617
2618        assert_eq!(event.kind(), "automations/completed");
2619        assert_eq!(event.source(), EventSource::Core);
2620        assert_eq!(event.thread_id().map(String::as_str), Some("thread-a"));
2621        assert_eq!(event.turn_id().map(String::as_str), Some("turn-a"));
2622
2623        let value = serde_json::to_value(&event).unwrap();
2624        assert_eq!(
2625            value["AutomationCompleted"]["run"]["occurrenceKey"],
2626            "automation-1:1970-01-01T00:00:00Z"
2627        );
2628        assert_eq!(
2629            value["AutomationCompleted"]["run"]["serverId"],
2630            "desktop-main"
2631        );
2632    }
2633
2634    #[test]
2635    fn tool_call_completed_round_trips_error_status() {
2636        let event = RoderEvent::ToolCallCompleted(ToolCallCompleted {
2637            thread_id: "thread-a".to_string(),
2638            turn_id: "turn-a".to_string(),
2639            tool_id: "tool-a".to_string(),
2640            tool_name: Some("list_files".to_string()),
2641            display_payload: Some(serde_json::json!({ "path": "." })),
2642            is_error: true,
2643            output: Some("tool failed".to_string()),
2644            timestamp: OffsetDateTime::UNIX_EPOCH,
2645        });
2646        let envelope = EventEnvelope {
2647            event_id: "event-tool-completed".to_string(),
2648            seq: 10,
2649            timestamp: OffsetDateTime::UNIX_EPOCH,
2650            source: event.source(),
2651            kind: event.kind().to_string(),
2652            thread_id: event.thread_id().cloned(),
2653            turn_id: event.turn_id().cloned(),
2654            event,
2655        };
2656
2657        let serialized = serde_json::to_string(&envelope).unwrap();
2658        let round_trip: EventEnvelope = serde_json::from_str(&serialized).unwrap();
2659
2660        assert_eq!(round_trip.kind, "tool.call_completed");
2661        match round_trip.event {
2662            RoderEvent::ToolCallCompleted(completed) => {
2663                assert_eq!(completed.tool_id, "tool-a");
2664                assert!(completed.is_error);
2665                assert_eq!(completed.output.as_deref(), Some("tool failed"));
2666            }
2667            other => panic!("unexpected event: {other:?}"),
2668        }
2669    }
2670
2671    #[test]
2672    fn file_change_preview_event_round_trips_public_metadata() {
2673        let event = RoderEvent::FileChangePreviewReady(FileChangePreviewReady {
2674            thread_id: "thread-a".to_string(),
2675            turn_id: "turn-a".to_string(),
2676            tool_id: "tool-a".to_string(),
2677            tool_name: "edit".to_string(),
2678            path: "src/lib.rs".to_string(),
2679            change_type: "modify".to_string(),
2680            before: Some("old\n".to_string()),
2681            after: "new\n".to_string(),
2682            supports_partial: false,
2683            timestamp: OffsetDateTime::UNIX_EPOCH,
2684        });
2685        let envelope = EventEnvelope {
2686            event_id: "event-file-preview".to_string(),
2687            seq: 8,
2688            timestamp: OffsetDateTime::UNIX_EPOCH,
2689            source: event.source(),
2690            kind: event.kind().to_string(),
2691            thread_id: event.thread_id().cloned(),
2692            turn_id: event.turn_id().cloned(),
2693            event,
2694        };
2695
2696        let serialized = serde_json::to_string(&envelope).unwrap();
2697        let round_trip: EventEnvelope = serde_json::from_str(&serialized).unwrap();
2698
2699        assert_eq!(round_trip.kind, "file.change_preview_ready");
2700        assert_eq!(round_trip.source, EventSource::Tool);
2701        assert_eq!(round_trip.thread_id.as_deref(), Some("thread-a"));
2702        assert_eq!(round_trip.turn_id.as_deref(), Some("turn-a"));
2703        match round_trip.event {
2704            RoderEvent::FileChangePreviewReady(preview) => {
2705                assert_eq!(preview.tool_id, "tool-a");
2706                assert_eq!(preview.tool_name, "edit");
2707                assert_eq!(preview.path, "src/lib.rs");
2708                assert_eq!(preview.change_type, "modify");
2709                assert_eq!(preview.before.as_deref(), Some("old\n"));
2710                assert_eq!(preview.after, "new\n");
2711                assert!(!preview.supports_partial);
2712            }
2713            other => panic!("unexpected event: {other:?}"),
2714        }
2715    }
2716
2717    #[test]
2718    fn inference_started_deserializes_older_records_without_model_fields() {
2719        let value = serde_json::json!({
2720            "InferenceStarted": {
2721                "thread_id": "thread-a",
2722                "turn_id": "turn-a",
2723                "engine_id": "mock",
2724                "timestamp": "1970-01-01T00:00:00Z"
2725            }
2726        });
2727
2728        let event: RoderEvent = serde_json::from_value(value).unwrap();
2729
2730        match event {
2731            RoderEvent::InferenceStarted(started) => {
2732                assert_eq!(started.model.provider, "");
2733                assert_eq!(started.model.model, "");
2734                assert_eq!(started.reasoning, ReasoningConfig::default());
2735            }
2736            other => panic!("expected inference started, got {other:?}"),
2737        }
2738    }
2739
2740    #[test]
2741    fn team_member_completed_round_trips_optional_result_details() {
2742        let event = RoderEvent::TeamMemberCompleted(TeamMemberCompleted {
2743            team_id: "team-a".to_string(),
2744            member_id: "member-a".to_string(),
2745            member_thread_id: "thread-a".to_string(),
2746            turn_id: Some("turn-a".to_string()),
2747            status: TeamMemberStatus::Failed,
2748            final_message: Some("Partial review result.".to_string()),
2749            error: Some("provider request failed".to_string()),
2750            timestamp: OffsetDateTime::UNIX_EPOCH,
2751        });
2752
2753        let value = serde_json::to_value(&event).unwrap();
2754        assert_eq!(
2755            value["TeamMemberCompleted"]["final_message"],
2756            "Partial review result."
2757        );
2758        assert_eq!(
2759            value["TeamMemberCompleted"]["error"],
2760            "provider request failed"
2761        );
2762        let round_trip: RoderEvent = serde_json::from_value(value).unwrap();
2763        match round_trip {
2764            RoderEvent::TeamMemberCompleted(completed) => {
2765                assert_eq!(
2766                    completed.final_message.as_deref(),
2767                    Some("Partial review result.")
2768                );
2769                assert_eq!(completed.error.as_deref(), Some("provider request failed"));
2770            }
2771            other => panic!("expected team completion, got {other:?}"),
2772        }
2773
2774        let legacy: RoderEvent = serde_json::from_value(serde_json::json!({
2775            "TeamMemberCompleted": {
2776                "team_id": "team-a",
2777                "member_id": "member-a",
2778                "member_thread_id": "thread-a",
2779                "turn_id": "turn-a",
2780                "status": "completed",
2781                "timestamp": "1970-01-01T00:00:00Z"
2782            }
2783        }))
2784        .unwrap();
2785        match legacy {
2786            RoderEvent::TeamMemberCompleted(completed) => {
2787                assert!(completed.final_message.is_none());
2788                assert!(completed.error.is_none());
2789            }
2790            other => panic!("expected team completion, got {other:?}"),
2791        }
2792    }
2793
2794    #[test]
2795    fn processes_events_expose_kind_source_scope_and_round_trip() {
2796        let descriptor = crate::processes::ProcessDescriptor {
2797            process_id: "process-1".to_string(),
2798            origin: crate::processes::ProcessOrigin::CommandExec,
2799            state: crate::processes::ProcessState::Running,
2800            command: vec!["sleep".to_string(), "10".to_string()],
2801            command_summary: "sleep 10".to_string(),
2802            cwd: Some("/repo".to_string()),
2803            pid: Some(1234),
2804            task_id: Some("task-1".to_string()),
2805            thread_id: Some("thread-a".to_string()),
2806            turn_id: Some("turn-a".to_string()),
2807            runner_destination_id: None,
2808            runner_session_id: None,
2809            stoppable: true,
2810            started_at: OffsetDateTime::UNIX_EPOCH,
2811            updated_at: OffsetDateTime::UNIX_EPOCH,
2812            stdout_tail: None,
2813            stderr_tail: None,
2814        };
2815        let event = RoderEvent::ProcessStarted(crate::processes::ProcessStarted {
2816            process: descriptor,
2817            timestamp: OffsetDateTime::UNIX_EPOCH,
2818        });
2819        let envelope = EventEnvelope {
2820            event_id: "event-process-started".to_string(),
2821            seq: 11,
2822            timestamp: OffsetDateTime::UNIX_EPOCH,
2823            source: event.source(),
2824            kind: event.kind().to_string(),
2825            thread_id: event.thread_id().cloned(),
2826            turn_id: event.turn_id().cloned(),
2827            event,
2828        };
2829
2830        let serialized = serde_json::to_string(&envelope).unwrap();
2831        let round_trip: EventEnvelope = serde_json::from_str(&serialized).unwrap();
2832
2833        assert_eq!(round_trip.kind, "process.started");
2834        assert_eq!(round_trip.source, EventSource::Extension);
2835        assert_eq!(round_trip.thread_id.as_deref(), Some("thread-a"));
2836        assert_eq!(round_trip.turn_id.as_deref(), Some("turn-a"));
2837        match round_trip.event {
2838            RoderEvent::ProcessStarted(started) => {
2839                assert_eq!(started.process.process_id, "process-1");
2840                assert_eq!(started.process.pid, Some(1234));
2841            }
2842            other => panic!("unexpected event: {other:?}"),
2843        }
2844
2845        let output = RoderEvent::ProcessOutput(crate::processes::ProcessOutput {
2846            process_id: "process-1".to_string(),
2847            stream: crate::tasks::TaskOutputStream::Stdout,
2848            chunk: "ready\n".to_string(),
2849            dropped_bytes: 0,
2850            thread_id: Some("thread-a".to_string()),
2851            turn_id: Some("turn-a".to_string()),
2852            timestamp: OffsetDateTime::UNIX_EPOCH,
2853        });
2854        assert_eq!(output.kind(), "process.output");
2855        assert_eq!(output.thread_id().map(String::as_str), Some("thread-a"));
2856        assert_eq!(output.turn_id().map(String::as_str), Some("turn-a"));
2857    }
2858
2859    #[test]
2860    fn skill_activation_event_exposes_kind_source_and_turn_scope() {
2861        let descriptor = crate::skills::SkillDescriptor {
2862            id: "builtin:commit".to_string(),
2863            name: "commit".to_string(),
2864            canonical_path: "roder-builtin://commit/SKILL.md".to_string(),
2865            source: crate::skills::SkillSource::BuiltIn,
2866            exposure: crate::skills::SkillExposure::DirectOnly,
2867            activation: crate::skills::SkillActivationState::Enabled,
2868            description: "Commit staged changes safely.".to_string(),
2869            short_description: Some("Commit safely".to_string()),
2870            experimental: false,
2871            diagnostics: Vec::new(),
2872            agent_metadata: None,
2873        };
2874        let event = RoderEvent::SkillActivationResolved(crate::skills::SkillActivationResolved {
2875            thread_id: "thread-a".to_string(),
2876            turn_id: "turn-a".to_string(),
2877            selector: crate::skills::SkillSelector::Name {
2878                name: "commit".to_string(),
2879            },
2880            activation_reason: crate::skills::SkillActivationReason::FeatureBinding,
2881            activated: true,
2882            descriptor: Some(descriptor),
2883            diagnostic: None,
2884            timestamp: OffsetDateTime::UNIX_EPOCH,
2885        });
2886
2887        assert_eq!(event.kind(), "skills/activationResolved");
2888        assert_eq!(event.source(), EventSource::Core);
2889        assert_eq!(event.thread_id().map(String::as_str), Some("thread-a"));
2890        assert_eq!(event.turn_id().map(String::as_str), Some("turn-a"));
2891    }
2892}