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