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