Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:35:49.884Z 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

tasks_ui.rs

713 lines · 27.3 KB · rust
1use std::{
2    path::Path,
3    sync::{Arc, LazyLock},
4};
5
6use anyhow::Context as _;
7use collections::HashMap;
8use editor::{Editor, MultiBufferOffset, ToPoint as _};
9use gpui::{App, AppContext as _, Context, Entity, Task, TaskExt, Window};
10use project::{Location, TaskContexts, TaskSourceKind, Worktree};
11use task::{RevealTarget, TaskContext, TaskId, TaskTemplate, TaskVariables, VariableName};
12use tree_sitter::{Query, StreamingIterator as _};
13use workspace::Workspace;
14
15mod modal;
16
17pub use modal::{Rerun, ShowAttachModal, Spawn, TaskOverrides, TasksModal};
18
19/// Inserts `new_task` (pretty-printed JSON object text) at the end of the top-level JSON
20/// array in the editor's buffer, creating the array if the buffer has none, and moves the
21/// cursor to the inserted task. The edit is left unsaved so callers decide whether to persist it.
22pub fn insert_task_json_into_editor(
23    editor: &mut Editor,
24    new_task: String,
25    window: &mut Window,
26    cx: &mut Context<Editor>,
27) -> anyhow::Result<()> {
28    static LAST_ITEM_QUERY: LazyLock<Query> = LazyLock::new(|| {
29        Query::new(
30            &tree_sitter_json::LANGUAGE.into(),
31            "(document (array (object) @object))", // TODO: use "." anchor to only match last object
32        )
33        .expect("Failed to create LAST_ITEM_QUERY")
34    });
35    static EMPTY_ARRAY_QUERY: LazyLock<Query> = LazyLock::new(|| {
36        Query::new(
37            &tree_sitter_json::LANGUAGE.into(),
38            "(document (array) @array)",
39        )
40        .expect("Failed to create EMPTY_ARRAY_QUERY")
41    });
42
43    let content = editor.text(cx);
44    let mut parser = tree_sitter::Parser::new();
45    parser.set_language(&tree_sitter_json::LANGUAGE.into())?;
46    let mut cursor = tree_sitter::QueryCursor::new();
47    let syntax_tree = parser
48        .parse(&content, None)
49        .context("could not parse tasks file")?;
50    let mut matches = cursor.matches(
51        &LAST_ITEM_QUERY,
52        syntax_tree.root_node(),
53        content.as_bytes(),
54    );
55
56    let mut last_offset = None;
57    while let Some(mat) = matches.next() {
58        if let Some(pos) = mat.captures.first().map(|m| m.node.byte_range().end) {
59            last_offset = Some(MultiBufferOffset(pos))
60        }
61    }
62    let mut edits = Vec::new();
63    let mut cursor_position = MultiBufferOffset(0);
64
65    if let Some(pos) = last_offset {
66        edits.push((pos..pos, format!(",\n{new_task}")));
67        cursor_position = pos + ",\n  ".len();
68    } else {
69        let mut matches = cursor.matches(
70            &EMPTY_ARRAY_QUERY,
71            syntax_tree.root_node(),
72            content.as_bytes(),
73        );
74
75        if let Some(mat) = matches.next() {
76            if let Some(pos) = mat.captures.first().map(|m| m.node.byte_range().end - 1) {
77                edits.push((
78                    MultiBufferOffset(pos)..MultiBufferOffset(pos),
79                    format!("\n{new_task}\n"),
80                ));
81                cursor_position = MultiBufferOffset(pos) + "\n  ".len();
82            }
83        } else {
84            edits.push((
85                MultiBufferOffset(0)..MultiBufferOffset(0),
86                format!("[\n{}\n]", new_task),
87            ));
88            cursor_position = MultiBufferOffset("[\n  ".len());
89        }
90    }
91    editor.transact(window, cx, |editor, window, cx| {
92        editor.edit(edits, cx);
93        let snapshot = editor.buffer().read(cx).read(cx);
94        let point = cursor_position.to_point(&snapshot);
95        drop(snapshot);
96        editor.go_to_singleton_buffer_point(point, window, cx);
97    });
98    Ok(())
99}
100
101pub fn init(cx: &mut App) {
102    cx.observe_new(
103        |workspace: &mut Workspace, _: Option<&mut Window>, _: &mut Context<Workspace>| {
104            workspace
105                .register_action(spawn_task_or_modal)
106                .register_action(move |workspace, action: &modal::Rerun, window, cx| {
107                    if let Some((task_source_kind, mut last_scheduled_task)) = workspace
108                        .project()
109                        .read(cx)
110                        .task_store()
111                        .read(cx)
112                        .task_inventory()
113                        .and_then(|inventory| {
114                            inventory.read(cx).last_scheduled_task(
115                                action
116                                    .task_id
117                                    .as_ref()
118                                    .map(|id| TaskId(id.clone()))
119                                    .as_ref(),
120                            )
121                        })
122                    {
123                        if action.reevaluate_context {
124                            let mut original_task = last_scheduled_task.original_task().clone();
125                            if let Some(allow_concurrent_runs) = action.allow_concurrent_runs {
126                                original_task.allow_concurrent_runs = allow_concurrent_runs;
127                            }
128                            if let Some(use_new_terminal) = action.use_new_terminal {
129                                original_task.use_new_terminal = use_new_terminal;
130                            }
131                            let task_contexts = task_contexts(workspace, window, cx);
132                            cx.spawn_in(window, async move |workspace, cx| {
133                                let task_contexts = task_contexts.await;
134                                let default_context = TaskContext::default();
135                                workspace
136                                    .update_in(cx, |workspace, window, cx| {
137                                        workspace.schedule_task(
138                                            task_source_kind,
139                                            &original_task,
140                                            task_contexts
141                                                .active_context()
142                                                .unwrap_or(&default_context),
143                                            false,
144                                            window,
145                                            cx,
146                                        )
147                                    })
148                                    .ok()
149                            })
150                            .detach()
151                        } else {
152                            let resolved = &mut last_scheduled_task.resolved;
153
154                            if let Some(allow_concurrent_runs) = action.allow_concurrent_runs {
155                                resolved.allow_concurrent_runs = allow_concurrent_runs;
156                            }
157                            if let Some(use_new_terminal) = action.use_new_terminal {
158                                resolved.use_new_terminal = use_new_terminal;
159                            }
160
161                            workspace.schedule_resolved_task(
162                                task_source_kind,
163                                last_scheduled_task,
164                                false,
165                                window,
166                                cx,
167                            );
168                        }
169                    } else {
170                        spawn_task_or_modal(
171                            workspace,
172                            &Spawn::ViaModal {
173                                reveal_target: None,
174                            },
175                            window,
176                            cx,
177                        );
178                    };
179                });
180        },
181    )
182    .detach();
183}
184
185fn spawn_task_or_modal(
186    workspace: &mut Workspace,
187    action: &Spawn,
188    window: &mut Window,
189    cx: &mut Context<Workspace>,
190) {
191    if let Some(provider) = workspace.debugger_provider() {
192        provider.spawn_task_or_modal(workspace, action, window, cx);
193        return;
194    }
195
196    match action {
197        Spawn::ByName {
198            task_name,
199            reveal_target,
200        } => {
201            let overrides = reveal_target.map(|reveal_target| TaskOverrides {
202                reveal_target: Some(reveal_target),
203            });
204            let name = task_name.clone();
205            spawn_tasks_filtered(move |(_, task)| task.label.eq(&name), overrides, window, cx)
206                .detach_and_log_err(cx)
207        }
208        Spawn::ByTag {
209            task_tag,
210            reveal_target,
211        } => {
212            let overrides = reveal_target.map(|reveal_target| TaskOverrides {
213                reveal_target: Some(reveal_target),
214            });
215            let tag = task_tag.clone();
216            spawn_tasks_filtered(
217                move |(_, task)| task.tags.contains(&tag),
218                overrides,
219                window,
220                cx,
221            )
222            .detach_and_log_err(cx)
223        }
224        Spawn::ViaModal { reveal_target } => {
225            toggle_modal(workspace, *reveal_target, window, cx).detach()
226        }
227    }
228}
229
230pub fn toggle_modal(
231    workspace: &mut Workspace,
232    reveal_target: Option<RevealTarget>,
233    window: &mut Window,
234    cx: &mut Context<Workspace>,
235) -> Task<()> {
236    let task_store = workspace.project().read(cx).task_store().clone();
237    let workspace_handle = workspace.weak_handle();
238    let can_open_modal = workspace
239        .project()
240        .read_with(cx, |project, _| !project.is_via_collab());
241    if can_open_modal {
242        let task_contexts = task_contexts(workspace, window, cx);
243        cx.spawn_in(window, async move |workspace, cx| {
244            let task_contexts = Arc::new(task_contexts.await);
245            workspace
246                .update_in(cx, |workspace, window, cx| {
247                    workspace.toggle_modal(window, cx, |window, cx| {
248                        TasksModal::new(
249                            task_store.clone(),
250                            task_contexts,
251                            reveal_target.map(|target| TaskOverrides {
252                                reveal_target: Some(target),
253                            }),
254                            true,
255                            workspace_handle,
256                            window,
257                            cx,
258                        )
259                    })
260                })
261                .ok();
262        })
263    } else {
264        Task::ready(())
265    }
266}
267
268pub fn spawn_tasks_filtered<F>(
269    mut predicate: F,
270    overrides: Option<TaskOverrides>,
271    window: &mut Window,
272    cx: &mut Context<Workspace>,
273) -> Task<anyhow::Result<()>>
274where
275    F: FnMut((&TaskSourceKind, &TaskTemplate)) -> bool + 'static,
276{
277    cx.spawn_in(window, async move |workspace, cx| {
278        let task_contexts = workspace.update_in(cx, |workspace, window, cx| {
279            task_contexts(workspace, window, cx)
280        })?;
281        let task_contexts = task_contexts.await;
282        let mut tasks = workspace
283            .update(cx, |workspace, cx| {
284                let Some(task_inventory) = workspace
285                    .project()
286                    .read(cx)
287                    .task_store()
288                    .read(cx)
289                    .task_inventory()
290                    .cloned()
291                else {
292                    return Task::ready(Vec::new());
293                };
294                let (language, buffer) = task_contexts
295                    .location()
296                    .map(|location| {
297                        let buffer = location.buffer.clone();
298                        (
299                            buffer.read(cx).language_at(location.range.start),
300                            Some(buffer),
301                        )
302                    })
303                    .unwrap_or_default();
304                task_inventory
305                    .read(cx)
306                    .list_tasks(buffer, language, task_contexts.worktree(), cx)
307            })?
308            .await;
309
310        let did_spawn = workspace
311            .update_in(cx, |workspace, window, cx| {
312                let default_context = TaskContext::default();
313                let active_context = task_contexts.active_context().unwrap_or(&default_context);
314
315                tasks.retain_mut(|(task_source_kind, target_task)| {
316                    if predicate((task_source_kind, target_task)) {
317                        if let Some(overrides) = &overrides
318                            && let Some(target_override) = overrides.reveal_target
319                        {
320                            target_task.reveal_target = target_override;
321                        }
322                        workspace.schedule_task(
323                            task_source_kind.clone(),
324                            target_task,
325                            active_context,
326                            false,
327                            window,
328                            cx,
329                        );
330                        true
331                    } else {
332                        false
333                    }
334                });
335
336                if tasks.is_empty() { None } else { Some(()) }
337            })?
338            .is_some();
339        if !did_spawn {
340            workspace
341                .update_in(cx, |workspace, window, cx| {
342                    spawn_task_or_modal(
343                        workspace,
344                        &Spawn::ViaModal {
345                            reveal_target: overrides.and_then(|overrides| overrides.reveal_target),
346                        },
347                        window,
348                        cx,
349                    );
350                })
351                .ok();
352        }
353
354        Ok(())
355    })
356}
357
358pub fn task_contexts(
359    workspace: &Workspace,
360    window: &mut Window,
361    cx: &mut App,
362) -> Task<TaskContexts> {
363    let active_item = workspace.active_item(cx);
364    let active_worktree = active_item
365        .as_ref()
366        .and_then(|item| item.project_path(cx))
367        .map(|project_path| project_path.worktree_id)
368        .filter(|worktree_id| {
369            workspace
370                .project()
371                .read(cx)
372                .worktree_for_id(*worktree_id, cx)
373                .is_some_and(|worktree| is_visible_directory(&worktree, cx))
374        })
375        .or_else(|| {
376            workspace
377                .visible_worktrees(cx)
378                .next()
379                .map(|tree| tree.read(cx).id())
380        });
381
382    let active_editor = active_item.and_then(|item| item.act_as::<Editor>(cx));
383
384    let editor_context_task = active_editor.as_ref().map(|active_editor| {
385        active_editor.update(cx, |editor, cx| editor.task_context(window, cx))
386    });
387
388    let location = active_editor.as_ref().and_then(|editor| {
389        editor.update(cx, |editor, cx| {
390            let selection = editor.selections.newest_anchor();
391            let multi_buffer = editor.buffer().clone();
392            let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
393            let (buffer_snapshot, buffer_offset) =
394                multi_buffer_snapshot.point_to_buffer_offset(selection.head())?;
395            let buffer_anchor = buffer_snapshot.anchor_before(buffer_offset);
396            let buffer = multi_buffer.read(cx).buffer(buffer_snapshot.remote_id())?;
397            Some(Location {
398                buffer,
399                range: buffer_anchor..buffer_anchor,
400            })
401        })
402    });
403
404    let lsp_task_sources = active_editor
405        .as_ref()
406        .map(|active_editor| {
407            active_editor.update(cx, |editor, cx| editor.lsp_task_sources(false, false, cx))
408        })
409        .unwrap_or_default();
410
411    let latest_selection = active_editor.as_ref().and_then(|active_editor| {
412        let snapshot = active_editor.read(cx).buffer().read(cx).snapshot(cx);
413        snapshot
414            .anchor_to_buffer_anchor(active_editor.read(cx).selections.newest_anchor().head())
415            .map(|(anchor, _)| anchor)
416    });
417
418    let mut worktree_abs_paths = workspace
419        .worktrees(cx)
420        .filter(|worktree| is_visible_directory(worktree, cx))
421        .map(|worktree| {
422            let worktree = worktree.read(cx);
423            (worktree.id(), worktree.abs_path())
424        })
425        .collect::<HashMap<_, _>>();
426
427    cx.background_spawn(async move {
428        let mut task_contexts = TaskContexts::default();
429
430        task_contexts.lsp_task_sources = lsp_task_sources;
431        task_contexts.latest_selection = latest_selection;
432
433        if let Some(editor_context_task) = editor_context_task
434            && let Some(editor_context) = editor_context_task.await
435        {
436            task_contexts.active_item_context = Some((active_worktree, location, editor_context));
437        }
438
439        if let Some(active_worktree) = active_worktree {
440            if let Some(active_worktree_abs_path) = worktree_abs_paths.remove(&active_worktree) {
441                task_contexts.active_worktree_context =
442                    Some((active_worktree, worktree_context(&active_worktree_abs_path)));
443            }
444        } else if worktree_abs_paths.len() == 1 {
445            task_contexts.active_worktree_context = worktree_abs_paths
446                .drain()
447                .next()
448                .map(|(id, abs_path)| (id, worktree_context(&abs_path)));
449        }
450
451        task_contexts.other_worktree_contexts.extend(
452            worktree_abs_paths
453                .into_iter()
454                .map(|(id, abs_path)| (id, worktree_context(&abs_path))),
455        );
456        task_contexts
457    })
458}
459
460fn is_visible_directory(worktree: &Entity<Worktree>, cx: &App) -> bool {
461    let worktree = worktree.read(cx);
462    worktree.is_visible() && worktree.root_entry().is_some_and(|entry| entry.is_dir())
463}
464
465fn worktree_context(worktree_abs_path: &Path) -> TaskContext {
466    let mut task_variables = TaskVariables::default();
467    task_variables.insert(
468        VariableName::WorktreeRoot,
469        worktree_abs_path.to_string_lossy().into_owned(),
470    );
471    TaskContext {
472        cwd: Some(worktree_abs_path.to_path_buf()),
473        task_variables,
474        project_env: HashMap::default(),
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use std::{collections::HashMap, sync::Arc};
481
482    use editor::{Editor, MultiBufferOffset, SelectionEffects};
483    use gpui::TestAppContext;
484    use language::{Language, LanguageConfig};
485    use project::{BasicContextProvider, FakeFs, Project, task_store::TaskStore};
486    use serde_json::json;
487    use task::{TaskContext, TaskVariables, VariableName};
488    use ui::VisualContext;
489    use util::{path, rel_path::rel_path};
490    use workspace::{AppState, MultiWorkspace};
491
492    use crate::task_contexts;
493
494    #[gpui::test]
495    async fn test_default_language_context(cx: &mut TestAppContext) {
496        init_test(cx);
497        let fs = FakeFs::new(cx.executor());
498        fs.insert_tree(
499            path!("/dir"),
500            json!({
501                ".zed": {
502                    "tasks.json": r#"[
503                            {
504                                "label": "example task",
505                                "command": "echo",
506                                "args": ["4"]
507                            },
508                            {
509                                "label": "another one",
510                                "command": "echo",
511                                "args": ["55"]
512                            },
513                        ]"#,
514                },
515                "a.ts": "function this_is_a_test() { }",
516                "rust": {
517                                    "b.rs": "use std; fn this_is_a_rust_file() { }",
518                }
519
520            }),
521        )
522        .await;
523        let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
524        let (worktree_store, git_store) = project.read_with(cx, |project, _| {
525            (project.worktree_store(), project.git_store().clone())
526        });
527        let rust_language = Arc::new(
528            Language::new(
529                LanguageConfig {
530                    name: "Rust".into(),
531                    ..Default::default()
532                },
533                Some(tree_sitter_rust::LANGUAGE.into()),
534            )
535            .with_outline_query(
536                r#"(function_item
537            "fn" @context
538            name: (_) @name) @item"#,
539            )
540            .unwrap()
541            .with_context_provider(Some(Arc::new(BasicContextProvider::new(
542                worktree_store.clone(),
543                git_store.clone(),
544            )))),
545        );
546
547        let typescript_language = Arc::new(
548            Language::new(
549                LanguageConfig {
550                    name: "TypeScript".into(),
551                    ..Default::default()
552                },
553                Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
554            )
555            .with_outline_query(
556                r#"(function_declaration
557                    "async"? @context
558                    "function" @context
559                    name: (_) @name
560                    parameters: (formal_parameters
561                        "(" @context
562                        ")" @context)) @item"#,
563            )
564            .unwrap()
565            .with_context_provider(Some(Arc::new(BasicContextProvider::new(
566                worktree_store.clone(),
567                git_store.clone(),
568            )))),
569        );
570
571        let worktree_id = project.update(cx, |project, cx| {
572            project.worktrees(cx).next().unwrap().read(cx).id()
573        });
574        let (multi_workspace, cx) =
575            cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
576        let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
577
578        let buffer1 = workspace
579            .update(cx, |this, cx| {
580                this.project().update(cx, |this, cx| {
581                    this.open_buffer((worktree_id, rel_path("a.ts")), cx)
582                })
583            })
584            .await
585            .unwrap();
586        buffer1.update(cx, |this, cx| {
587            this.set_language(Some(typescript_language), cx)
588        });
589        let editor1 = cx.new_window_entity(|window, cx| {
590            Editor::for_buffer(buffer1, Some(project.clone()), window, cx)
591        });
592        let buffer2 = workspace
593            .update(cx, |this, cx| {
594                this.project().update(cx, |this, cx| {
595                    this.open_buffer((worktree_id, rel_path("rust/b.rs")), cx)
596                })
597            })
598            .await
599            .unwrap();
600        buffer2.update(cx, |this, cx| this.set_language(Some(rust_language), cx));
601        let editor2 = cx
602            .new_window_entity(|window, cx| Editor::for_buffer(buffer2, Some(project), window, cx));
603
604        let first_context = workspace
605            .update_in(cx, |workspace, window, cx| {
606                workspace.add_item_to_center(Box::new(editor1.clone()), window, cx);
607                workspace.add_item_to_center(Box::new(editor2.clone()), window, cx);
608                assert_eq!(
609                    workspace.active_item(cx).unwrap().item_id(),
610                    editor2.entity_id()
611                );
612                task_contexts(workspace, window, cx)
613            })
614            .await;
615
616        assert_eq!(
617            first_context
618                .active_context()
619                .expect("Should have an active context"),
620            &TaskContext {
621                cwd: Some(path!("/dir").into()),
622                task_variables: TaskVariables::from_iter([
623                    (VariableName::File, path!("/dir/rust/b.rs").into()),
624                    (VariableName::Filename, "b.rs".into()),
625                    (VariableName::RelativeFile, path!("rust/b.rs").into()),
626                    (VariableName::RelativeDir, "rust".into()),
627                    (VariableName::Dirname, path!("/dir/rust").into()),
628                    (VariableName::Stem, "b".into()),
629                    (VariableName::WorktreeRoot, path!("/dir").into()),
630                    (VariableName::Row, "1".into()),
631                    (VariableName::Column, "1".into()),
632                    (VariableName::Language, "Rust".into()),
633                ]),
634                project_env: HashMap::default(),
635            }
636        );
637
638        // And now, let's select an identifier.
639        editor2.update_in(cx, |editor, window, cx| {
640            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
641                selections.select_ranges([MultiBufferOffset(14)..MultiBufferOffset(18)])
642            })
643        });
644
645        assert_eq!(
646            workspace
647                .update_in(cx, |workspace, window, cx| {
648                    task_contexts(workspace, window, cx)
649                })
650                .await
651                .active_context()
652                .expect("Should have an active context"),
653            &TaskContext {
654                cwd: Some(path!("/dir").into()),
655                task_variables: TaskVariables::from_iter([
656                    (VariableName::File, path!("/dir/rust/b.rs").into()),
657                    (VariableName::Filename, "b.rs".into()),
658                    (VariableName::RelativeFile, path!("rust/b.rs").into()),
659                    (VariableName::RelativeDir, "rust".into()),
660                    (VariableName::Dirname, path!("/dir/rust").into()),
661                    (VariableName::Stem, "b".into()),
662                    (VariableName::WorktreeRoot, path!("/dir").into()),
663                    (VariableName::Row, "1".into()),
664                    (VariableName::Column, "15".into()),
665                    (VariableName::SelectedText, "is_i".into()),
666                    (VariableName::Symbol, "this_is_a_rust_file".into()),
667                    (VariableName::Language, "Rust".into()),
668                ]),
669                project_env: HashMap::default(),
670            }
671        );
672
673        assert_eq!(
674            workspace
675                .update_in(cx, |workspace, window, cx| {
676                    // Now, let's switch the active item to .ts file.
677                    workspace.activate_item(&editor1, true, true, window, cx);
678                    task_contexts(workspace, window, cx)
679                })
680                .await
681                .active_context()
682                .expect("Should have an active context"),
683            &TaskContext {
684                cwd: Some(path!("/dir").into()),
685                task_variables: TaskVariables::from_iter([
686                    (VariableName::File, path!("/dir/a.ts").into()),
687                    (VariableName::Filename, "a.ts".into()),
688                    (VariableName::RelativeFile, "a.ts".into()),
689                    (VariableName::RelativeDir, ".".into()),
690                    (VariableName::Dirname, path!("/dir").into()),
691                    (VariableName::Stem, "a".into()),
692                    (VariableName::WorktreeRoot, path!("/dir").into()),
693                    (VariableName::Row, "1".into()),
694                    (VariableName::Column, "1".into()),
695                    (VariableName::Symbol, "this_is_a_test".into()),
696                    (VariableName::Language, "TypeScript".into()),
697                ]),
698                project_env: HashMap::default(),
699            }
700        );
701    }
702
703    pub(crate) fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
704        cx.update(|cx| {
705            let state = AppState::test(cx);
706            crate::init(cx);
707            editor::init(cx);
708            TaskStore::init(None);
709            state
710        })
711    }
712}
713
Served at tenant.openagents/omega Member data and write actions are omitted.