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