Skip to main content

roder_api/
events.rs

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