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