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