Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:36:05.041Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

conflict_view.rs

699 lines · 25.2 KB · rust
1use agent_settings::AgentSettings;
2use collections::{HashMap, HashSet};
3use editor::{
4    ConflictsOurs, ConflictsOursMarker, ConflictsOuter, ConflictsTheirs, ConflictsTheirsMarker,
5    Editor, MultiBuffer, RowHighlightOptions,
6    display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
7};
8use gpui::{
9    App, ClickEvent, Context, Empty, Entity, InteractiveElement as _, ParentElement as _,
10    Subscription, Task, WeakEntity,
11};
12use language::{Anchor, Buffer, BufferId};
13use project::{
14    ConflictRegion, ConflictSet, ConflictSetUpdate, Project,
15    git_store::{GitStore, GitStoreEvent, RepositoryEvent},
16};
17use settings::Settings;
18use std::{ops::Range, sync::Arc};
19use ui::{ButtonLike, Divider, Tooltip, prelude::*};
20use util::debug_panic;
21use workspace::{HideStatusItem, StatusItemView, Workspace, item::ItemHandle};
22use zed_actions::agent::{
23    ConflictContent, ResolveConflictedFilesWithAgent, ResolveConflictsWithAgent,
24};
25
26pub(crate) struct ConflictAddon {
27    buffers: HashMap<BufferId, BufferConflicts>,
28}
29
30struct BufferConflicts {
31    block_ids: Vec<(Range<Anchor>, CustomBlockId)>,
32    conflict_set: Entity<ConflictSet>,
33    _subscription: Subscription,
34}
35
36impl editor::Addon for ConflictAddon {
37    fn to_any(&self) -> &dyn std::any::Any {
38        self
39    }
40
41    fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
42        Some(self)
43    }
44}
45
46pub fn register_editor(editor: &mut Editor, buffer: Entity<MultiBuffer>, cx: &mut Context<Editor>) {
47    let is_singleton = editor.buffer().read(cx).is_singleton();
48    if !editor.mode().is_full()
49        || (!is_singleton && !editor.buffer().read(cx).all_diff_hunks_expanded())
50        || editor.read_only(cx)
51    {
52        return;
53    }
54
55    editor.register_addon(ConflictAddon {
56        buffers: Default::default(),
57    });
58
59    if is_singleton {
60        let buffers = buffer.read(cx).all_buffers();
61        for buffer in buffers {
62            open_conflict_set_for_buffer(editor, buffer, cx);
63        }
64    }
65}
66
67fn open_conflict_set_for_buffer(
68    _editor: &mut Editor,
69    buffer: Entity<Buffer>,
70    cx: &mut Context<Editor>,
71) {
72    let buffer = buffer.downgrade();
73
74    cx.spawn(async move |editor, cx| {
75        let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id())?;
76        if let Some(conflict_set) = editor.read_with(cx, |editor, _| {
77            editor
78                .addon::<ConflictAddon>()
79                .and_then(|addon| addon.buffers.get(&buffer_id))
80                .map(|buffer_conflicts| buffer_conflicts.conflict_set.clone())
81        })? {
82            editor.update(cx, |editor, cx| {
83                buffer_ranges_updated(editor, conflict_set, cx);
84            })?;
85            return anyhow::Ok(());
86        }
87
88        let Some(project) = editor.read_with(cx, |editor, _| editor.project().cloned())? else {
89            return anyhow::Ok(());
90        };
91        let git_store = project.read_with(cx, |project, _| project.git_store().clone());
92        let Some(buffer) = buffer.upgrade() else {
93            return Ok(());
94        };
95        let conflict_set = git_store
96            .update(cx, |git_store, cx| {
97                git_store.open_conflict_set(buffer.clone(), cx)
98            })
99            .await;
100        editor.update(cx, |editor, cx| {
101            buffer_ranges_updated(editor, conflict_set, cx);
102        })?;
103        Ok(())
104    })
105    .detach();
106}
107
108pub(crate) fn buffer_ranges_updated(
109    editor: &mut Editor,
110    conflict_set: Entity<ConflictSet>,
111    cx: &mut Context<Editor>,
112) {
113    let buffer_id = conflict_set.read(cx).snapshot.buffer_id;
114    if editor.buffer().read(cx).buffer(buffer_id).is_none() {
115        return;
116    }
117
118    let Some(conflict_addon) = editor.addon_mut::<ConflictAddon>() else {
119        return;
120    };
121    let buffer_conflicts = conflict_addon.buffers.entry(buffer_id).or_insert_with(|| {
122        let subscription = cx.subscribe(&conflict_set, conflicts_updated);
123        BufferConflicts {
124            block_ids: Vec::new(),
125            conflict_set: conflict_set.clone(),
126            _subscription: subscription,
127        }
128    });
129
130    let conflict_set = buffer_conflicts.conflict_set.clone();
131    let conflicts_len = conflict_set.read(cx).snapshot().conflicts.len();
132    let addon_conflicts_len = buffer_conflicts.block_ids.len();
133    conflicts_updated(
134        editor,
135        conflict_set,
136        &ConflictSetUpdate {
137            buffer_range: None,
138            old_range: 0..addon_conflicts_len,
139            new_range: 0..conflicts_len,
140        },
141        cx,
142    );
143}
144
145pub(crate) fn buffers_removed(
146    editor: &mut Editor,
147    removed_buffer_ids: &[BufferId],
148    cx: &mut Context<Editor>,
149) {
150    let mut removed_block_ids = HashSet::default();
151    let Some(conflict_addon) = editor.addon_mut::<ConflictAddon>() else {
152        return;
153    };
154    conflict_addon.buffers.retain(|buffer_id, buffer| {
155        if removed_buffer_ids.contains(buffer_id) {
156            removed_block_ids.extend(buffer.block_ids.iter().map(|(_, block_id)| *block_id));
157            false
158        } else {
159            true
160        }
161    });
162    editor.remove_blocks(removed_block_ids, None, cx);
163}
164
165#[ztracing::instrument(skip_all)]
166fn conflicts_updated(
167    editor: &mut Editor,
168    conflict_set: Entity<ConflictSet>,
169    event: &ConflictSetUpdate,
170    cx: &mut Context<Editor>,
171) {
172    let buffer_id = conflict_set.read(cx).snapshot.buffer_id;
173    let conflict_set = conflict_set.read(cx).snapshot();
174    let multibuffer = editor.buffer().read(cx);
175    let snapshot = multibuffer.snapshot(cx);
176    let old_range = {
177        let Some(conflict_addon) = editor.addon_mut::<ConflictAddon>() else {
178            return;
179        };
180        let Some(buffer_conflicts) = conflict_addon.buffers.get(&buffer_id) else {
181            return;
182        };
183        match buffer_conflicts.block_ids.get(event.old_range.clone()) {
184            Some(_) => Some(event.old_range.clone()),
185            None => {
186                debug_panic!(
187                    "conflicts updated event old range is invalid for buffer conflicts view (block_ids len is {:?}, old_range is {:?})",
188                    buffer_conflicts.block_ids.len(),
189                    event.old_range,
190                );
191                if event.old_range.start <= event.old_range.end {
192                    Some(
193                        event.old_range.start.min(buffer_conflicts.block_ids.len())
194                            ..event.old_range.end.min(buffer_conflicts.block_ids.len()),
195                    )
196                } else {
197                    None
198                }
199            }
200        }
201    };
202
203    // Remove obsolete highlights and blocks
204    let Some(conflict_addon) = editor.addon_mut::<ConflictAddon>() else {
205        return;
206    };
207    if let Some((buffer_conflicts, old_range)) = conflict_addon
208        .buffers
209        .get_mut(&buffer_id)
210        .zip(old_range.clone())
211    {
212        let old_conflicts = buffer_conflicts.block_ids[old_range].to_owned();
213        let mut removed_highlighted_ranges = Vec::new();
214        let mut removed_block_ids = HashSet::default();
215        for (conflict_range, block_id) in old_conflicts {
216            let Some(range) = snapshot.buffer_anchor_range_to_anchor_range(conflict_range) else {
217                continue;
218            };
219            removed_highlighted_ranges.push(range.clone());
220            removed_block_ids.insert(block_id);
221        }
222
223        editor.remove_gutter_highlights::<ConflictsOuter>(removed_highlighted_ranges.clone(), cx);
224
225        editor.remove_highlighted_rows::<ConflictsOuter>(removed_highlighted_ranges.clone(), cx);
226        editor.remove_highlighted_rows::<ConflictsOurs>(removed_highlighted_ranges.clone(), cx);
227        editor
228            .remove_highlighted_rows::<ConflictsOursMarker>(removed_highlighted_ranges.clone(), cx);
229        editor.remove_highlighted_rows::<ConflictsTheirs>(removed_highlighted_ranges.clone(), cx);
230        editor.remove_highlighted_rows::<ConflictsTheirsMarker>(
231            removed_highlighted_ranges.clone(),
232            cx,
233        );
234        editor.remove_blocks(removed_block_ids, None, cx);
235    }
236
237    // Add new highlights and blocks
238    let editor_handle = cx.weak_entity();
239    let new_conflicts = &conflict_set.conflicts[event.new_range.clone()];
240    let mut blocks = Vec::new();
241    for conflict in new_conflicts {
242        update_conflict_highlighting(editor, conflict, &snapshot, cx);
243
244        let Some(anchor) = snapshot.anchor_in_excerpt(conflict.range.start) else {
245            continue;
246        };
247
248        let editor_handle = editor_handle.clone();
249        blocks.push(BlockProperties {
250            placement: BlockPlacement::Above(anchor),
251            height: Some(1),
252            style: BlockStyle::Sticky,
253            render: Arc::new({
254                let conflict = conflict.clone();
255                move |cx| render_conflict_buttons(&conflict, editor_handle.clone(), cx)
256            }),
257            priority: 0,
258        })
259    }
260    let new_block_ids = editor.insert_blocks(blocks, None, cx);
261
262    let Some(conflict_addon) = editor.addon_mut::<ConflictAddon>() else {
263        return;
264    };
265    if let Some((buffer_conflicts, old_range)) =
266        conflict_addon.buffers.get_mut(&buffer_id).zip(old_range)
267    {
268        buffer_conflicts.block_ids.splice(
269            old_range,
270            new_conflicts
271                .iter()
272                .map(|conflict| conflict.range.clone())
273                .zip(new_block_ids),
274        );
275    }
276}
277
278#[ztracing::instrument(skip_all)]
279fn update_conflict_highlighting(
280    editor: &mut Editor,
281    conflict: &ConflictRegion,
282    buffer: &editor::MultiBufferSnapshot,
283    cx: &mut Context<Editor>,
284) -> Option<()> {
285    log::debug!("update conflict highlighting for {conflict:?}");
286
287    let outer = buffer.buffer_anchor_range_to_anchor_range(conflict.range.clone())?;
288    let ours = buffer.buffer_anchor_range_to_anchor_range(conflict.ours.clone())?;
289    let theirs = buffer.buffer_anchor_range_to_anchor_range(conflict.theirs.clone())?;
290
291    let ours_background = |cx: &App| cx.theme().colors().version_control_conflict_marker_ours;
292    let theirs_background = |cx: &App| cx.theme().colors().version_control_conflict_marker_theirs;
293
294    let options = RowHighlightOptions {
295        include_gutter: true,
296        ..Default::default()
297    };
298
299    editor.insert_gutter_highlight::<ConflictsOuter>(
300        outer.start..theirs.end,
301        |cx| cx.theme().colors().editor_background,
302        cx,
303    );
304
305    // Prevent diff hunk highlighting within the entire conflict region.
306    editor.highlight_rows::<ConflictsOuter>(outer.clone(), theirs_background, options, cx);
307    editor.highlight_rows::<ConflictsOurs>(ours.clone(), ours_background, options, cx);
308    editor.highlight_rows::<ConflictsOursMarker>(
309        outer.start..ours.start,
310        ours_background,
311        options,
312        cx,
313    );
314    editor.highlight_rows::<ConflictsTheirs>(theirs.clone(), theirs_background, options, cx);
315    editor.highlight_rows::<ConflictsTheirsMarker>(
316        theirs.end..outer.end,
317        theirs_background,
318        options,
319        cx,
320    );
321
322    Some(())
323}
324
325fn render_conflict_buttons(
326    conflict: &ConflictRegion,
327    editor: WeakEntity<Editor>,
328    cx: &mut BlockContext,
329) -> AnyElement {
330    let is_ai_enabled = AgentSettings::get_global(cx).enabled(cx);
331
332    h_flex()
333        .id(cx.block_id)
334        .h(cx.line_height)
335        .ml(cx.margins.gutter.width)
336        .gap_1()
337        .bg(cx.theme().colors().editor_background)
338        .child(
339            Button::new("head", format!("Use {}", conflict.ours_branch_name))
340                .label_size(LabelSize::Small)
341                .on_click({
342                    let editor = editor.clone();
343                    let conflict = conflict.clone();
344                    let ours = conflict.ours.clone();
345                    move |_, window, cx| {
346                        resolve_conflict(
347                            editor.clone(),
348                            conflict.clone(),
349                            vec![ours.clone()],
350                            window,
351                            cx,
352                        )
353                        .detach()
354                    }
355                }),
356        )
357        .child(
358            Button::new("origin", format!("Use {}", conflict.theirs_branch_name))
359                .label_size(LabelSize::Small)
360                .on_click({
361                    let editor = editor.clone();
362                    let conflict = conflict.clone();
363                    let theirs = conflict.theirs.clone();
364                    move |_, window, cx| {
365                        resolve_conflict(
366                            editor.clone(),
367                            conflict.clone(),
368                            vec![theirs.clone()],
369                            window,
370                            cx,
371                        )
372                        .detach()
373                    }
374                }),
375        )
376        .child(
377            Button::new("both", "Use Both")
378                .label_size(LabelSize::Small)
379                .on_click({
380                    let editor = editor.clone();
381                    let conflict = conflict.clone();
382                    let ours = conflict.ours.clone();
383                    let theirs = conflict.theirs.clone();
384                    move |_, window, cx| {
385                        resolve_conflict(
386                            editor.clone(),
387                            conflict.clone(),
388                            vec![ours.clone(), theirs.clone()],
389                            window,
390                            cx,
391                        )
392                        .detach()
393                    }
394                }),
395        )
396        .when(is_ai_enabled, |this| {
397            this.child(Divider::vertical()).child(
398                Button::new("resolve-with-agent", "Resolve with Agent")
399                    .label_size(LabelSize::Small)
400                    .start_icon(
401                        Icon::new(IconName::OmegaAssistant)
402                            .size(IconSize::Small)
403                            .color(Color::Muted),
404                    )
405                    .on_click({
406                        let conflict = conflict.clone();
407                        move |_, window, cx| {
408                            let content = editor
409                                .update(cx, |editor, cx| {
410                                    let multibuffer = editor.buffer().read(cx);
411                                    let buffer_id = conflict.ours.end.buffer_id;
412                                    let buffer = multibuffer.buffer(buffer_id)?;
413                                    let buffer_read = buffer.read(cx);
414                                    let snapshot = buffer_read.snapshot();
415                                    let conflict_text = snapshot
416                                        .text_for_range(conflict.range.clone())
417                                        .collect::<String>();
418                                    let file_path = buffer_read
419                                        .file()
420                                        .and_then(|file| file.as_local())
421                                        .map(|f| f.abs_path(cx).to_string_lossy().to_string())
422                                        .unwrap_or_default();
423                                    Some(ConflictContent {
424                                        file_path,
425                                        conflict_text,
426                                        ours_branch_name: conflict.ours_branch_name.to_string(),
427                                        theirs_branch_name: conflict.theirs_branch_name.to_string(),
428                                    })
429                                })
430                                .ok()
431                                .flatten();
432                            if let Some(content) = content {
433                                window.dispatch_action(
434                                    Box::new(ResolveConflictsWithAgent {
435                                        conflicts: vec![content],
436                                    }),
437                                    cx,
438                                );
439                            }
440                        }
441                    }),
442            )
443        })
444        .into_any()
445}
446
447fn collect_conflicted_file_paths(project: &Project, cx: &App) -> Vec<String> {
448    let git_store = project.git_store().read(cx);
449    let mut paths = Vec::new();
450
451    for repo in git_store.repositories().values() {
452        let snapshot = repo.read(cx).snapshot();
453        for (repo_path, _) in snapshot.merge.merge_heads_by_conflicted_path.iter() {
454            let is_currently_conflicted = snapshot
455                .status_for_path(repo_path)
456                .is_some_and(|entry| entry.status.is_conflicted());
457            if !is_currently_conflicted {
458                continue;
459            }
460            if let Some(project_path) = repo.read(cx).repo_path_to_project_path(repo_path, cx) {
461                paths.push(
462                    project_path
463                        .path
464                        .as_std_path()
465                        .to_string_lossy()
466                        .to_string(),
467                );
468            }
469        }
470    }
471
472    paths
473}
474
475pub(crate) fn resolve_conflict(
476    editor: WeakEntity<Editor>,
477    resolved_conflict: ConflictRegion,
478    ranges: Vec<Range<Anchor>>,
479    window: &mut Window,
480    cx: &mut App,
481) -> Task<()> {
482    window.spawn(cx, async move |cx| {
483        editor
484            .update(cx, |editor, cx| {
485                let multibuffer = editor.buffer().clone();
486                let buffer_id = resolved_conflict.ours.end.buffer_id;
487                let buffer = multibuffer.read(cx).buffer(buffer_id)?;
488                resolved_conflict.resolve(buffer.clone(), &ranges, cx);
489                let conflict_addon = editor.addon_mut::<ConflictAddon>()?;
490                let snapshot = multibuffer.read(cx).snapshot(cx);
491                let buffer_snapshot = buffer.read(cx).snapshot();
492                let state = conflict_addon
493                    .buffers
494                    .get_mut(&buffer_snapshot.remote_id())?;
495                let ix = state
496                    .block_ids
497                    .binary_search_by(|(range, _)| {
498                        range
499                            .start
500                            .cmp(&resolved_conflict.range.start, &buffer_snapshot)
501                    })
502                    .ok()?;
503                let &(_, block_id) = &state.block_ids[ix];
504                let range =
505                    snapshot.buffer_anchor_range_to_anchor_range(resolved_conflict.range)?;
506
507                editor.remove_gutter_highlights::<ConflictsOuter>(vec![range.clone()], cx);
508
509                editor.remove_highlighted_rows::<ConflictsOuter>(vec![range.clone()], cx);
510                editor.remove_highlighted_rows::<ConflictsOurs>(vec![range.clone()], cx);
511                editor.remove_highlighted_rows::<ConflictsTheirs>(vec![range.clone()], cx);
512                editor.remove_highlighted_rows::<ConflictsOursMarker>(vec![range.clone()], cx);
513                editor.remove_highlighted_rows::<ConflictsTheirsMarker>(vec![range], cx);
514                editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
515                Some(())
516            })
517            .ok();
518    })
519}
520
521pub struct MergeConflictIndicator {
522    project: Entity<Project>,
523    conflicted_paths: Vec<String>,
524    last_shown_paths: HashSet<String>,
525    dismissed: bool,
526    _subscription: Subscription,
527}
528
529impl MergeConflictIndicator {
530    pub fn new(workspace: &Workspace, cx: &mut Context<Self>) -> Self {
531        let project = workspace.project().clone();
532        let git_store = project.read(cx).git_store().clone();
533
534        let subscription = cx.subscribe(&git_store, Self::on_git_store_event);
535
536        let conflicted_paths = collect_conflicted_file_paths(project.read(cx), cx);
537        let last_shown_paths: HashSet<String> = conflicted_paths.iter().cloned().collect();
538
539        Self {
540            project,
541            conflicted_paths,
542            last_shown_paths,
543            dismissed: false,
544            _subscription: subscription,
545        }
546    }
547
548    fn on_git_store_event(
549        &mut self,
550        _git_store: Entity<GitStore>,
551        event: &GitStoreEvent,
552        cx: &mut Context<Self>,
553    ) {
554        let conflicts_changed = matches!(
555            event,
556            GitStoreEvent::ConflictsUpdated
557                | GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::StatusesChanged, _)
558        );
559
560        let agent_settings = AgentSettings::get_global(cx);
561        if !agent_settings.enabled(cx)
562            || !agent_settings.show_merge_conflict_indicator
563            || !conflicts_changed
564        {
565            return;
566        }
567
568        let project = self.project.read(cx);
569        if project.is_via_collab() {
570            return;
571        }
572
573        let paths = collect_conflicted_file_paths(project, cx);
574        let current_paths_set: HashSet<String> = paths.iter().cloned().collect();
575
576        if paths.is_empty() {
577            self.conflicted_paths.clear();
578            self.last_shown_paths.clear();
579            self.dismissed = false;
580            cx.notify();
581        } else if self.last_shown_paths != current_paths_set {
582            self.last_shown_paths = current_paths_set;
583            self.conflicted_paths = paths;
584            self.dismissed = false;
585            cx.notify();
586        }
587    }
588
589    fn resolve_with_agent(&mut self, window: &mut Window, cx: &mut Context<Self>) {
590        window.dispatch_action(
591            Box::new(ResolveConflictedFilesWithAgent {
592                conflicted_file_paths: self.conflicted_paths.clone(),
593            }),
594            cx,
595        );
596        self.dismissed = true;
597        cx.notify();
598    }
599
600    fn dismiss(&mut self, _: &ClickEvent, _window: &mut Window, cx: &mut Context<Self>) {
601        self.dismissed = true;
602        cx.notify();
603    }
604}
605
606impl Render for MergeConflictIndicator {
607    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
608        let agent_settings = AgentSettings::get_global(cx);
609        if !agent_settings.enabled(cx)
610            || !agent_settings.show_merge_conflict_indicator
611            || self.conflicted_paths.is_empty()
612            || self.dismissed
613        {
614            return Empty.into_any_element();
615        }
616
617        let file_count = self.conflicted_paths.len();
618
619        let message: SharedString = format!(
620            "Resolve Merge Conflict{} with Agent",
621            if file_count == 1 { "" } else { "s" }
622        )
623        .into();
624
625        let tooltip_label: SharedString = format!(
626            "Found {} {} across the codebase",
627            file_count,
628            if file_count == 1 {
629                "conflict"
630            } else {
631                "conflicts"
632            }
633        )
634        .into();
635
636        let border_color = cx.theme().colors().text_accent.opacity(0.2);
637
638        h_flex()
639            .h(rems_from_px(22.))
640            .rounded_sm()
641            .border_1()
642            .border_color(border_color)
643            .child(
644                ButtonLike::new("update-button")
645                    .tab_index(0isize)
646                    .aria_label(message.clone())
647                    .child(
648                        h_flex()
649                            .h_full()
650                            .gap_1()
651                            .child(
652                                Icon::new(IconName::GitMergeConflict)
653                                    .size(IconSize::Small)
654                                    .color(Color::Muted),
655                            )
656                            .child(Label::new(message).size(LabelSize::Small)),
657                    )
658                    .tooltip(move |_, cx| {
659                        Tooltip::with_meta(
660                            tooltip_label.clone(),
661                            None,
662                            "Click to Resolve with Agent",
663                            cx,
664                        )
665                    })
666                    .on_click(cx.listener(|this, _, window, cx| {
667                        this.resolve_with_agent(window, cx);
668                    })),
669            )
670            .child(
671                div().border_l_1().border_color(border_color).child(
672                    IconButton::new("dismiss-merge-conflicts", IconName::Close)
673                        .icon_size(IconSize::XSmall)
674                        .on_click(cx.listener(Self::dismiss)),
675                ),
676            )
677            .into_any_element()
678    }
679}
680
681impl StatusItemView for MergeConflictIndicator {
682    fn set_active_pane_item(
683        &mut self,
684        _: Option<&dyn ItemHandle>,
685        _window: &mut Window,
686        _: &mut Context<Self>,
687    ) {
688    }
689
690    fn hide_setting(&self, _: &App) -> Option<HideStatusItem> {
691        Some(HideStatusItem::new(|settings| {
692            settings
693                .agent
694                .get_or_insert_default()
695                .show_merge_conflict_indicator = Some(false);
696        }))
697    }
698}
699
Served at tenant.openagents/omega Member data and write actions are omitted.