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