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