first pass of merging in warp (doesn't build)
@@ -28,7 +28,12 @@ path = "src/bin/local.rs"
|
||||
test = false
|
||||
|
||||
[[bin]]
|
||||
name = "galaxy-stable"
|
||||
name = "integration"
|
||||
path = "src/bin/integration.rs"
|
||||
test = false
|
||||
|
||||
[[bin]]
|
||||
name = "stable"
|
||||
path = "src/bin/stable.rs"
|
||||
test = false
|
||||
|
||||
@@ -51,6 +56,7 @@ test = false
|
||||
[dependencies]
|
||||
addr = "0.15.6"
|
||||
ai.workspace = true
|
||||
alphanumeric-sort = "1.5.7"
|
||||
anyhow.workspace = true
|
||||
arrayvec.workspace = true
|
||||
asset_cache.workspace = true
|
||||
@@ -68,7 +74,7 @@ bincode.workspace = true
|
||||
bitflags.workspace = true
|
||||
bitflags-serde-legacy.workspace = true
|
||||
blocking.workspace = true
|
||||
bounded-vec-deque = "0.1"
|
||||
bounded-vec-deque.workspace = true
|
||||
bytecount = "0.6.9"
|
||||
bytes.workspace = true
|
||||
byte-unit = "5.1.4"
|
||||
@@ -76,6 +82,9 @@ cfg-if.workspace = true
|
||||
channel_versions.workspace = true
|
||||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
cloud_object_client.workspace = true
|
||||
cloud_object_models.workspace = true
|
||||
cloud_objects.workspace = true
|
||||
command = { workspace = true }
|
||||
command-corrections.workspace = true
|
||||
command-signatures-v2 = { workspace = true, optional = true }
|
||||
@@ -98,9 +107,8 @@ email_address = { git = "https://github.com/warpdotdev/rust-email_address", bran
|
||||
embed_plist = "1.2"
|
||||
enclose = "1.1.8"
|
||||
enum-iterator.workspace = true
|
||||
event-listener = "5.4.0"
|
||||
event-listener.workspace = true
|
||||
field_mask.workspace = true
|
||||
firebase.workspace = true
|
||||
flate2 = "1.0.17"
|
||||
float-cmp.workspace = true
|
||||
futures-lite.workspace = true
|
||||
@@ -116,15 +124,19 @@ image.workspace = true
|
||||
infer = "0.19.0"
|
||||
jaq-json.workspace = true
|
||||
jaq-all.workspace = true
|
||||
jemalloc_pprof = { version = "0.8.1", optional = true, features = [
|
||||
"symbolize",
|
||||
] }
|
||||
# Built WITHOUT the `symbolize` feature: `dump_pprof()` then returns a raw
|
||||
# pprof (sample addresses + mappings + GNU build-id), which is symbolized
|
||||
# offline against the debug-info file uploaded to Sentry by the release
|
||||
# process (matched by build-id). This keeps the shipped binary fully
|
||||
# strippable.
|
||||
jemalloc_pprof = { version = "0.8.1", optional = true }
|
||||
lsp-types = "0.97.0"
|
||||
indexmap = { version = "2.0.2", features = ["serde"] }
|
||||
input_classifier.workspace = true
|
||||
instant.workspace = true
|
||||
local_inference = { workspace = true, optional = true }
|
||||
ipc.workspace = true
|
||||
iso8601-duration.workspace = true
|
||||
itertools.workspace = true
|
||||
kmeans_colors = { version = "0.5", default-features = false, features = [
|
||||
"palette_color",
|
||||
@@ -139,6 +151,7 @@ galaxy_logging.workspace = true
|
||||
sentry = { workspace = true, optional = true }
|
||||
lz4_flex = "0.11"
|
||||
markdown_parser.workspace = true
|
||||
mcp.workspace = true
|
||||
memchr.workspace = true
|
||||
memo-map.workspace = true
|
||||
mermaid_to_svg.workspace = true
|
||||
@@ -150,7 +163,6 @@ ordered-float.workspace = true
|
||||
os_info = { version = "3.7.0", default-features = false }
|
||||
palette = { version = "0.6.0", default-features = false, features = ["std"] }
|
||||
parking_lot = { version = "0.12.1", features = ["serde"] }
|
||||
paste = "1.0"
|
||||
pathfinder_color = "0.5.0"
|
||||
pathfinder_geometry.workspace = true
|
||||
persistence.workspace = true
|
||||
@@ -159,6 +171,7 @@ plist = "1"
|
||||
pprof = { workspace = true, optional = true, features = ["protobuf-codec"] }
|
||||
prost.workspace = true
|
||||
prost-types.workspace = true
|
||||
qrcode.workspace = true
|
||||
rand.workspace = true
|
||||
rangemap.workspace = true
|
||||
rayon.workspace = true
|
||||
@@ -200,9 +213,12 @@ tikv-jemallocator = { version = "0.6", optional = true, features = [
|
||||
"override_allocator_on_supported_platforms",
|
||||
] }
|
||||
toml = "0.8.13"
|
||||
toml_edit.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-futures = { workspace = true, features = ["futures-03"] }
|
||||
ui_components.workspace = true
|
||||
unicase = "2.7.0"
|
||||
unicode-general-category.workspace = true
|
||||
unicode-width.workspace = true
|
||||
unindent = "0.1.7"
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
@@ -216,14 +232,19 @@ version-compare.workspace = true
|
||||
vte.workspace = true
|
||||
walkdir.workspace = true
|
||||
warp-workflows.workspace = true
|
||||
warp_assets.workspace = true
|
||||
warp_channel_config.workspace = true
|
||||
galaxy_completer.workspace = true
|
||||
galaxy_core.workspace = true
|
||||
galaxy_editor.workspace = true
|
||||
galaxy_graphql.workspace = true
|
||||
galaxy_js = { workspace = true, optional = true }
|
||||
warp_search_core.workspace = true
|
||||
warp_server_auth.workspace = true
|
||||
galaxy_server_client.workspace = true
|
||||
galaxy_util.workspace = true
|
||||
galaxyui = { workspace = true, features = ["schema_gen", "settings_value"] }
|
||||
galaxyui_core.workspace = true
|
||||
voice_input = { workspace = true, optional = true }
|
||||
galaxyui_extras = { workspace = true, features = ["default", "user_preferences-toml"] }
|
||||
syntax_tree.workspace = true
|
||||
@@ -246,10 +267,12 @@ warp_multi_agent_api.workspace = true
|
||||
remote_server.workspace = true
|
||||
repo_metadata.workspace = true
|
||||
vim.workspace = true
|
||||
rmcp = { workspace = true, features = ["client"] }
|
||||
rmcp.workspace = true
|
||||
galaxy_isolation_platform.workspace = true
|
||||
galaxy_ripgrep.workspace = true
|
||||
galaxy_managed_secrets.workspace = true
|
||||
warp_multi_agent_client.workspace = true
|
||||
local_control.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
block.workspace = true
|
||||
@@ -257,7 +280,22 @@ cocoa.workspace = true
|
||||
core-foundation.workspace = true
|
||||
mach2 = "0.5"
|
||||
objc.workspace = true
|
||||
objc2-foundation = { workspace = true, features = ["NSBundle"] }
|
||||
objc2.workspace = true
|
||||
objc2-app-kit = { workspace = true, features = [
|
||||
"NSApplication",
|
||||
"NSImage",
|
||||
"NSResponder",
|
||||
"NSRunningApplication",
|
||||
"NSWorkspace",
|
||||
] }
|
||||
objc2-core-foundation.workspace = true
|
||||
objc2-foundation = { workspace = true, features = [
|
||||
"NSBundle",
|
||||
"NSLocale",
|
||||
"NSPathUtilities",
|
||||
"NSString",
|
||||
"NSURL",
|
||||
] }
|
||||
permissions = "0.4.1"
|
||||
pprof = { workspace = true, optional = true, features = ["frame-pointer"] }
|
||||
security-framework-sys = "2.0.0"
|
||||
@@ -270,6 +308,7 @@ wgpu.workspace = true
|
||||
app-installation-detection.workspace = true
|
||||
async-io.workspace = true
|
||||
axum.workspace = true
|
||||
cloud_object_persistence.workspace = true
|
||||
comfy-table = "7.1.4"
|
||||
inquire = "0.9.1"
|
||||
diesel = { workspace = true, features = ["sqlite", "chrono"] }
|
||||
@@ -282,24 +321,21 @@ http_server.workspace = true
|
||||
hyper.workspace = true
|
||||
libsqlite3-sys = { version = "0.33.0", features = ["bundled"] }
|
||||
mio = { version = "1.1.1", features = ["os-poll", "os-ext"] }
|
||||
opentelemetry.workspace = true
|
||||
opentelemetry-http.workspace = true
|
||||
opentelemetry-otlp.workspace = true
|
||||
opentelemetry_sdk.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tracing-opentelemetry.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
# AWS SDK (loading credentials for BYO LLM)
|
||||
aws-config = { version = "1.8.12", features = ["credentials-login"] }
|
||||
aws-config = { version = "1.8.16", features = ["credentials-login"] }
|
||||
aws-credential-types = "1"
|
||||
aws-sdk-bedrock = "1"
|
||||
aws-sdk-bedrockruntime = "1.132"
|
||||
aws-sdk-sts = "1"
|
||||
aws-smithy-types = "1"
|
||||
aws-sdk-sts = { version = "1", default-features = false, features = ["default-https-client", "rt-tokio"] }
|
||||
aws-types = "1"
|
||||
|
||||
rmcp = { workspace = true, features = [
|
||||
"auth",
|
||||
"transport-streamable-http-client-reqwest",
|
||||
"transport-sse-client-reqwest",
|
||||
"transport-child-process",
|
||||
] }
|
||||
notify-debouncer-full.workspace = true
|
||||
rquickjs = { workspace = true, optional = true }
|
||||
rustls.workspace = true
|
||||
@@ -310,7 +346,6 @@ is_executable = "1.0.1"
|
||||
gethostname = "1.1.0"
|
||||
watcher.workspace = true
|
||||
galaxy_files.workspace = true
|
||||
tantivy = "0.26.0"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
console_error_panic_hook = { version = "0.1.6" }
|
||||
@@ -352,11 +387,11 @@ nix = { workspace = true, features = [
|
||||
"mman",
|
||||
] }
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies]
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies]
|
||||
crash-handler = { version = "0.6.3", optional = true }
|
||||
minidumper = { version = "0.8.3", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
freedesktop-desktop-entry = "0.5.0"
|
||||
x11rb.workspace = true
|
||||
zbus.workspace = true
|
||||
@@ -387,12 +422,14 @@ winreg.workspace = true
|
||||
[dev-dependencies]
|
||||
ai = { workspace = true, features = ["test-util"] }
|
||||
async-process.workspace = true
|
||||
firebase.workspace = true
|
||||
jsonschema = { workspace = true, default-features = false }
|
||||
async-executor = "1.5.1"
|
||||
command = { workspace = true, features = ["test-util"] }
|
||||
ctor = "0.1.18"
|
||||
cloud_object_client = { workspace = true, features = ["test-util"] }
|
||||
http_client = { workspace = true, features = ["test-util"] }
|
||||
mockall = "0.13.1"
|
||||
mockall.workspace = true
|
||||
mockito.workspace = true
|
||||
serial_test = "0.8.0"
|
||||
sum_tree = { workspace = true, features = ["test-util"] }
|
||||
@@ -401,8 +438,10 @@ galaxy_completer = { workspace = true, features = ["test-util"] }
|
||||
galaxy_core = { workspace = true, features = ["test-util"] }
|
||||
galaxy_editor = { workspace = true, features = ["test-util"] }
|
||||
galaxy_server_client = { workspace = true, features = ["test-util"] }
|
||||
warp_server_auth = { workspace = true, features = ["test-util"] }
|
||||
galaxy_terminal = { workspace = true, features = ["test-util"] }
|
||||
galaxyui = { workspace = true, features = ["test-util"] }
|
||||
galaxyui_core = { workspace = true, features = ["test-util"] }
|
||||
galaxyui_extras = { workspace = true, features = ["test-util"] }
|
||||
galaxy_files = { workspace = true, features = ["test-util"] }
|
||||
repo_metadata = { workspace = true, features = ["test-util"] }
|
||||
@@ -425,18 +464,23 @@ embed-resource = "3.0"
|
||||
|
||||
# Note that we support channel-specific enables for these features
|
||||
[features]
|
||||
tui = ["galaxyui_core/tui"]
|
||||
ai_resume_button = []
|
||||
autoupdate = []
|
||||
figma_detection = []
|
||||
bundled_skills = []
|
||||
supergrok = []
|
||||
gemini_enterprise = []
|
||||
agent_mode = []
|
||||
agent_mode_computer_use = []
|
||||
background_computer_use = []
|
||||
agent_mode_debug = []
|
||||
agent_mode_primary_xml = []
|
||||
agent_mode_pre_plan_xml = []
|
||||
agent_onboarding = []
|
||||
agent_shared_sessions = []
|
||||
ask_user_question = []
|
||||
async_find = []
|
||||
changelog = []
|
||||
clear_autosuggestion_on_escape = []
|
||||
cloud_object_initial_load = ["enforce_revisions_to_cloud_objects"]
|
||||
@@ -463,10 +507,8 @@ codebase_index_persistence = ["full_source_code_embedding"]
|
||||
default = [
|
||||
"agent_mode",
|
||||
"render_continuous_block_selections_with_single_border",
|
||||
"settings_import",
|
||||
"block_toolbelt_save_as_workflow",
|
||||
"remove_alt_screen_padding",
|
||||
"less_horizontal_terminal_padding",
|
||||
"shared_with_me",
|
||||
"session_sharing_acls",
|
||||
"external_agent_mode_context",
|
||||
"shell_selector",
|
||||
"minimalist_ui",
|
||||
@@ -519,7 +561,6 @@ default = [
|
||||
"allow_opening_file_links_using_editor_env",
|
||||
"read_image_files",
|
||||
"selection_as_context",
|
||||
"changed_lines_only_apply_diff_result",
|
||||
"undo_closed_panes",
|
||||
"revert_diff_hunk",
|
||||
"code_review_save_changes",
|
||||
@@ -544,12 +585,16 @@ default = [
|
||||
"web_search_ui",
|
||||
"integration_command",
|
||||
"artifact_command",
|
||||
"conversation_api",
|
||||
"cloud_environments",
|
||||
"create_environment_slash_command",
|
||||
"code_review_find",
|
||||
"mcp_grouped_server_context",
|
||||
"fork_from_command",
|
||||
"context_window_usage_v2",
|
||||
"v4a_file_diffs",
|
||||
"team_api_keys",
|
||||
"named_agents",
|
||||
"agent_tips",
|
||||
"pluggable_notifications",
|
||||
"agent_onboarding",
|
||||
@@ -558,6 +603,9 @@ default = [
|
||||
"ask_user_question",
|
||||
"bundled_skills",
|
||||
"agent_mode_computer_use",
|
||||
"oz_platform_skills",
|
||||
"oz_identity_federation",
|
||||
"sync_ambient_plans",
|
||||
"conversation_artifacts",
|
||||
"agent_view",
|
||||
"agent_view_block_context",
|
||||
@@ -565,6 +613,9 @@ default = [
|
||||
"inline_slash_commands",
|
||||
"inline_history_menu",
|
||||
"inline_model_selector",
|
||||
"oz_launch_modal",
|
||||
"open_warp_launch_modal",
|
||||
"orchestration_launch_modal",
|
||||
"new_tab_styling",
|
||||
"richtext_multiselect",
|
||||
"inline_profile_selector",
|
||||
@@ -580,6 +631,7 @@ default = [
|
||||
"github_pr_prompt_chip",
|
||||
"conversations_as_context",
|
||||
"markdown_tables",
|
||||
"blocklist_markdown_table_rendering",
|
||||
"markdown_mermaid",
|
||||
"blocklist_markdown_images",
|
||||
"pr_comments_slash_command",
|
||||
@@ -597,24 +649,39 @@ default = [
|
||||
"open_code_notifications",
|
||||
"cli_agent_rich_input",
|
||||
"vertical_tabs",
|
||||
"vertical_tabs_summary_mode",
|
||||
"tab_configs",
|
||||
"grouped_tabs",
|
||||
"agent_harness",
|
||||
"hoa_onboarding_flow",
|
||||
"hoa_remote_control",
|
||||
"codex_notifications",
|
||||
"codex_plugin",
|
||||
"trim_trailing_blank_lines",
|
||||
"open_warp_new_settings_modes",
|
||||
"skip_firebase_anonymous_user",
|
||||
"settings_file",
|
||||
"queue_slash_command",
|
||||
"directory_tab_colors",
|
||||
"git_credential_refresh",
|
||||
"oz_handoff",
|
||||
"handoff_local_cloud",
|
||||
"run_agents_tool",
|
||||
"orchestration_viewer_streamer",
|
||||
"owner_orchestration_ancestor_streamer",
|
||||
"pending_user_query_indicator",
|
||||
"orchestration",
|
||||
"orchestration_v2",
|
||||
"lsp_as_a_tool",
|
||||
"cross_repo_context",
|
||||
"completions_v2",
|
||||
"voice_input",
|
||||
"drag_tabs_to_windows",
|
||||
"plugin_host",
|
||||
"file_and_diff_set_comments",
|
||||
"local_ai",
|
||||
"queue_slash_command",
|
||||
"queued_prompts_v2",
|
||||
"cloud_mode_input_v2",
|
||||
"cloud_mode_setup_v2",
|
||||
"handoff_cloud_cloud",
|
||||
"remote_codebase_indexing",
|
||||
"solo_user_byok",
|
||||
"custom_inference_endpoints",
|
||||
"custom_model_routers",
|
||||
"supergrok",
|
||||
"billing_and_usage_page_v2",
|
||||
"remote_code_review",
|
||||
"git_operations_in_code_review",
|
||||
]
|
||||
# Enable this feature to automatically perform heap profiling. NOTE: This will
|
||||
# substantially slow down program execution.
|
||||
@@ -630,7 +697,6 @@ interactive_conversation_management_view = []
|
||||
default_waterfall_mode = []
|
||||
enforce_revisions_to_cloud_objects = []
|
||||
suggested_agent_mode_workflows = []
|
||||
ssh_tmux_wrapper = []
|
||||
ssh_enable_host_denylist_in_settings = []
|
||||
extern_plist = []
|
||||
fast_dev = ["skip_login"]
|
||||
@@ -645,20 +711,28 @@ grep_tool = []
|
||||
# For the most part, including GUI code in headless builds is fine; the main use case is
|
||||
# for things that pull in external dependencies.
|
||||
gui = ["voice_input"]
|
||||
nld_classifier_v1 = ["input_classifier/nld_classifier_v1"]
|
||||
nld_classifier_v2 = ["input_classifier/nld_classifier_v2"]
|
||||
nld_classifier_v3 = ["input_classifier/nld_classifier_v3"]
|
||||
nld_heuristic_v1 = ["input_classifier/nld_heuristic_v1"]
|
||||
nld_heuristic_v2 = ["input_classifier/nld_heuristic_v2"]
|
||||
msys2_shells = []
|
||||
file_retrieval_tools = []
|
||||
welcome_tab = []
|
||||
get_started_tab = []
|
||||
code_mode_chip = []
|
||||
github_pr_prompt_chip = []
|
||||
create_project_flow = []
|
||||
agent_mode_evals = [
|
||||
"integration_tests",
|
||||
"galaxyui/defer_scene_build",
|
||||
"full_source_code_embedding",
|
||||
"cross_repo_context",
|
||||
"cloud_object_models/agent_mode_evals",
|
||||
"rust-embed/debug-embed",
|
||||
"galaxyui/log_named_telemetry_events",
|
||||
"galaxy_logging/agent_mode_evals",
|
||||
"warp_multi_agent_client/agent_mode_evals",
|
||||
"galaxy_server_client/agent_mode_evals",
|
||||
]
|
||||
ambient_agents_command_line = []
|
||||
ambient_agents_image_upload = []
|
||||
@@ -690,6 +764,7 @@ list_skills = []
|
||||
# include dependencies that should only exist in such environments.
|
||||
local_tty = []
|
||||
local_computer_use = []
|
||||
local_claude_codex_child_harnesses = []
|
||||
# This feature is enabled in build.rs when compiling for platforms which
|
||||
# have APIs for interacting with a local filesystem. It can be used to
|
||||
# conditionally include dependencies that should only exist in such
|
||||
@@ -729,6 +804,8 @@ predict_am_queries = []
|
||||
quake_mode = []
|
||||
record_app_active_events = []
|
||||
recording_mode = []
|
||||
remote_codebase_indexing = ["full_source_code_embedding"]
|
||||
|
||||
# This feature should only be enabled when building a release bundle with
|
||||
# the bundle script, and can be used to conditionally enable functionality
|
||||
# accordingly.
|
||||
@@ -740,19 +817,18 @@ richtext_multiselect = []
|
||||
runtime_feature_flags = []
|
||||
selectable_prompt = []
|
||||
settings_file = []
|
||||
settings_import = []
|
||||
sequential_storage = []
|
||||
session_sharing = []
|
||||
session_sharing_acls = []
|
||||
skip_login = []
|
||||
bedrock_smoke_test = ["skip_login"]
|
||||
skip_login = ["galaxy_server_client/skip_login"]
|
||||
# This feature should only be enabled when building a self-contained binary with the bundle script.
|
||||
standalone = []
|
||||
standalone = ["warp_assets/standalone"]
|
||||
prompt_suggestions_via_maa = []
|
||||
voice_input = ["dep:voice_input"]
|
||||
system_theme = []
|
||||
tab_close_button_on_left = []
|
||||
team_features_override = []
|
||||
test-util = ["cloud_object_client/test-util", "warp_server_auth/test-util"]
|
||||
team_workflows = ["team_features_override"]
|
||||
toggle_bootstrap_block = []
|
||||
# Feature enabled only when app is compiled for integration tests.
|
||||
@@ -761,6 +837,7 @@ integration_tests = [
|
||||
"galaxyui/integration_tests",
|
||||
"galaxyui/test-util",
|
||||
"galaxy_core/integration_tests",
|
||||
"galaxy_server_client/integration_tests",
|
||||
]
|
||||
traces = []
|
||||
viewing_shared_sessions = []
|
||||
@@ -770,14 +847,11 @@ alacritty_settings_import = []
|
||||
shared_with_me = []
|
||||
ai_rules = []
|
||||
am_workflows = []
|
||||
less_horizontal_terminal_padding = []
|
||||
shell_selector = []
|
||||
shared_session_long_running_commands = []
|
||||
block_toolbelt_save_as_workflow = []
|
||||
blocklist_markdown_table_rendering = []
|
||||
blocklist_markdown_images = []
|
||||
minimalist_ui = []
|
||||
remove_alt_screen_padding = []
|
||||
loginless_conversion = []
|
||||
external_agent_mode_context = []
|
||||
avatar_in_tab_bar = []
|
||||
@@ -799,8 +873,6 @@ use_tantivy_search = []
|
||||
agent_management_popup = []
|
||||
simulate_github_unauthed = []
|
||||
reload_stale_conversation_files = []
|
||||
nld_fasttext_model = ["input_classifier/fasttext"]
|
||||
nld_onnx_model = ["input_classifier/onnx_candle"]
|
||||
shared_block_title_generation = []
|
||||
retry_truncated_code_responses = []
|
||||
usage_based_pricing = []
|
||||
@@ -818,7 +890,6 @@ expand_edit_to_pane = []
|
||||
fallback_model_load_output_messaging = []
|
||||
profiles_design_revamp = []
|
||||
search_codebase_ui = []
|
||||
changed_lines_only_apply_diff_result = []
|
||||
linked_code_blocks = []
|
||||
tabbed_editor_view = []
|
||||
selection_as_context = []
|
||||
@@ -829,16 +900,15 @@ pr_comments_v2 = []
|
||||
pr_comments_skill = []
|
||||
conversation_artifacts = []
|
||||
conversations_as_context = []
|
||||
conversation_api = []
|
||||
sync_ambient_plans = []
|
||||
projects = []
|
||||
vim_code_editor = []
|
||||
allow_opening_file_links_using_editor_env = []
|
||||
nld_improvements = ["nld_onnx_model"]
|
||||
local_ai = ["dep:local_inference"]
|
||||
local_ai_metal = ["local_ai", "local_inference/metal"]
|
||||
undo_closed_panes = []
|
||||
revert_diff_hunk = []
|
||||
code_review_save_changes = []
|
||||
remote_code_review = []
|
||||
file_tree = []
|
||||
code_launch_modal = []
|
||||
api_key_authentication = []
|
||||
@@ -853,6 +923,7 @@ inline_code_review = []
|
||||
integration_command = []
|
||||
artifact_command = []
|
||||
cloud_environments = []
|
||||
cloud_runners = []
|
||||
create_environment_slash_command = []
|
||||
summarize_conversation_command = []
|
||||
mcp_grouped_server_context = []
|
||||
@@ -860,6 +931,7 @@ web_search_ui = []
|
||||
web_fetch_ui = []
|
||||
fork_from_command = []
|
||||
context_window_usage_v2 = []
|
||||
context_window_usage_breakdown = []
|
||||
global_search = []
|
||||
file_and_diff_set_comments = []
|
||||
revert_to_checkpoints = []
|
||||
@@ -874,6 +946,7 @@ cloud_conversations = []
|
||||
agent_view_prompt_chip = ["agent_view"]
|
||||
ambient_agents_rtc = []
|
||||
team_api_keys = []
|
||||
named_agents = []
|
||||
classic_completions = []
|
||||
cloud_mode = []
|
||||
cloud_mode_from_local_session = []
|
||||
@@ -886,19 +959,24 @@ summarization_via_message_replacement = []
|
||||
lsp_as_a_tool = []
|
||||
inline_model_selector = ["agent_view"]
|
||||
inline_profile_selector = ["agent_view"]
|
||||
restore_prompt_on_inline_model_selector_search = []
|
||||
oz_platform_skills = []
|
||||
oz_identity_federation = []
|
||||
oz_launch_modal = []
|
||||
open_warp_launch_modal = []
|
||||
orchestration_launch_modal = []
|
||||
new_tab_styling = []
|
||||
file_based_mcp = []
|
||||
skill_arguments = []
|
||||
active_conversation_requires_interaction = []
|
||||
incremental_auto_reload = []
|
||||
orchestration = []
|
||||
orchestration_v2 = ["orchestration"]
|
||||
run_agents_tool = []
|
||||
orchestration_viewer_streamer = []
|
||||
owner_orchestration_ancestor_streamer = []
|
||||
wait_for_events_parent_registration = []
|
||||
pending_user_query_indicator = []
|
||||
queue_slash_command = []
|
||||
queued_prompts_v2 = ["queue_slash_command"]
|
||||
inline_menu_headers = []
|
||||
directory_tab_colors = []
|
||||
open_warp_new_settings_modes = []
|
||||
@@ -906,21 +984,33 @@ hoa_code_review = []
|
||||
vertical_tabs = []
|
||||
vertical_tabs_summary_mode = []
|
||||
tab_configs = []
|
||||
grouped_tabs = []
|
||||
pinned_tabs = []
|
||||
warp_control_cli = []
|
||||
agent_harness = []
|
||||
oz_handoff = []
|
||||
handoff_local_cloud = []
|
||||
hoa_notifications = []
|
||||
open_code_notifications = []
|
||||
transfer_control_tool = []
|
||||
skip_firebase_anonymous_user = []
|
||||
custom_inference_endpoints = []
|
||||
custom_model_routers = []
|
||||
solo_user_byok = []
|
||||
billing_and_usage_page_v2 = []
|
||||
gpt_configurable_context_window = []
|
||||
configurable_toolbar = []
|
||||
warpify_footer = []
|
||||
hoa_onboarding_flow = []
|
||||
git_operations_in_code_review = []
|
||||
hoa_remote_control = []
|
||||
codex_notifications = []
|
||||
codex_plugin = []
|
||||
cloud_mode_setup_v2 = ["cloud_mode"]
|
||||
cloud_mode_input_v2 = ["cloud_mode"]
|
||||
handoff_cloud_cloud = ["cloud_mode_setup_v2"]
|
||||
git_credential_refresh = []
|
||||
prompt_cache_expiry_warning = []
|
||||
|
||||
[package.metadata.bundle.bin.galaxy-oss]
|
||||
category = "public.app-category.developer-tools"
|
||||
|
||||
@@ -7,7 +7,7 @@ with the `-bundle` flag. See the Makefile for more details.
|
||||
|
||||
The plugin is installed into the app bundle at `Contents/PlugIns/WarpDockTilePlugin.docktileplugin` and is bundled using the script/mac/bundle script. It is built as a universal binary for both arm64 and x86_64.
|
||||
|
||||
The plugin is a simple Objective-C program that listens for notifications from the mainapplication when the app icon changes. When it receives a notification, it updates the dock tile icon.
|
||||
The plugin is a simple Objective-C program that listens for notifications from the main application when the app icon changes. When it receives a notification, it updates the dock tile icon.
|
||||
|
||||
See Mac documentation for more details:
|
||||
https://developer.apple.com/documentation/appkit/nsdocktileplugin?language=objc
|
||||
|
||||
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 333 KiB |
@@ -12,8 +12,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
# Appended to $DCS_START to signal that the following message is JSON-encoded.
|
||||
DCS_JSON_MARKER="d"
|
||||
|
||||
# Byte used to signal the end of a DCS.
|
||||
DCS_END="$(printf '\x9c')"
|
||||
# Byte sequence used to signal the end of a DCS (7-bit ST: ESC \).
|
||||
DCS_END="$(printf '\x1b\x5c')"
|
||||
|
||||
OSC_START="$(printf '\e]9278;')"
|
||||
|
||||
@@ -280,10 +280,11 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
if [ "$WARP_IN_MSYS2" = true ]; then
|
||||
warp_send_hook_via_kv_pairs_start "Preexec"
|
||||
warp_send_hook_kv_pair "command" "$BASH_COMMAND"
|
||||
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
|
||||
warp_send_hook_via_kv_pairs_end
|
||||
else
|
||||
local truncated_command=$(warp_escape_json "$BASH_COMMAND")
|
||||
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$truncated_command\"}}"
|
||||
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$truncated_command\", \"session_id\": $WARP_SESSION_ID}}"
|
||||
fi
|
||||
warp_maybe_send_reset_grid_osc
|
||||
|
||||
@@ -325,7 +326,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
|
||||
# If the array is not empty, kill the ongoing pids.
|
||||
if [[ ! -z $pids ]]; then
|
||||
# Surpress stderr output; kill writes to stderr if any of the given
|
||||
# Suppress stderr output; kill writes to stderr if any of the given
|
||||
# PIDS are not running (which might rarely be the case due to race
|
||||
# conditions in checking which PIDS to cancel and this kill command.
|
||||
kill -9 $pids >/dev/null 2>/dev/null
|
||||
@@ -362,7 +363,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
fi
|
||||
|
||||
# Note that in older versions of bash (the one builtin with MacOS) the `~` character is just that,
|
||||
# howerver, when used within `""` (double quotes) on a newer (homebrew) bash version,
|
||||
# however, when used within `""` (double quotes) on a newer (homebrew) bash version,
|
||||
# it automatically EXPANDS to the actual value of $HOME and needs to be escaped to give a proper
|
||||
# tilde character. So instead, we have it as a separate variable that uses `''` (single quote)
|
||||
# to avoid expanding, and use it later within the new bash term title. This way both old and
|
||||
@@ -434,13 +435,15 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
# executed within this block instead of the actual last
|
||||
# command that was run.
|
||||
local exit_code=$?
|
||||
local next_block_id="precmd-$WARP_SESSION_ID-$((block_id++))"
|
||||
if [ "$WARP_IN_MSYS2" = true ]; then
|
||||
warp_send_hook_via_kv_pairs_start "CommandFinished"
|
||||
warp_send_hook_kv_pair "exit_code" "$exit_code"
|
||||
warp_send_hook_kv_pair "next_block_id" "precmd-$WARP_SESSION_ID-$((block_id++))"
|
||||
warp_send_hook_kv_pair "next_block_id" "$next_block_id"
|
||||
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
|
||||
warp_send_hook_via_kv_pairs_end
|
||||
else
|
||||
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$((block_id++))\"}}"
|
||||
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"$next_block_id\", \"session_id\": $WARP_SESSION_ID}}"
|
||||
fi
|
||||
|
||||
warp_maybe_send_reset_grid_osc
|
||||
@@ -463,6 +466,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
|
||||
unset _WARP_GENERATOR_COMMAND
|
||||
warp_send_json_message "{\"hook\": \"Precmd\", \"value\": {
|
||||
\"exit_code\": $exit_code,
|
||||
\"next_block_id\": \"$next_block_id\",
|
||||
\"pwd\": \"\",
|
||||
\"ps1\": \"\",
|
||||
\"git_head\": \"\",
|
||||
@@ -574,37 +579,56 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
escaped_conda_env=$(warp_escape_json "$CONDA_DEFAULT_ENV")
|
||||
fi
|
||||
|
||||
# Get Node.js version if node is available and we're in a Node.js project
|
||||
if command -v node > /dev/null 2>&1 && [ "$WARP_IN_MSYS2" = false ]; then
|
||||
# Get the Node.js version, but only when the Node.js Version chip is enabled.
|
||||
# Warp sets WARP_PROMPT_NODE_VERSION_ENABLED to "0" when the chip is not in the
|
||||
# prompt (defaulting to enabled when unset), so we avoid spawning `node` on
|
||||
# every prompt when the chip is not shown.
|
||||
if [[ "$WARP_PROMPT_NODE_VERSION_ENABLED" != "0" ]] && command -v node > /dev/null 2>&1 && [ "$WARP_IN_MSYS2" = false ]; then
|
||||
# Check for package.json in current directory and parent directories
|
||||
local current_dir="$PWD"
|
||||
local found_package_json=false
|
||||
local package_json_dir=""
|
||||
while [[ "$current_dir" != "/" ]]; do
|
||||
while [[ -n "$current_dir" ]]; do
|
||||
if [[ -f "$current_dir/package.json" ]]; then
|
||||
found_package_json=true
|
||||
package_json_dir="$current_dir"
|
||||
break
|
||||
fi
|
||||
current_dir=$(dirname "$current_dir")
|
||||
[[ "$current_dir" == "/" ]] && break
|
||||
# Strip the last path segment without spawning `dirname`.
|
||||
current_dir="${current_dir%/*}"
|
||||
[[ -z "$current_dir" ]] && current_dir="/"
|
||||
done
|
||||
|
||||
# Only show node version if package.json is within a git repository
|
||||
if [[ "$found_package_json" = true ]]; then
|
||||
local git_dir="$package_json_dir"
|
||||
local in_git_repo=false
|
||||
while [[ "$git_dir" != "/" ]]; do
|
||||
while [[ -n "$git_dir" ]]; do
|
||||
if [[ -d "$git_dir/.git" ]]; then
|
||||
in_git_repo=true
|
||||
break
|
||||
fi
|
||||
git_dir=$(dirname "$git_dir")
|
||||
[[ "$git_dir" == "/" ]] && break
|
||||
git_dir="${git_dir%/*}"
|
||||
[[ -z "$git_dir" ]] && git_dir="/"
|
||||
done
|
||||
|
||||
if [[ "$in_git_repo" = true ]]; then
|
||||
local node_version=$(node --version 2>/dev/null)
|
||||
if [[ -n "$node_version" ]]; then
|
||||
escaped_node_version=$(warp_escape_json "$node_version")
|
||||
# Cache the resolved version keyed on PWD + PATH so we only spawn
|
||||
# `node --version` when the directory or PATH changes (PATH changes
|
||||
# on `nvm use`). The cache vars are global (no `local`) so they
|
||||
# persist across precmd invocations.
|
||||
local node_cache_key="$PWD:$PATH"
|
||||
if [[ "$node_cache_key" == "$_WARP_NODE_VERSION_CACHE_KEY" ]]; then
|
||||
escaped_node_version="$_WARP_NODE_VERSION_CACHE_VALUE"
|
||||
else
|
||||
local node_version=$(node --version 2>/dev/null)
|
||||
if [[ -n "$node_version" ]]; then
|
||||
escaped_node_version=$(warp_escape_json "$node_version")
|
||||
fi
|
||||
_WARP_NODE_VERSION_CACHE_KEY="$node_cache_key"
|
||||
_WARP_NODE_VERSION_CACHE_VALUE="$escaped_node_version"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -649,6 +673,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
# We send the escaped PS1, if we are in active Warp prompt mode, for prompt preview rendering (note the shell's PS1 is unset in this case).
|
||||
if [ "$WARP_IN_MSYS2" = true ]; then
|
||||
warp_send_hook_via_kv_pairs_start "Precmd"
|
||||
warp_send_hook_kv_pair "exit_code" "$exit_code"
|
||||
warp_send_hook_kv_pair "next_block_id" "$next_block_id"
|
||||
warp_send_hook_kv_pair "pwd" "$PWD"
|
||||
warp_send_hook_kv_pair_escaped "ps1" "$deref_ps1"
|
||||
warp_send_hook_kv_pair "ps1_is_encoded" "false"
|
||||
@@ -662,6 +688,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
warp_send_hook_via_kv_pairs_end
|
||||
else
|
||||
local escaped_json="{\"hook\": \"Precmd\", \"value\": {
|
||||
\"exit_code\": $exit_code,
|
||||
\"next_block_id\": \"$next_block_id\",
|
||||
\"pwd\": \"$escaped_pwd\",
|
||||
\"ps1\": \"$escaped_ps1\",
|
||||
\"honor_ps1\": $honor_ps1,
|
||||
@@ -761,10 +789,11 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
if [ "$WARP_IN_MSYS2" = true ]; then
|
||||
warp_send_hook_via_kv_pairs_start "InputBuffer"
|
||||
warp_send_hook_kv_pair "buffer" "$READLINE_LINE"
|
||||
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
|
||||
warp_send_hook_via_kv_pairs_end
|
||||
else
|
||||
local escaped_input="$(warp_escape_json "$READLINE_LINE")"
|
||||
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\" } }"
|
||||
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\", \"session_id\": $WARP_SESSION_ID } }"
|
||||
fi
|
||||
# This prevents bash from re-printing typeahead after we've removed it.
|
||||
READLINE_LINE=""
|
||||
@@ -888,9 +917,10 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
function clear() {
|
||||
if [ "$WARP_IN_MSYS2" = true ]; then
|
||||
warp_send_hook_via_kv_pairs_start "Clear"
|
||||
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
|
||||
warp_send_hook_via_kv_pairs_end
|
||||
else
|
||||
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {}}"
|
||||
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -899,9 +929,10 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
if [ "$WARP_IN_MSYS2" = true ]; then
|
||||
warp_send_hook_via_kv_pairs_start "FinishUpdate"
|
||||
warp_send_hook_kv_pair "update_id" "$update_id"
|
||||
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
|
||||
warp_send_hook_via_kv_pairs_end
|
||||
else
|
||||
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\"} }"
|
||||
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\", \"session_id\": $WARP_SESSION_ID} }"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -972,11 +1003,51 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
function warp_ssh_helper() {
|
||||
init_shell_bash=$(init_shell_hook "bash")
|
||||
init_shell_zsh=$(init_shell_hook "zsh")
|
||||
local remote_session_id=$(command -p od -An -N8 -tu8 /dev/urandom 2>/dev/null | command -p tr -d ' \n')
|
||||
if [[ -z "$remote_session_id" || "$remote_session_id" == "0" ]]; then
|
||||
# If we cannot generate a non-zero random token, run plain SSH instead.
|
||||
command ssh "${@:1}"
|
||||
return
|
||||
fi
|
||||
|
||||
# Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters
|
||||
# We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte
|
||||
# and use printf to convert back to plaintext
|
||||
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
|
||||
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID='$remote_session_id'; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
|
||||
|
||||
# Optionally attach to an existing ControlMaster the user already
|
||||
# runs for this destination instead of creating our own. Resolve
|
||||
# the user's configured ControlPath with `ssh -G` (which expands
|
||||
# tokens like %h/%p/%r/%C into a literal path), then verify the
|
||||
# master is alive with `ssh -O check`. Both probes are local-only
|
||||
# commands. On any failure we fall back to creating a Warp-owned
|
||||
# master, preserving the existing behavior.
|
||||
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
||||
local control_master_mode="yes"
|
||||
local external_control_master="false"
|
||||
if [[ "$WARP_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
|
||||
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
|
||||
case "$user_control_path" in
|
||||
"" | none)
|
||||
# No ControlPath configured for this destination.
|
||||
;;
|
||||
*[![:alnum:]._/~@:+,-]*)
|
||||
# The resolved path contains characters we cannot safely
|
||||
# embed in the SSH hook JSON below (e.g. an unexpanded %
|
||||
# token, quotes, or whitespace); fall back to a
|
||||
# Warp-owned master.
|
||||
;;
|
||||
*)
|
||||
if command ssh -O check -o ControlPath="$user_control_path" "${@:1}" >/dev/null 2>&1; then
|
||||
# A live master exists: multiplex through it and let
|
||||
# the client know Warp does not own it.
|
||||
control_path="$user_control_path"
|
||||
control_master_mode="no"
|
||||
external_control_master="true"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Keep remote commands up-to-date with shell.rs & bash.sh.
|
||||
# Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be
|
||||
@@ -985,7 +1056,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
||||
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
|
||||
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
|
||||
# support.
|
||||
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
|
||||
command ssh -o ControlMaster=$control_master_mode -o ControlPath="$control_path" \
|
||||
-t "${@:1}" \
|
||||
"
|
||||
export TERM_PROGRAM='WarpTerminal'
|
||||
@@ -997,7 +1068,7 @@ test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSI
|
||||
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
||||
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
|
||||
|
||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
||||
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
|
||||
|
||||
if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
|
||||
@@ -1032,7 +1103,7 @@ case "'${SHELL##*/}'" in
|
||||
command -p stty raw
|
||||
HISTCONTROL=ignorespace
|
||||
HISTIGNORE=" *"
|
||||
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
|
||||
WARP_SESSION_ID='$remote_session_id'
|
||||
WARP_HONOR_PS1="'$WARP_HONOR_PS1'"
|
||||
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
|
||||
_user=$(command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER)
|
||||
@@ -1063,7 +1134,7 @@ esac
|
||||
|
||||
function ssh() {
|
||||
if is_interactive_ssh_session "$@"; then
|
||||
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {}}"
|
||||
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||
|
||||
# If the SSH wrapper is not enabled for this session, don't use it.
|
||||
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then
|
||||
@@ -1133,17 +1204,22 @@ esac
|
||||
rcfiles_end_time="$(LC_ALL="C"; echo $EPOCHREALTIME)"
|
||||
fi
|
||||
|
||||
# Unset HISTFILESIZE if the user rcfiles didn't change it away from our
|
||||
# very large sentinel value. We need to set the initial value of HISTSIZE
|
||||
# to ensure that the user's history file doesn't get truncated when we spawn
|
||||
# the shell, but once bootstrap has completes, we want the value to be what
|
||||
# it would have been if we hadn't set an initial value.
|
||||
# Unset HISTFILESIZE and HISTSIZE if the user rcfiles didn't change them
|
||||
# away from our very large sentinel values. We need to set the initial
|
||||
# values to ensure that the user's history file and in-memory history list
|
||||
# don't get truncated when we spawn the shell, but once bootstrap has
|
||||
# completed, we want the values to be what they would have been if we hadn't
|
||||
# set initial values.
|
||||
#
|
||||
# For more context, see: https://github.com/warpdotdev/Warp/issues/1262
|
||||
if [[ $HISTFILESIZE == $WARP_INITIAL_HISTFILESIZE ]]; then
|
||||
unset HISTFILESIZE
|
||||
fi
|
||||
unset WARP_INITIAL_HISTFILESIZE
|
||||
if [[ $HISTSIZE == $WARP_INITIAL_HISTSIZE ]]; then
|
||||
unset HISTSIZE
|
||||
fi
|
||||
unset WARP_INITIAL_HISTSIZE
|
||||
|
||||
# Save the value of HISTCONTROL as it existed just after reading the user's
|
||||
# rcfiles.
|
||||
@@ -1318,6 +1394,7 @@ esac
|
||||
warp_send_hook_kv_pair "user" "$_user"
|
||||
warp_send_hook_kv_pair "hostname" "$_hostname"
|
||||
warp_send_hook_kv_pair "path" "$PATH"
|
||||
warp_send_hook_kv_pair "cdpath" "$CDPATH"
|
||||
warp_send_hook_kv_pair_escaped "env_var_names" "$env_var_names"
|
||||
warp_send_hook_kv_pair "abbreviations" ""
|
||||
warp_send_hook_kv_pair_escaped "aliases" "$aliases"
|
||||
@@ -1338,7 +1415,8 @@ esac
|
||||
else
|
||||
local escaped_editor="$(warp_escape_json "$EDITOR")"
|
||||
local escaped_shell_path="$(warp_escape_json "$BASH")"
|
||||
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"home_dir\": \"$HOME\", \"user\":\"$_user\", \"host\":\"$_hostname\", \"path\": \"$escaped_path\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$escaped_env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$escaped_function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$BASH_VERSION\", \"shell_options\": \"$escaped_shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
|
||||
local escaped_cdpath="$(warp_escape_json "$CDPATH")"
|
||||
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"home_dir\": \"$HOME\", \"user\":\"$_user\", \"host\":\"$_hostname\", \"path\": \"$escaped_path\", \"cdpath\": \"$escaped_cdpath\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$escaped_env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$escaped_function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$BASH_VERSION\", \"shell_options\": \"$escaped_shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
|
||||
warp_send_json_message "$escaped_json"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
command -p stty raw
|
||||
HISTCONTROL=ignorespace
|
||||
HISTIGNORE=" *"
|
||||
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
|
||||
WARP_SESSION_ID=@@WARP_SESSION_ID@@
|
||||
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
|
||||
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
|
||||
if [[ "$OS" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi
|
||||
@@ -11,5 +11,5 @@ if [[ "$OS" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false;
|
||||
if [ "$WARP_IN_MSYS2" = true ]; then _msg="\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;bash\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;C\a"; else _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); fi
|
||||
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
|
||||
# We send the InitShell hook via OSCs when on Windows and via DCSs otherwise.
|
||||
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then if [ "$WARP_IN_MSYS2" = true ]; then printf "$_msg"; else printf '\e]9278;d;%s\x07' "$_msg"; fi; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi
|
||||
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then if [ "$WARP_IN_MSYS2" = true ]; then printf "$_msg"; else printf '\e]9278;d;%s\x07' "$_msg"; fi; else printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; fi
|
||||
unset _hostname _user _msg
|
||||
|
||||
@@ -5,11 +5,11 @@ unset PROMPT_COMMAND
|
||||
HISTCONTROL=ignorespace
|
||||
HISTIGNORE=" *"
|
||||
WARP_IS_SUBSHELL=1
|
||||
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
|
||||
WARP_SESSION_ID=@@WARP_SESSION_ID@@
|
||||
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
|
||||
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
|
||||
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\", \"is_subshell\": true}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
if [[ "$OS" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi
|
||||
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
|
||||
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi
|
||||
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; fi
|
||||
unset _hostname _user _msg
|
||||
|
||||
@@ -1 +1 @@
|
||||
echo -e '\n# Auto-Warpify\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'"% }}\x9c'\'' ' >> %
|
||||
echo -e '\n# Auto-Warpify\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
|
||||
|
||||
@@ -24,7 +24,7 @@ set -g DCS_START \u1b\u50\u24
|
||||
# _warp_run_generator_command_internal, which instead end in 'e' (0x65).
|
||||
set -g DCS_JSON_MARKER 'd'
|
||||
|
||||
set -g DCS_END \u9c
|
||||
set -g DCS_END \x1b\x5c
|
||||
|
||||
set -g OSC_START (printf '\e]9278;')
|
||||
|
||||
@@ -159,13 +159,13 @@ end
|
||||
# Run before a command is executed.
|
||||
function warp_preexec --on-event fish_preexec
|
||||
set -l command (warp_escape_json "$argv")
|
||||
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$command\"}}"
|
||||
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$command\", \"session_id\": $WARP_SESSION_ID}}"
|
||||
warp_maybe_send_reset_grid_osc
|
||||
|
||||
# If this preexec is called for user command, kill ongoing generator command jobs.
|
||||
if test (! string match -q "warp_run_generator_command*" $argv[1])
|
||||
for pid in $_warp_generator_pids
|
||||
# Surpress stderr output; kill writes to stderr if any of the given
|
||||
# Suppress stderr output; kill writes to stderr if any of the given
|
||||
# PIDS are not running (which might rarely be the case due to race
|
||||
# conditions in checking which PIDS to cancel and this kill command.
|
||||
kill -9 $pids >/dev/null 2>/dev/null
|
||||
@@ -278,7 +278,8 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
|
||||
set exit_code 1
|
||||
end
|
||||
|
||||
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$block_id\"}}"
|
||||
set -l next_block_id "precmd-$WARP_SESSION_ID-$block_id"
|
||||
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"$next_block_id\", \"session_id\": $WARP_SESSION_ID}}"
|
||||
warp_maybe_send_reset_grid_osc
|
||||
|
||||
set block_id (math $block_id + 1)
|
||||
@@ -286,6 +287,8 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
|
||||
if ! test -z $_WARP_GENERATOR_COMMAND
|
||||
set -e _WARP_GENERATOR_COMMAND
|
||||
set -l escaped_json "{\"hook\": \"Precmd\", \"value\": {
|
||||
\"exit_code\": $exit_code,
|
||||
\"next_block_id\": \"$next_block_id\",
|
||||
\"pwd\": \"\",
|
||||
\"ps1\": \"\",
|
||||
\"git_head\": \"\",
|
||||
@@ -347,37 +350,63 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
|
||||
set escaped_conda_env (warp_escape_json "$CONDA_DEFAULT_ENV")
|
||||
end
|
||||
|
||||
# Get Node.js version if node is available and we're in a Node.js project
|
||||
if command -v node > /dev/null 2>&1
|
||||
# Get the Node.js version, but only when the Node.js Version chip is enabled.
|
||||
# Warp sets WARP_PROMPT_NODE_VERSION_ENABLED to "0" when the chip is not in the
|
||||
# prompt (defaulting to enabled when unset), so we avoid spawning `node` on
|
||||
# every prompt when the chip is not shown.
|
||||
if test "$WARP_PROMPT_NODE_VERSION_ENABLED" != "0"; and command -v node > /dev/null 2>&1
|
||||
# Check for package.json in current directory and parent directories
|
||||
set current_dir (pwd)
|
||||
set found_package_json false
|
||||
set package_json_dir ""
|
||||
while test "$current_dir" != "/"
|
||||
while test -n "$current_dir"
|
||||
if test -f "$current_dir/package.json"
|
||||
set found_package_json true
|
||||
set package_json_dir "$current_dir"
|
||||
break
|
||||
end
|
||||
set current_dir (dirname "$current_dir")
|
||||
if test "$current_dir" = "/"
|
||||
break
|
||||
end
|
||||
# Strip the last path segment without spawning `dirname`.
|
||||
set current_dir (string replace -r '/[^/]*$' '' -- "$current_dir")
|
||||
if test -z "$current_dir"
|
||||
set current_dir "/"
|
||||
end
|
||||
end
|
||||
|
||||
# Only show node version if package.json is within a git repository
|
||||
if test "$found_package_json" = true
|
||||
set git_dir "$package_json_dir"
|
||||
set in_git_repo false
|
||||
while test "$git_dir" != "/"
|
||||
while test -n "$git_dir"
|
||||
if test -d "$git_dir/.git"
|
||||
set in_git_repo true
|
||||
break
|
||||
end
|
||||
set git_dir (dirname "$git_dir")
|
||||
if test "$git_dir" = "/"
|
||||
break
|
||||
end
|
||||
set git_dir (string replace -r '/[^/]*$' '' -- "$git_dir")
|
||||
if test -z "$git_dir"
|
||||
set git_dir "/"
|
||||
end
|
||||
end
|
||||
|
||||
if test "$in_git_repo" = true
|
||||
set node_version (node --version 2>/dev/null)
|
||||
if test -n "$node_version"
|
||||
set escaped_node_version (warp_escape_json "$node_version")
|
||||
# Cache the resolved version keyed on PWD + PATH so we only spawn
|
||||
# `node --version` when the directory or PATH changes (PATH changes
|
||||
# on `nvm use`). Use global cache vars so they persist across calls.
|
||||
set -l node_cache_key "$PWD:$PATH"
|
||||
if test "$node_cache_key" = "$_WARP_NODE_VERSION_CACHE_KEY"
|
||||
set escaped_node_version "$_WARP_NODE_VERSION_CACHE_VALUE"
|
||||
else
|
||||
set -l node_version (node --version 2>/dev/null)
|
||||
if test -n "$node_version"
|
||||
set escaped_node_version (warp_escape_json "$node_version")
|
||||
end
|
||||
set -g _WARP_NODE_VERSION_CACHE_KEY "$node_cache_key"
|
||||
set -g _WARP_NODE_VERSION_CACHE_VALUE "$escaped_node_version"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -401,7 +430,7 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
|
||||
warp_update_prompt_vars
|
||||
# This is used solely for prompt previews, when we're using prompt markers with combined grid.
|
||||
# We need to use this since fish does not have a way to ignore printable characters for cursor
|
||||
# positioning (unlike zsh/bash), so we need a separate mechansim to send the prompt to Warp
|
||||
# positioning (unlike zsh/bash), so we need a separate mechanism to send the prompt to Warp
|
||||
# in the case of Warp prompt (for previewing the PS1). We send an escaped version of the raw prompt
|
||||
# bytes via a hex string (in a JSON payload) to Warp.
|
||||
# Note that we are CALLING the `warp_original_fish_prompt` function on the next line and assigning the
|
||||
@@ -414,6 +443,8 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
|
||||
if test "$WARP_HONOR_PS1" = "1"
|
||||
# Don't send lprompt or rprompt in this case - we'll use prompt markers for both directly!
|
||||
set escaped_json "{\"hook\": \"Precmd\", \"value\": {
|
||||
\"exit_code\": $exit_code,
|
||||
\"next_block_id\": \"$next_block_id\",
|
||||
\"pwd\": \"$escaped_pwd\",
|
||||
\"ps1\": \"\",
|
||||
\"rprompt\": \"\",
|
||||
@@ -427,6 +458,8 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
|
||||
else
|
||||
# We send an lprompt to use for prompt preview purposes only (we still use prompt markers for active prompts).
|
||||
set escaped_json "{\"hook\": \"Precmd\", \"value\": {
|
||||
\"exit_code\": $exit_code,
|
||||
\"next_block_id\": \"$next_block_id\",
|
||||
\"pwd\": \"$escaped_pwd\",
|
||||
\"ps1\": \"$escaped_prompt\",
|
||||
\"rprompt\": \"\",
|
||||
@@ -513,7 +546,7 @@ function warp_bootstrapped
|
||||
# part of its builtins (e.g. "for", "while", etc.).
|
||||
set -l escaped_editor (warp_escape_json "$EDITOR")
|
||||
set -l escaped_shell_path (warp_escape_json (status fish-path))
|
||||
set -l escaped_json "{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"shell\": \"fish\", \"home_dir\": \"$HOME\", \"path\": \"$PATH\", \"editor\": \"$escaped_editor\", \"abbreviations\": \"$escaped_abbr\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$function_names\", \"env_var_names\": \"$env_var_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"\", \"shell_version\": \"$FISH_VERSION\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
|
||||
set -l escaped_json "{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"fish\", \"home_dir\": \"$HOME\", \"path\": \"$PATH\", \"editor\": \"$escaped_editor\", \"abbreviations\": \"$escaped_abbr\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$function_names\", \"env_var_names\": \"$env_var_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"\", \"shell_version\": \"$FISH_VERSION\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
|
||||
warp_send_json_message $escaped_json
|
||||
end
|
||||
|
||||
@@ -529,18 +562,18 @@ end
|
||||
# Binding to ESC-1 caused bootstrap failures with vi keybindings.
|
||||
function warp_report_input
|
||||
set -l escaped_input (warp_escape_json (commandline))
|
||||
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\" } }"
|
||||
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\", \"session_id\": $WARP_SESSION_ID } }"
|
||||
# This prevents fish from rendering typeahead as background output once we've collected it.
|
||||
commandline ''
|
||||
end
|
||||
|
||||
function clear
|
||||
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {}}"
|
||||
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||
end
|
||||
|
||||
function warp_finish_update
|
||||
set -l update_id "$argv[1]"
|
||||
warp_send_json_message "{\"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\"}}"
|
||||
warp_send_json_message "{\"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\", \"session_id\": $WARP_SESSION_ID}}"
|
||||
end
|
||||
|
||||
|
||||
@@ -598,10 +631,45 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
|
||||
function warp_ssh_helper
|
||||
set -l init_shell_zsh (warp_init_shell "zsh")
|
||||
set -l init_shell_bash (warp_init_shell "bash")
|
||||
set -l remote_session_id (command od -An -N8 -tu8 /dev/urandom 2>/dev/null | command tr -d ' \n')
|
||||
if test -z "$remote_session_id"; or test "$remote_session_id" = "0"
|
||||
# If we cannot generate a non-zero random token, run plain SSH instead.
|
||||
command ssh $argv
|
||||
return
|
||||
end
|
||||
# Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters
|
||||
# We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte
|
||||
# and use printf to convert back to plaintext
|
||||
set -l zsh_env_script (printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); printf '"'"'\x1b\x50\x24\x64%s\x9c'"'"' $_msg; unset _hostname _user _msg' | command od -An -v -tx1 | command tr -d ' \n')
|
||||
set -l zsh_env_script (printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID='$remote_session_id'; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); printf '"'"'\x1b\x50\x24\x64%s\x1b\x5c'"'"' $_msg; unset _hostname _user _msg' | command od -An -v -tx1 | command tr -d ' \n')
|
||||
|
||||
# Optionally attach to an existing ControlMaster the user already
|
||||
# runs for this destination instead of creating our own. Resolve
|
||||
# the user's configured ControlPath with `ssh -G` (which expands
|
||||
# tokens like %h/%p/%r/%C into a literal path), then verify the
|
||||
# master is alive with `ssh -O check`. Both probes are local-only
|
||||
# commands. On any failure we fall back to creating a Warp-owned
|
||||
# master, preserving the existing behavior.
|
||||
set -l control_path "$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
||||
set -l control_master_mode "yes"
|
||||
set -l external_control_master "false"
|
||||
if test "$WARP_SSH_REUSE_CONTROL_MASTER" = "1"
|
||||
set -l user_control_path (command ssh -G $argv 2>/dev/null | command sed -n 's/^controlpath //p')
|
||||
# Skip when no ControlPath is configured, and reject resolved
|
||||
# paths containing characters we cannot safely embed in the SSH
|
||||
# hook JSON below (e.g. an unexpanded % token, quotes, or
|
||||
# whitespace); in those cases fall back to a Warp-owned master.
|
||||
if test -n "$user_control_path"
|
||||
and test "$user_control_path" != "none"
|
||||
and string match --quiet --regex '^[A-Za-z0-9._/~@:+,-]+$' -- "$user_control_path"
|
||||
if command ssh -O check -o ControlPath="$user_control_path" $argv >/dev/null 2>&1
|
||||
# A live master exists: multiplex through it and let the
|
||||
# client know Warp does not own it.
|
||||
set control_path "$user_control_path"
|
||||
set control_master_mode "no"
|
||||
set external_control_master "true"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be
|
||||
# escaped with "''" to avoid the local shell from expanding them before they're passed to the remote shell.
|
||||
@@ -609,14 +677,14 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
|
||||
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
|
||||
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
|
||||
# support.
|
||||
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
|
||||
command ssh -o ControlMaster=$control_master_mode -o ControlPath="$control_path" \
|
||||
-t $argv \
|
||||
"
|
||||
export TERM_PROGRAM='WarpTerminal'
|
||||
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION'
|
||||
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
||||
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
|
||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'"
|
||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'"
|
||||
printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'"
|
||||
|
||||
if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
|
||||
@@ -651,14 +719,14 @@ bash)
|
||||
stty raw
|
||||
HISTCONTROL=ignorespace
|
||||
HISTIGNORE=" *"
|
||||
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
|
||||
WARP_SESSION_ID='$remote_session_id'
|
||||
WARP_HONOR_PS1="'$WARP_HONOR_PS1'"
|
||||
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n)
|
||||
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
|
||||
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
||||
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
|
||||
if [[ "'$OS'" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi
|
||||
printf '\''"'\eP$d%s\x9c'"'\'' \""'$_msg'"\"'
|
||||
printf '\''"'\x1b\x50\x24\x64%s\x1b\x5c'"'\'' \""'$_msg'"\"'
|
||||
unset _hostname _user _msg
|
||||
)
|
||||
;;
|
||||
@@ -683,7 +751,7 @@ esac
|
||||
|
||||
function ssh
|
||||
if is_interactive_ssh_session $argv
|
||||
warp_send_json_message '{"hook": "PreInteractiveSSHSession", "value": {}}'
|
||||
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||
|
||||
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]
|
||||
if test $WARP_SHELL_DEBUG_MODE
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
set -g WARP_SESSION_ID (random)
|
||||
set -g WARP_SESSION_ID @@WARP_SESSION_ID@@
|
||||
set _hostname (command -v hostname >/dev/null 2>&1 && command hostname 2>/dev/null || uname -n)
|
||||
set _user (command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER)
|
||||
set -g WARP_IN_MSYS2 (test "$OS" = Windows_NT; and echo true; or echo false)
|
||||
if test "$WARP_IN_MSYS2" = true; set _msg "\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;fish\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;C\a"; else; set _msg (printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"fish\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command od -An -v -tx1 | command tr -d " \n"); end
|
||||
set WARP_USING_WINDOWS_CON_PTY @@USING_CON_PTY_BOOLEAN@@
|
||||
# We send the InitShell hook via OSCs when on Windows and via DCSs otherwise.
|
||||
if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\e\x50\x24\x64%s\x9c' "$_msg"; end
|
||||
if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; end
|
||||
set -e _hostname _user _msg
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
set -g WARP_SESSION_ID (random)
|
||||
set -g WARP_SESSION_ID @@WARP_SESSION_ID@@
|
||||
set _hostname (command -v hostname >/dev/null 2>&1 && command hostname 2>/dev/null || uname -n)
|
||||
set _user (command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER)
|
||||
set -g WARP_IN_MSYS2 (test "$OS" = Windows_NT; and echo true; or echo false)
|
||||
if test "$WARP_IN_MSYS2" = true; set _msg "\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;fish\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;B;is_subshell;true\a\e]9278;k;C\a"; else; set _msg (printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"user\": \"%s\", \"hostname\": \"%s\", \"shell\": \"fish\", \"is_subshell\": true, \"wsl_name\": \"$WSL_DISTRO_NAME\"}}" "$_user" "$_hostname" | command od -An -v -tx1 | command tr -d " \n"); end
|
||||
set WARP_USING_WINDOWS_CON_PTY @@USING_CON_PTY_BOOLEAN@@
|
||||
if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\e\x50\x24\x64%s\x9c' "$_msg"; end
|
||||
if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; end
|
||||
set -e _hostname _user _msg
|
||||
|
||||
@@ -1 +1 @@
|
||||
echo -e '\n# Auto-Warpify\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'"% }}\x9c'\'' ' >> %
|
||||
echo -e '\n# Auto-Warpify\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
|
||||
|
||||
@@ -142,9 +142,27 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
(Get-Variable | Select-Object -ExpandProperty Name) -join ' '
|
||||
$aliasesRaw = Get-Command -CommandType Alias | Select-Object -ExpandProperty DisplayName
|
||||
$aliases = $aliasesRaw -join [Environment]::NewLine
|
||||
$functionNamesRaw = Get-Command -CommandType Function | Where-Object { -not $_.Name.StartsWith('Warp') } | Select-Object -ExpandProperty Name
|
||||
# Only query modules that ship with PowerShell itself. This keeps bootstrap fast by
|
||||
# avoiding Windows system modules and third-party modules from the Gallery. The rest are
|
||||
# loaded asynchronously later.
|
||||
$corePsModules = @(
|
||||
'Microsoft.PowerShell.*', # All built-in PS modules (cross-platform)
|
||||
'Microsoft.WSMan.*', # WS-Management (Windows PS)
|
||||
'CimCmdlets', # CIM/WMI (Windows)
|
||||
'PackageManagement', # Package management
|
||||
'PowerShellGet', # Package get
|
||||
'PSReadLine', # Line editor bundled with PS
|
||||
'ThreadJob', # Thread jobs (PS 7)
|
||||
'PSDiagnostics', # PS diagnostics
|
||||
'PSDesiredStateConfiguration', # DSC (Windows PS)
|
||||
'PSWorkflow', # Legacy workflow (Windows PS 5)
|
||||
'PSWorkflowUtility' # Legacy workflow utility (Windows PS 5)
|
||||
)
|
||||
$functionNamesRaw = Get-Command -CommandType Function -Module $corePsModules |
|
||||
Where-Object { -not $_.Name.StartsWith('Warp') } |
|
||||
Select-Object -ExpandProperty Name
|
||||
$functionNames = $functionNamesRaw -join [Environment]::NewLine
|
||||
$builtinsRaw = Get-Command -CommandType Cmdlet | Select-Object -ExpandProperty Name
|
||||
$builtinsRaw = Get-Command -CommandType Cmdlet -Module $corePsModules | Select-Object -ExpandProperty Name
|
||||
$builtins = $builtinsRaw -join [Environment]::NewLine
|
||||
$shellVersion = $PSVersionTable.PSVersion.ToString()
|
||||
# PowerShell wasn't cross-platform until version 6. Anything before that is definitely on Windows.
|
||||
@@ -200,6 +218,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
$bootstrappedMsg = @{
|
||||
hook = 'Bootstrapped'
|
||||
value = @{
|
||||
session_id = $global:_warpSessionId
|
||||
histfile = $(Get-PSReadLineOption).HistorySavePath
|
||||
shell = 'pwsh'
|
||||
home_dir = "$HOME"
|
||||
@@ -230,6 +249,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
$preexecMsg = @{
|
||||
hook = 'Preexec'
|
||||
value = @{
|
||||
session_id = $global:_warpSessionId
|
||||
command = $command
|
||||
}
|
||||
}
|
||||
@@ -243,7 +263,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
}
|
||||
|
||||
# Clean up any completed warp jobs so they do not show up on the user's 'get-job'
|
||||
# comands
|
||||
# commands
|
||||
Warp-Clean-CompletedThread
|
||||
|
||||
# Remove any instance of the 'Warp-Run-GeneratorCommand' call from the user's history
|
||||
@@ -254,6 +274,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
$updateMsg = @{
|
||||
hook = 'FinishUpdate'
|
||||
value = @{
|
||||
session_id = $global:_warpSessionId
|
||||
update_id = $updateId
|
||||
}
|
||||
}
|
||||
@@ -288,7 +309,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
# 2. We need to make sure that we are calling the Application git, and not
|
||||
# an alias or cmdlet named Git
|
||||
#
|
||||
# NOTE: Inlining this call in the function has a weird side effect of outputing
|
||||
# NOTE: Inlining this call in the function has a weird side effect of outputting
|
||||
# an escape sequence '^[i'. Since it made it more convenient to have a wrapper
|
||||
# function anyway, I have not investigated this, but in case someone is working
|
||||
# on this in the future, beware attempting to inline this function.
|
||||
@@ -344,6 +365,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
$inputBufferMsg = @{
|
||||
hook = 'InputBuffer'
|
||||
value = @{
|
||||
session_id = $global:_warpSessionId
|
||||
buffer = $inputBuffer
|
||||
}
|
||||
}
|
||||
@@ -386,7 +408,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
Warp-Disable-PSPrediction
|
||||
}
|
||||
|
||||
# Force use of the Inline PredictionViewStyle. The ListView style can occassionally cause some
|
||||
# Force use of the Inline PredictionViewStyle. The ListView style can occasionally cause some
|
||||
# flickering when using Warp and it doesn't matter what the value of this setting is because
|
||||
# Warp has its own input editor.
|
||||
function Warp-Disable-PSPrediction {
|
||||
@@ -434,11 +456,13 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
$HOST.UI.RawUI.WindowTitle = $newTitle
|
||||
|
||||
$blockId = $script:nextBlockId++
|
||||
$nextBlockId = "precmd-${global:_warpSessionId}-$blockId"
|
||||
$commandFinishedMsg = @{
|
||||
hook = 'CommandFinished'
|
||||
value = @{
|
||||
session_id = $global:_warpSessionId
|
||||
exit_code = $exitCode
|
||||
next_block_id = "precmd-${global:_warpSessionId}-$blockId"
|
||||
next_block_id = $nextBlockId
|
||||
}
|
||||
}
|
||||
Warp-Send-JsonMessage $commandFinishedMsg
|
||||
@@ -457,6 +481,8 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
$precmdMsg = @{
|
||||
hook = 'Precmd'
|
||||
value = @{
|
||||
exit_code = $exitCode
|
||||
next_block_id = $nextBlockId
|
||||
pwd = ''
|
||||
ps1 = ''
|
||||
git_head = ''
|
||||
@@ -493,39 +519,52 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
$kubeConfig = $env:KUBECONFIG
|
||||
}
|
||||
|
||||
# Compute Node.js version if node is available and we're in a Node project within a Git repo.
|
||||
$hasNodeCommand = Get-Command -CommandType Application node 2>$null
|
||||
# Compute the Node.js version only when the Node.js Version chip is enabled
|
||||
# (WARP_PROMPT_NODE_VERSION_ENABLED is '0' when the chip is not shown; default
|
||||
# enabled when unset) and node is available. Cache the result keyed on the
|
||||
# current location + PATH so we only spawn node when the directory or PATH
|
||||
# changes (PATH changes on version-manager switches like `nvm use`).
|
||||
$nodeChipEnabled = "$env:WARP_PROMPT_NODE_VERSION_ENABLED" -ne '0'
|
||||
$hasNodeCommand = if ($nodeChipEnabled) { Get-Command -CommandType Application node 2>$null } else { $null }
|
||||
if ($hasNodeCommand) {
|
||||
try {
|
||||
# Walk up from the current directory to find a package.json
|
||||
$dir = Get-Item -LiteralPath (Get-Location).Path
|
||||
$foundPackageJson = $false
|
||||
$packageJsonDir = $null
|
||||
while ($null -ne $dir) {
|
||||
$candidate = Join-Path $dir.FullName 'package.json'
|
||||
if (Test-Path -LiteralPath $candidate) {
|
||||
$foundPackageJson = $true
|
||||
$packageJsonDir = $dir.FullName
|
||||
break
|
||||
}
|
||||
$dir = $dir.Parent
|
||||
}
|
||||
|
||||
if ($foundPackageJson) {
|
||||
# Verify package.json resides within a Git repository by walking up to find a .git directory
|
||||
$probe = Get-Item -LiteralPath $packageJsonDir
|
||||
$inGitRepo = $false
|
||||
while ($null -ne $probe) {
|
||||
if (Test-Path -LiteralPath (Join-Path $probe.FullName '.git')) {
|
||||
$inGitRepo = $true
|
||||
$nodeCacheKey = "$((Get-Location).Path)|$env:PATH"
|
||||
if ($nodeCacheKey -eq $script:warpNodeVersionCacheKey) {
|
||||
$nodeVersion = $script:warpNodeVersionCacheValue
|
||||
} else {
|
||||
# Walk up from the current directory to find a package.json
|
||||
$dir = Get-Item -LiteralPath (Get-Location).Path
|
||||
$foundPackageJson = $false
|
||||
$packageJsonDir = $null
|
||||
while ($null -ne $dir) {
|
||||
$candidate = Join-Path $dir.FullName 'package.json'
|
||||
if (Test-Path -LiteralPath $candidate) {
|
||||
$foundPackageJson = $true
|
||||
$packageJsonDir = $dir.FullName
|
||||
break
|
||||
}
|
||||
$probe = $probe.Parent
|
||||
$dir = $dir.Parent
|
||||
}
|
||||
|
||||
if ($inGitRepo) {
|
||||
$nodeVersion = Warp-TryGet-NodeVersion
|
||||
if ($foundPackageJson) {
|
||||
# Verify package.json resides within a Git repository by walking up to find a .git directory
|
||||
$probe = Get-Item -LiteralPath $packageJsonDir
|
||||
$inGitRepo = $false
|
||||
while ($null -ne $probe) {
|
||||
if (Test-Path -LiteralPath (Join-Path $probe.FullName '.git')) {
|
||||
$inGitRepo = $true
|
||||
break
|
||||
}
|
||||
$probe = $probe.Parent
|
||||
}
|
||||
|
||||
if ($inGitRepo) {
|
||||
$nodeVersion = Warp-TryGet-NodeVersion
|
||||
}
|
||||
}
|
||||
|
||||
$script:warpNodeVersionCacheKey = $nodeCacheKey
|
||||
$script:warpNodeVersionCacheValue = $nodeVersion
|
||||
}
|
||||
} catch {
|
||||
# Log at verbose level so the catch block is not empty and diagnostics are available when needed.
|
||||
@@ -558,6 +597,8 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
$precmdMsg = @{
|
||||
hook = 'Precmd'
|
||||
value = @{
|
||||
exit_code = $exitCode
|
||||
next_block_id = $nextBlockId
|
||||
pwd = (Get-Location).Path
|
||||
# TODO(PLAT-687) - honor the PS1
|
||||
ps1 = ''
|
||||
@@ -896,7 +937,9 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
function Clear-Host() {
|
||||
$inputBufferMsg = @{
|
||||
hook = 'Clear'
|
||||
value = @{}
|
||||
value = @{
|
||||
session_id = $global:_warpSessionId
|
||||
}
|
||||
}
|
||||
Warp-Send-JsonMessage $inputBufferMsg
|
||||
}
|
||||
@@ -904,7 +947,9 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
|
||||
function clear() {
|
||||
$inputBufferMsg = @{
|
||||
hook = 'Clear'
|
||||
value = @{}
|
||||
value = @{
|
||||
session_id = $global:_warpSessionId
|
||||
}
|
||||
}
|
||||
Warp-Send-JsonMessage $inputBufferMsg
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ function prompt {
|
||||
# Reset the prompt back to the default to avoid infinite loops if sourcing the bootstrap script has an error.
|
||||
$function:global:prompt = $global:_warpOriginalPrompt
|
||||
$username = [Environment]::UserName
|
||||
$epoch = [int](New-TimeSpan -Start ([DateTime]::new(1970, 1, 1, 0, 0, 0, 0)) -End ([DateTime]::UtcNow)).TotalSeconds
|
||||
$random = Get-Random -Maximum 32768
|
||||
$global:_warpSessionId = [int64]"$epoch$random"
|
||||
$global:_warpSessionId = [uint64]@@WARP_SESSION_ID@@
|
||||
$msg = ConvertTo-Json -Compress -InputObject @{ hook = 'InitShell'; value = @{ session_id = $_warpSessionId; shell = 'pwsh'; user = $username; hostname = [System.Net.Dns]::GetHostName() } }
|
||||
$encodedMsg = [BitConverter]::ToString([System.Text.Encoding]::UTF8.GetBytes($msg)).Replace('-', '')
|
||||
$oscStart = "$([char]0x1b)]9278;"
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
# Thankfully, we don't need curly braces around the first expression, so we can put the fish check
|
||||
# first and it early exits. This runs correctly in sh, bash, zsh, and fish.
|
||||
# Replace `HOOK_NAME` with the appropriate hook name.
|
||||
[ -z $WARP_BOOTSTRAPPED ] && printf "\\e]9278;f;{\"hook\": \"HOOK_NAME\", \"value\": { \"shell\": \"%s\", \"uname\": \"%s\" }}\\a" $([ $FISH_VERSION ] && echo "fish" || { echo $0 | command -p grep -q zsh && echo "zsh"; } || { echo $0 | command -p grep -q bash && echo "bash"; } || echo "unknown") $(uname)
|
||||
[ -z $WARP_BOOTSTRAPPED ] && printf "\\e]9278;f;{\"hook\": \"HOOK_NAME\", \"value\": { \"shell\": \"%s\", \"uname\": \"%s\", \"session_id\": @@WARP_SESSION_ID@@ }}\\a" $([ $FISH_VERSION ] && echo "fish" || { echo $0 | command -p grep -q zsh && echo "zsh"; } || { echo $0 | command -p grep -q bash && echo "bash"; } || echo "unknown") $(uname)
|
||||
|
||||
@@ -21,8 +21,8 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
# Appended to $DCS_START to signal that the following message is JSON-encoded.
|
||||
DCS_JSON_MARKER="d"
|
||||
|
||||
# Byte used to signal the end of a DCS.
|
||||
DCS_END="$(printf '\x9c')"
|
||||
# Byte sequence used to signal the end of a DCS (7-bit ST: ESC \).
|
||||
DCS_END="$(printf '\x1b\x5c')"
|
||||
|
||||
# OSC used to mark the start of in-band command output.
|
||||
#
|
||||
@@ -168,14 +168,14 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
local -a command
|
||||
command=("${@:2}")
|
||||
# Declare raw_output prior to actually assigning it, because `local` is a command itself, which
|
||||
# inteferes with capturing the exit code via $? (it overwrites $? with the 0, because the
|
||||
# interferes with capturing the exit code via $? (it overwrites $? with the 0, because the
|
||||
# 'local' command always succeeds).
|
||||
local raw_output
|
||||
# Command substitution only captures stdout, so redirect stderr to stdout.
|
||||
# Note that we use `eval` here to actually execute the command, because some shell syntax
|
||||
# that may be used in the command might not be valid in a command substitution (e.g. the
|
||||
# '$(<command>)' syntax).
|
||||
# Also note that zsh variables can contain null charcters, so this doesn't require any special
|
||||
# Also note that zsh variables can contain null characters, so this doesn't require any special
|
||||
# handling.
|
||||
raw_output=$(eval "$command" 2>&1)
|
||||
local exit_code=$?
|
||||
@@ -253,7 +253,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
# invoke any external commands in here.
|
||||
warp_preexec () {
|
||||
local warp_escaped_command="$(warp_escape_json $1)"
|
||||
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$warp_escaped_command\"}}"
|
||||
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$warp_escaped_command\", \"session_id\": $WARP_SESSION_ID}}"
|
||||
warp_maybe_send_reset_grid_osc
|
||||
|
||||
# If this preexec is called for user command, kill ongoing generator command jobs and clean
|
||||
@@ -277,7 +277,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
|
||||
# If the array is not empty, kill the ongoing pids.
|
||||
if [[ ! -z $pids ]]; then
|
||||
# Surpress stderr output; kill writes to stderr if any of the given
|
||||
# Suppress stderr output; kill writes to stderr if any of the given
|
||||
# PIDS are not running (which might rarely be the case due to race
|
||||
# conditions in checking which PIDS to cancel and this kill command.
|
||||
(kill -9 $pids 2>&1) >/dev/null
|
||||
@@ -306,8 +306,9 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
# previously run user command (as opposed to any of the commands executed
|
||||
# in this function below).
|
||||
local exit_code=$?
|
||||
local next_block_id="precmd-$WARP_SESSION_ID-$((block_id++))"
|
||||
|
||||
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$((block_id++))\"}}"
|
||||
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"$next_block_id\", \"session_id\": $WARP_SESSION_ID}}"
|
||||
warp_maybe_send_reset_grid_osc
|
||||
|
||||
# If this is being called for a generator command, short circuit and send an unpopulated
|
||||
@@ -320,6 +321,8 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
|
||||
_WARP_GENERATOR_COMMAND=""
|
||||
warp_send_json_message "{\"hook\": \"Precmd\", \"value\": {
|
||||
\"exit_code\": $exit_code,
|
||||
\"next_block_id\": \"$next_block_id\",
|
||||
\"pwd\": \"\",
|
||||
\"ps1\": \"\",
|
||||
\"git_head\": \"\",
|
||||
@@ -394,37 +397,56 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
escaped_conda_env=$(warp_escape_json $CONDA_DEFAULT_ENV)
|
||||
fi
|
||||
|
||||
# Get Node.js version if node is available and we're in a Node.js project
|
||||
if command -v node > /dev/null 2>&1; then
|
||||
# Get the Node.js version, but only when the Node.js Version chip is enabled.
|
||||
# Warp sets WARP_PROMPT_NODE_VERSION_ENABLED to "0" when the chip is not in the
|
||||
# prompt (defaulting to enabled when unset), so we avoid spawning `node` on
|
||||
# every prompt when the chip is not shown.
|
||||
if [[ "$WARP_PROMPT_NODE_VERSION_ENABLED" != "0" ]] && command -v node > /dev/null 2>&1; then
|
||||
# Check for package.json in current directory and parent directories
|
||||
local current_dir="$PWD"
|
||||
local found_package_json=false
|
||||
local package_json_dir=""
|
||||
while [[ "$current_dir" != "/" ]]; do
|
||||
while [[ -n "$current_dir" ]]; do
|
||||
if [[ -f "$current_dir/package.json" ]]; then
|
||||
found_package_json=true
|
||||
package_json_dir="$current_dir"
|
||||
break
|
||||
fi
|
||||
current_dir=$(dirname "$current_dir")
|
||||
[[ "$current_dir" == "/" ]] && break
|
||||
# Strip the last path segment without spawning `dirname`.
|
||||
current_dir="${current_dir%/*}"
|
||||
[[ -z "$current_dir" ]] && current_dir="/"
|
||||
done
|
||||
|
||||
# Only show node version if package.json is within a git repository
|
||||
if [[ "$found_package_json" = true ]]; then
|
||||
local git_dir="$package_json_dir"
|
||||
local in_git_repo=false
|
||||
while [[ "$git_dir" != "/" ]]; do
|
||||
while [[ -n "$git_dir" ]]; do
|
||||
if [[ -d "$git_dir/.git" ]]; then
|
||||
in_git_repo=true
|
||||
break
|
||||
fi
|
||||
git_dir=$(dirname "$git_dir")
|
||||
[[ "$git_dir" == "/" ]] && break
|
||||
git_dir="${git_dir%/*}"
|
||||
[[ -z "$git_dir" ]] && git_dir="/"
|
||||
done
|
||||
|
||||
if [[ "$in_git_repo" = true ]]; then
|
||||
local node_version=$(node --version 2>/dev/null)
|
||||
if [[ -n "$node_version" ]]; then
|
||||
escaped_node_version=$(warp_escape_json "$node_version")
|
||||
# Cache the resolved version keyed on PWD + PATH so we only spawn
|
||||
# `node --version` when the directory or PATH changes (PATH changes
|
||||
# on `nvm use`). The cache vars are global (no `local`) so they
|
||||
# persist across precmd invocations.
|
||||
local node_cache_key="$PWD:$PATH"
|
||||
if [[ "$node_cache_key" == "$_WARP_NODE_VERSION_CACHE_KEY" ]]; then
|
||||
escaped_node_version="$_WARP_NODE_VERSION_CACHE_VALUE"
|
||||
else
|
||||
local node_version=$(node --version 2>/dev/null)
|
||||
if [[ -n "$node_version" ]]; then
|
||||
escaped_node_version=$(warp_escape_json "$node_version")
|
||||
fi
|
||||
_WARP_NODE_VERSION_CACHE_KEY="$node_cache_key"
|
||||
_WARP_NODE_VERSION_CACHE_VALUE="$escaped_node_version"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -461,6 +483,8 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
fi
|
||||
|
||||
local escaped_json="{\"hook\": \"Precmd\", \"value\": {
|
||||
\"exit_code\": $exit_code,
|
||||
\"next_block_id\": \"$next_block_id\",
|
||||
\"pwd\": \"$escaped_pwd\",
|
||||
\"ps1\": \"\",
|
||||
\"honor_ps1\": $honor_ps1,
|
||||
@@ -622,19 +646,19 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
|
||||
function warp_report_input {
|
||||
local escaped_input="$(warp_escape_json "$BUFFER")"
|
||||
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\" } }"
|
||||
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\", \"session_id\": $WARP_SESSION_ID } }"
|
||||
# This prevents zsh from printing typeahead as background output after we've fetched it.
|
||||
BUFFER=""
|
||||
}
|
||||
zle -N warp_report_input
|
||||
|
||||
function clear() {
|
||||
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {}}"
|
||||
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||
}
|
||||
|
||||
function warp_finish_update {
|
||||
local update_id="$1"
|
||||
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\"} }"
|
||||
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\", \"session_id\": $WARP_SESSION_ID} }"
|
||||
}
|
||||
|
||||
# Check if the warp apt source file has been renamed to `warpdotdev.list.distUpgrade` due to an ubuntu version update.
|
||||
@@ -661,6 +685,21 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
fi
|
||||
}
|
||||
|
||||
# Strips prompt constructs that zsh counts as visible "glitch" columns even
|
||||
# when they appear inside a %{...%} zero-width region, returning the result
|
||||
# in $REPLY. The explicit-width form %n{ is rewritten to %{ (preserving the
|
||||
# brace pairing), and the %G, %nG, and %-nG forms are removed entirely.
|
||||
# Neither change affects the rendered prompt bytes, only zsh's internal
|
||||
# width accounting. Literal %% escapes are matched first so that they cannot
|
||||
# form false positives (e.g. %%1{ renders as literal text and must be left
|
||||
# alone).
|
||||
function warp_strip_glitch_width_constructs() {
|
||||
setopt localoptions extendedglob
|
||||
local match mbegin mend
|
||||
REPLY=${1:-}
|
||||
REPLY=${REPLY//(#b)(%%|%<->\{|%(-|)(<->|)G)/${${match[1]:#%(-|)(<->|)G}/(#s)%<->\{(#e)/%\{}}
|
||||
}
|
||||
|
||||
# Check whether the prompt-related variables have OSC prompt marker sequences,
|
||||
# and if not, wrap them with the appropriate markers so that we can direct the
|
||||
# prompt bytes to the appropriate grids.
|
||||
@@ -712,7 +751,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
# markers. If they exist, we remove the first occurrence of the prefix
|
||||
# and the last occurrence of the suffix, which should be the ones that
|
||||
# Warp has added, to avoid duplicating the prefix and suffix. Shell
|
||||
# parameter expansion is used to remove the first and last occurences.
|
||||
# parameter expansion is used to remove the first and last occurrences.
|
||||
# Specifically note that virtualenvs can add content to the prompt, so we need to
|
||||
# remove the markers before re-adding them.
|
||||
# https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
|
||||
@@ -742,7 +781,21 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
PROMPT=$preceding_suffix$following_suffix
|
||||
fi
|
||||
|
||||
ORIGINAL_PROMPT=$PROMPT
|
||||
# If the prompt we extracted is exactly the glitch-stripped value that we
|
||||
# installed on a previous refresh, keep the existing ORIGINAL_PROMPT: it
|
||||
# holds the pristine value, whose width annotations are still needed if
|
||||
# we later switch to honoring the PS1.
|
||||
if [[ "$PROMPT" != "${WARP_STRIPPED_ORIGINAL_PROMPT:-}" ]]; then
|
||||
if [[ -n "${WARP_STRIPPED_ORIGINAL_PROMPT:-}" && "$PROMPT" == *"$WARP_STRIPPED_ORIGINAL_PROMPT"* ]]; then
|
||||
# Another hook added content around the stripped prompt that we
|
||||
# installed (e.g. a virtualenv prefix). Rehydrate the stripped
|
||||
# portion back to its pristine value before saving, so that the
|
||||
# width annotations survive alongside the added content.
|
||||
ORIGINAL_PROMPT=${PROMPT//$WARP_STRIPPED_ORIGINAL_PROMPT/$ORIGINAL_PROMPT}
|
||||
else
|
||||
ORIGINAL_PROMPT=$PROMPT
|
||||
fi
|
||||
fi
|
||||
PROMPT="$prompt_prefix$PROMPT$suffix"
|
||||
fi
|
||||
|
||||
@@ -762,14 +815,26 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
# If we are using the Warp prompt, we pass a "hidden left prompt" to the prompt
|
||||
# preview grid (the hidden prompt grid) with cursor markers surrounding the entire prompt.
|
||||
if [[ "$WARP_HONOR_PS1" != "1" ]]; then
|
||||
if [[ "$PROMPT" != "%{$prompt_prefix$ORIGINAL_PROMPT$suffix%}" ]]; then
|
||||
# Even though the entire prompt is surrounded by cursor markers below,
|
||||
# zsh still counts explicit-width constructs (%n{...%} and %G) within it
|
||||
# as visible "glitch" columns. Since the prompt is routed to the hidden
|
||||
# prompt grid and occupies zero columns of the combined prompt/command
|
||||
# grid, any nonzero counted width desyncs zle's internal cursor position
|
||||
# from the physical one, which corrupts partial redraws of the command
|
||||
# (e.g. when zsh-syntax-highlighting recolors individual tokens). Strip
|
||||
# those constructs before wrapping; this only changes zsh's width
|
||||
# accounting, never the rendered prompt bytes.
|
||||
local REPLY
|
||||
warp_strip_glitch_width_constructs "$ORIGINAL_PROMPT"
|
||||
WARP_STRIPPED_ORIGINAL_PROMPT=$REPLY
|
||||
if [[ "$PROMPT" != "%{$prompt_prefix$WARP_STRIPPED_ORIGINAL_PROMPT$suffix%}" ]]; then
|
||||
# We purposefully surround this entire prompt with cursor markers to prevent
|
||||
# the shell from moving its internal state of the cursor position, for purposes
|
||||
# of printing the command with the Warp prompt.
|
||||
# Note that the Warp prompt is always ABOVE the combined grid in finished blocks
|
||||
# (same line prompt only affects the input editor with Warp prompt, not
|
||||
# finished blocks).
|
||||
PROMPT="%{$prompt_prefix$ORIGINAL_PROMPT$suffix%}"
|
||||
PROMPT="%{$prompt_prefix$WARP_STRIPPED_ORIGINAL_PROMPT$suffix%}"
|
||||
fi
|
||||
# Otherwise, if we are using the PS1, we use the normal prompt markers.
|
||||
else
|
||||
@@ -780,7 +845,9 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${RPROMPT:-}" != "%{"*"%}" ]]; then
|
||||
# Do not synthesize an empty right prompt. Even without visible content, zsh reserves
|
||||
# right-prompt layout space and may corrupt wrapped command redraws in the command grid.
|
||||
if [[ -n "${RPROMPT:-}" && "${RPROMPT:-}" != "%{"*"%}" ]]; then
|
||||
RPROMPT="%{${RPROMPT:-}%}"
|
||||
fi
|
||||
|
||||
@@ -864,10 +931,50 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
}
|
||||
|
||||
function warp_ssh_helper() {
|
||||
local remote_session_id=$(command -p od -An -N8 -tu8 /dev/urandom 2>/dev/null | command -p tr -d ' \n')
|
||||
if [[ -z "$remote_session_id" || "$remote_session_id" == "0" ]]; then
|
||||
# If we cannot generate a non-zero random token, run plain SSH instead.
|
||||
command ssh "${@:1}"
|
||||
return
|
||||
fi
|
||||
# Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters
|
||||
# We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte
|
||||
# and use printf to convert back to plaintext
|
||||
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
|
||||
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID='$remote_session_id'; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
|
||||
|
||||
# Optionally attach to an existing ControlMaster the user already
|
||||
# runs for this destination instead of creating our own. Resolve
|
||||
# the user's configured ControlPath with `ssh -G` (which expands
|
||||
# tokens like %h/%p/%r/%C into a literal path), then verify the
|
||||
# master is alive with `ssh -O check`. Both probes are local-only
|
||||
# commands. On any failure we fall back to creating a Warp-owned
|
||||
# master, preserving the existing behavior.
|
||||
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
||||
local control_master_mode="yes"
|
||||
local external_control_master="false"
|
||||
if [[ "$WARP_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
|
||||
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
|
||||
case "$user_control_path" in
|
||||
"" | none)
|
||||
# No ControlPath configured for this destination.
|
||||
;;
|
||||
*[![:alnum:]._/~@:+,-]*)
|
||||
# The resolved path contains characters we cannot safely
|
||||
# embed in the SSH hook JSON below (e.g. an unexpanded %
|
||||
# token, quotes, or whitespace); fall back to a
|
||||
# Warp-owned master.
|
||||
;;
|
||||
*)
|
||||
if command ssh -O check -o ControlPath="$user_control_path" "${@:1}" >/dev/null 2>&1; then
|
||||
# A live master exists: multiplex through it and let
|
||||
# the client know Warp does not own it.
|
||||
control_path="$user_control_path"
|
||||
control_master_mode="no"
|
||||
external_control_master="true"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Keep remote commands up-to-date with shell.rs & bash.sh.
|
||||
# Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be
|
||||
@@ -876,7 +983,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
||||
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
|
||||
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
|
||||
# support.
|
||||
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
|
||||
command ssh -o ControlMaster=$control_master_mode -o ControlPath="$control_path" \
|
||||
-t "${@:1}" \
|
||||
"
|
||||
export TERM_PROGRAM='WarpTerminal'
|
||||
@@ -887,7 +994,7 @@ export WARP_IS_SSH='1'
|
||||
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION'
|
||||
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
||||
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
|
||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
||||
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
|
||||
|
||||
if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
|
||||
@@ -922,7 +1029,7 @@ case "'${SHELL##*/}'" in
|
||||
command -p stty raw
|
||||
HISTCONTROL=ignorespace
|
||||
HISTIGNORE=" *"
|
||||
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
|
||||
WARP_SESSION_ID='$remote_session_id'
|
||||
WARP_HONOR_PS1="'$WARP_HONOR_PS1'"
|
||||
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
|
||||
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
|
||||
@@ -955,7 +1062,7 @@ esac
|
||||
|
||||
function ssh() {
|
||||
if is_interactive_ssh_session "$@"; then
|
||||
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {}}"
|
||||
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||
|
||||
# If the SSH wrapper is not enabled for this session, don't use it.
|
||||
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then
|
||||
@@ -1169,6 +1276,12 @@ esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# Restore the built-in bracketed-paste widget. This works around a buggy interaction we observed
|
||||
# with the bracketed-paste-magic plugin (included in oh-my-zsh by default), zsh's "allexport"
|
||||
# option (set -a), and Warp's bootstrapping code.
|
||||
# https://github.com/warpdotdev/warp/issues/11520
|
||||
zle -A .bracketed-paste bracketed-paste
|
||||
|
||||
precmd_functions+=(warp_precmd warp_update_prompt_vars)
|
||||
preexec_functions+=(warp_preexec)
|
||||
|
||||
@@ -1395,7 +1508,8 @@ esac
|
||||
|
||||
local escaped_editor="$(warp_escape_json "$EDITOR")"
|
||||
local escaped_shell_path="$(warp_escape_json "${commands[zsh]}")"
|
||||
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"shell\": \"zsh\", \"home_dir\": \"$HOME\", \"path\": \"$escaped_path\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$ZSH_VERSION\", \"shell_options\": \"$shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"shell_plugins\": \"$escaped_shell_plugins\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"${WSL_DISTRO_NAME:-}\", \"shell_path\": \"$escaped_shell_path\"}}"
|
||||
local escaped_cdpath="$(warp_escape_json "$CDPATH")"
|
||||
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"home_dir\": \"$HOME\", \"path\": \"$escaped_path\", \"cdpath\": \"$escaped_cdpath\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$ZSH_VERSION\", \"shell_options\": \"$shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"shell_plugins\": \"$escaped_shell_plugins\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"${WSL_DISTRO_NAME:-}\", \"shell_path\": \"$escaped_shell_path\"}}"
|
||||
warp_send_json_message "$escaped_json"
|
||||
}
|
||||
warp_bootstrapped
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
# command -p resolves the given command with the system default PATH, ensuring the shell
|
||||
# can find them even if the user has a clobbered PATH value.
|
||||
unsetopt ZLE
|
||||
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
|
||||
WARP_SESSION_ID=@@WARP_SESSION_ID@@
|
||||
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
|
||||
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
|
||||
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
|
||||
# We send the InitShell hook via OSCs when on Windows and via DCSs otherwise.
|
||||
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi
|
||||
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; fi
|
||||
unset _hostname _user _msg
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
setopt hist_ignore_space
|
||||
unsetopt ZLE
|
||||
WARP_IS_SUBSHELL=1
|
||||
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
|
||||
WARP_SESSION_ID=@@WARP_SESSION_ID@@
|
||||
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
|
||||
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
|
||||
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\", \"is_subshell\": true, \"wsl_name\": \"$WSL_DISTRO_NAME\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
|
||||
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi
|
||||
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; fi
|
||||
unset _hostname _user _msg
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
brew install tmux && tmux -Lwarp -CC && exit
|
||||
@@ -1,29 +0,0 @@
|
||||
INSTALL_TMUX='set -e
|
||||
|
||||
_on_error() {
|
||||
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\''\033\120\044\144%s\234'\'' "$_msg"
|
||||
rm -rf "$HOME/.warp/tmux"
|
||||
}
|
||||
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
|
||||
|
||||
mkdir -p $HOME/.warp/tmux
|
||||
pushd "$HOME/.warp/tmux"
|
||||
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64) ARCH_NAME=amd64 ;;
|
||||
amd64) ARCH_NAME=amd64 ;;
|
||||
aarch64) ARCH_NAME=arm64 ;;
|
||||
*) echo "Unsupported architecture $ARCH"; exit 1 ;;
|
||||
esac
|
||||
|
||||
URL="https://github.com/warpdotdev/portable-tmux/releases/download/tmux-3.5a/tmux-${ARCH_NAME}.tar.gz"
|
||||
|
||||
(curl -o tmux.tar.gz -L $URL || wget -O tmux.tar.gz $URL) && tar -xf tmux.tar.gz
|
||||
|
||||
INSTALL_PATH="$HOME/.warp/tmux/local"
|
||||
echo "TERM=tmux-256color LD_LIBRARY_PATH=\"$INSTALL_PATH/lib\" TERMINFO=\"$INSTALL_PATH/share/terminfo/\" \"$INSTALL_PATH/bin/tmux\" \"\$@\";" > ~/.warp/tmux/execute_tmux.sh
|
||||
chmod +x ~/.warp/tmux/execute_tmux.sh;'
|
||||
|
||||
bash <<< "$INSTALL_TMUX" && ~/.warp/tmux/execute_tmux.sh -Lwarp -CC && exit
|
||||
@@ -1,13 +0,0 @@
|
||||
INSTALL_TMUX='set -e
|
||||
|
||||
_on_error() {
|
||||
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\''\033\120\044\144%s\234'\'' "$_msg"
|
||||
rm -rf "$HOME/.warp/tmux"
|
||||
}
|
||||
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
|
||||
|
||||
sudo apt update -y
|
||||
sudo apt install -y tmux'
|
||||
|
||||
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
|
||||
@@ -1,13 +0,0 @@
|
||||
INSTALL_TMUX='set -e
|
||||
|
||||
_on_error() {
|
||||
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\''\033\120\044\144%s\234'\'' "$_msg"
|
||||
rm -rf "$HOME/.warp/tmux"
|
||||
}
|
||||
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
|
||||
|
||||
sudo dnf update -y
|
||||
sudo dnf install -y tmux'
|
||||
|
||||
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
|
||||
@@ -1,13 +0,0 @@
|
||||
INSTALL_TMUX='set -e
|
||||
|
||||
_on_error() {
|
||||
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\''\033\120\044\144%s\234'\'' "$_msg"
|
||||
rm -rf "$HOME/.warp/tmux"
|
||||
}
|
||||
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
|
||||
|
||||
sudo pacman -Syu --noconfirm
|
||||
sudo pacman -S --noconfirm tmux'
|
||||
|
||||
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
|
||||
@@ -1,13 +0,0 @@
|
||||
INSTALL_TMUX='set -e
|
||||
|
||||
_on_error() {
|
||||
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\''\033\120\044\144%s\234'\'' "$_msg"
|
||||
rm -rf "$HOME/.warp/tmux"
|
||||
}
|
||||
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
|
||||
|
||||
sudo yum update -y
|
||||
sudo yum install -y tmux'
|
||||
|
||||
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
|
||||
@@ -1,13 +0,0 @@
|
||||
INSTALL_TMUX='set -e
|
||||
|
||||
_on_error() {
|
||||
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\''\033\120\044\144%s\234'\'' "$_msg"
|
||||
rm -rf "$HOME/.warp/tmux"
|
||||
}
|
||||
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
|
||||
|
||||
sudo zypper refresh
|
||||
sudo zypper install -y tmux'
|
||||
|
||||
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
|
||||
@@ -1,68 +0,0 @@
|
||||
_find() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
_log() {
|
||||
_msg=$(printf "{\"hook\": \"$1\", \"value\": $2}" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\033\120\044\144%s\234' "$_msg"
|
||||
}
|
||||
|
||||
_err() {
|
||||
_log RemoteWarpificationIsUnavailable "$1"
|
||||
}
|
||||
|
||||
_system_details() {
|
||||
OS=$(uname)
|
||||
if [ "$OS" = "Darwin" ]; then
|
||||
if _find brew; then
|
||||
PKG="homebrew"
|
||||
fi
|
||||
elif [ "$OS" = "Linux" ]; then
|
||||
if _find pacman; then
|
||||
PKG="pacman"
|
||||
elif _find zypper; then
|
||||
PKG="zypper"
|
||||
elif _find dnf; then
|
||||
PKG="dnf"
|
||||
elif _find yum && _find yumdownloader; then
|
||||
PKG="yum"
|
||||
elif _find apt; then
|
||||
PKG="apt"
|
||||
fi
|
||||
fi
|
||||
RA="no_root_access"
|
||||
if command -v sudo >/dev/null && { sudo -vn && sudo -ln; } 2>&1 | grep -E 'may run|a password' > /dev/null; then RA="can_run_sudo"
|
||||
elif [ "$(id -u)" -eq 0 ] && [ "$(whoami)" = "root" ]; then RA="is_root"
|
||||
fi
|
||||
|
||||
WH=$( [ -w ~ ] && echo true || echo false )
|
||||
|
||||
printf '%s' "{\"os\": \"$OS\", \"pkg\": \"$PKG\", \"shell\": \"$(basename $SHELL)\", \"root_access\": \"$RA\", \"writable_home\": $WH}"
|
||||
}
|
||||
|
||||
# _check_tmux is used in tmux install script post install!
|
||||
_check_tmux() {
|
||||
if _find $HOME/.warp/tmux/execute_tmux.sh; then
|
||||
_log SshTmuxInstaller "\"warp\""
|
||||
TMUX="$HOME/.warp/tmux/execute_tmux.sh"
|
||||
elif _find tmux; then
|
||||
TMUX="tmux"
|
||||
_log SshTmuxInstaller "\"user\""
|
||||
fi
|
||||
|
||||
if [ $TMUX ]; then
|
||||
VER=$(command $TMUX -V 2>/dev/null | awk '{print $2}')
|
||||
if [ -z "$VER" ]; then
|
||||
_err "\"TmuxFailed\""
|
||||
elif [ "$(printf '%s\n' "$VER" "2.9" | sort -V | tail -n1)" = "2.9" ]; then
|
||||
_err "{\"UnsupportedTmuxVersion\": $(_system_details)}"
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
_err "{\"TmuxNotInstalled\": $(_system_details)}"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
_check_tmux && command $TMUX -Lwarp -CC && exit
|
||||
@@ -1,49 +0,0 @@
|
||||
_find() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
_log() {
|
||||
_msg=$(printf "{\"hook\": \"$1\", \"value\": $2}" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\033\120\044\144%s\234' "$_msg"
|
||||
}
|
||||
|
||||
_err() {
|
||||
_log RemoteWarpificationIsUnavailable "$1"
|
||||
}
|
||||
|
||||
_sd() {
|
||||
if _find brew; then
|
||||
PKG="homebrew"
|
||||
fi
|
||||
|
||||
WH=$( [ -w ~ ] && echo true || echo false )
|
||||
|
||||
printf '{"os": "Darwin", "pkg": "%s", "shell": "%s", "root_access": "no_root_access", "writable_home": %s}' "$PKG" "$(basename $SHELL)" $WH
|
||||
}
|
||||
|
||||
# _check_tmux is used in tmux install script post install!
|
||||
_check_tmux() {
|
||||
TMUX="$HOME/.warp/tmux/execute_tmux.sh"
|
||||
if _find "$TMUX"; then
|
||||
_log SshTmuxInstaller "\"warp\""
|
||||
elif _find tmux; then
|
||||
TMUX="tmux"
|
||||
_log SshTmuxInstaller "\"user\""
|
||||
fi
|
||||
|
||||
if [ $TMUX ]; then
|
||||
VER=$($TMUX -V 2>/dev/null | awk '{print $2}')
|
||||
if [ -z "$VER" ]; then
|
||||
_err "\"TmuxFailed\""
|
||||
elif [ "$(printf '%s\n' "$VER" "2.9" | sort -V | tail -n1)" = "2.9" ]; then
|
||||
_err "{\"UnsupportedTmuxVersion\": $(_sd)}"
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
_err "{\"TmuxNotInstalled\": $(_sd)}"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
_check_tmux && $TMUX -Lwarp -CC && exit
|
||||
@@ -1,6 +0,0 @@
|
||||
brew install tmux
|
||||
|
||||
if test $status -eq 0
|
||||
tmux -Lwarp -CC
|
||||
exit
|
||||
end
|
||||
@@ -1,29 +0,0 @@
|
||||
set INSTALL_TMUX 'set -e
|
||||
|
||||
_on_error() {
|
||||
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
|
||||
printf '\''\033\120\044\144%s\234'\'' "$_msg"
|
||||
rm -rf "$HOME/.warp/tmux"
|
||||
}
|
||||
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
|
||||
|
||||
mkdir -p $HOME/.warp/tmux
|
||||
pushd "$HOME/.warp/tmux"
|
||||
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64) ARCH_NAME=amd64 ;;
|
||||
amd64) ARCH_NAME=amd64 ;;
|
||||
aarch64) ARCH_NAME=arm64 ;;
|
||||
*) echo "Unsupported architecture $ARCH"; exit 1 ;;
|
||||
esac
|
||||
|
||||
URL="https://github.com/warpdotdev/portable-tmux/releases/download/tmux-3.5a/tmux-${ARCH_NAME}.tar.gz"
|
||||
|
||||
(curl -o tmux.tar.gz -L $URL || wget -O tmux.tar.gz $URL) && tar -xf tmux.tar.gz
|
||||
|
||||
INSTALL_PATH="$HOME/.warp/tmux/local"
|
||||
echo "TERM=tmux-256color LD_LIBRARY_PATH=\"$INSTALL_PATH/lib\" TERMINFO=\"$INSTALL_PATH/share/terminfo/\" \"$INSTALL_PATH/bin/tmux\" \"\$@\";" > ~/.warp/tmux/execute_tmux.sh
|
||||
chmod +x ~/.warp/tmux/execute_tmux.sh;'
|
||||
|
||||
bash -c "$INSTALL_TMUX" && ~/.warp/tmux/execute_tmux.sh -Lwarp -CC && exit
|
||||
@@ -1,80 +0,0 @@
|
||||
function _is
|
||||
command -v $argv[1] >/dev/null 2>&1
|
||||
end
|
||||
|
||||
function _log
|
||||
set _hook $argv[1]
|
||||
set _value $argv[2]
|
||||
set _m (printf "{\"hook\": \"%s\", \"value\": %s}" $_hook $_value | od -An -v -tx1 | tr -d " \n")
|
||||
printf '\033\120\044\144%s\234' $_m
|
||||
end
|
||||
|
||||
function _err
|
||||
_log RemoteWarpificationIsUnavailable $argv[1]
|
||||
end
|
||||
|
||||
function _system_details
|
||||
set -l OS (uname)
|
||||
set -l PK ""
|
||||
|
||||
if test "$OS" = "Darwin"
|
||||
if _is brew
|
||||
set PK "homebrew"
|
||||
end
|
||||
else if test "$OS" = "Linux"
|
||||
if _is pacman
|
||||
set PK "pacman"
|
||||
else if _is zypper
|
||||
set PK "zypper"
|
||||
else if _is dnf
|
||||
set PK "dnf"
|
||||
else if _is yum; and _is yumdownloader
|
||||
set PK "yum"
|
||||
else if _is apt
|
||||
set PK "apt"
|
||||
end
|
||||
end
|
||||
|
||||
set -l CHECK (begin; command -v sudo > /dev/null 2>&1 && sudo -vn && sudo -ln; end 2>&1)
|
||||
set -l RA "no_root_access"
|
||||
if string match -qr '.*(may run|a password).*' "$CHECK"
|
||||
set RA "can_run_sudo"
|
||||
else if test (id -u) -eq 0; and test (whoami) = "root"
|
||||
set RA "is_root"
|
||||
end
|
||||
|
||||
set -l WH $( [ -w ~ ] && echo true || echo false )
|
||||
|
||||
printf '%s' "{\"os\": \"$OS\", \"pkg\": \"$PK\", \"shell\": \"fish\", \"root_access\": \"$RA\", \"writable_home\": $WH}"
|
||||
end
|
||||
|
||||
# _check_tmux is used in the install script post install!
|
||||
function _check_tmux
|
||||
set -g TMUX ""
|
||||
|
||||
if _is tmux
|
||||
set TMUX "tmux"
|
||||
_log SshTmuxInstaller "\"user\""
|
||||
else if _is $HOME/.warp/tmux/execute_tmux.sh
|
||||
set TMUX "$HOME/.warp/tmux/execute_tmux.sh"
|
||||
_log SshTmuxInstaller "\"warp\""
|
||||
end
|
||||
|
||||
if test -n "$TMUX"
|
||||
command $TMUX -V | awk '{print $2}' | read VER;
|
||||
if test -z "$VER"
|
||||
_err "\"TmuxFailed\""
|
||||
else if test (printf '%s\n' "$VER" "2.9" | sort -V | tail -n1) = "2.9"
|
||||
set -l DETAILS (_system_details)
|
||||
_err "{\"UnsupportedTmuxVersion\": $DETAILS}"
|
||||
else;
|
||||
return 0
|
||||
end
|
||||
else;
|
||||
set -l DETAILS (_system_details)
|
||||
_err "{\"TmuxNotInstalled\": $DETAILS}"
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
_check_tmux; and command $TMUX -Lwarp -CC; and exit
|
||||
@@ -1,51 +0,0 @@
|
||||
function _is
|
||||
command -v $argv[1] >/dev/null 2>&1
|
||||
end
|
||||
|
||||
function _l
|
||||
set _m (printf "{\"hook\": \"%s\", \"value\": %s}" $argv[1] $argv[2] | od -An -v -tx1 | tr -d " \n")
|
||||
printf '\033\120\044\144%s\234' $_m
|
||||
end
|
||||
|
||||
function _e
|
||||
_l RemoteWarpificationIsUnavailable $argv[1]
|
||||
end
|
||||
|
||||
function _sd
|
||||
set -l PK ""
|
||||
|
||||
if _is brew
|
||||
set PK "homebrew"
|
||||
end
|
||||
|
||||
printf '{"os": "Darwin", "pkg": "%s", "shell": "fish", "root_access": "no_root_access", "writable_home": %s}' "$PK" $( [ -w ~ ] && echo true || echo false )
|
||||
end
|
||||
|
||||
# _check_tmux is used in tmux install script post install!
|
||||
function _check_tmux
|
||||
set -g TMUX "$HOME/.warp/tmux/execute_tmux.sh"
|
||||
if _is "$TMUX"
|
||||
_l SshTmuxInstaller "\"warp\""
|
||||
else if _is tmux
|
||||
set TMUX "tmux"
|
||||
_l SshTmuxInstaller "\"user\""
|
||||
end
|
||||
|
||||
if test -n "$TMUX"
|
||||
$TMUX -V | awk '{print $2}' | read V;
|
||||
if test -z "$V"
|
||||
_e "\"TmuxFailed\""
|
||||
else if test (printf '%s\n' "$V" "2.9" | sort -V | tail -n1) = "2.9"
|
||||
set -l D (_sd)
|
||||
_e "{\"UnsupportedTmuxVersion\": $D}"
|
||||
else;
|
||||
return 0
|
||||
end
|
||||
else;
|
||||
set -l D (_sd)
|
||||
_e "{\"TmuxNotInstalled\": $D}"
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
_check_tmux; and $TMUX -Lwarp -CC; and exit
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#FF0000" fill-rule="evenodd" d="M21.751 22.607c1.34 1.005 3.35.335 1.508-1.508C17.73 15.74 18.904 1 12.037 1 5.17 1 6.342 15.74.815 21.1c-2.01 2.009.167 2.511 1.507 1.506 5.192-3.517 4.857-9.714 9.715-9.714 4.857 0 4.522 6.197 9.714 9.715z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 359 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.47994 2.19993C3.53894 2.54393 1.85494 4.83893 2.20194 8.03193C2.32494 9.16193 2.77794 10.6799 3.30794 11.7359L3.44694 12.0109L3.26594 12.3959C1.98794 15.1129 1.81194 17.7809 2.79494 19.5159C3.89294 21.4539 6.06194 22.2459 8.81994 21.7179C9.65294 21.5579 10.6929 21.2139 11.5879 20.8029L12.0349 20.5969L12.2799 20.7199C13.1829 21.1759 14.5269 21.6099 15.5799 21.7859C16.1889 21.8879 17.4679 21.8879 17.9789 21.7859C20.0439 21.3729 21.3799 20.0429 21.7799 17.9999C21.8709 17.5359 21.9019 16.5609 21.8429 16.0399C21.7099 14.8629 21.3369 13.5919 20.7739 12.3959L20.5929 12.0109L20.7119 11.7749C21.2019 10.8049 21.6469 9.40893 21.8059 8.33993C21.8769 7.86893 21.8769 6.55393 21.8059 6.15993C21.5909 4.95593 21.0949 4.04193 20.2669 3.32593C19.6479 2.79093 18.9519 2.46293 18.0399 2.27593C17.4479 2.15493 16.3559 2.14493 15.6399 2.25493C14.6469 2.40793 13.4279 2.78493 12.4479 3.24393L12.0249 3.44193L11.5029 3.20493C10.4779 2.73993 9.39394 2.40693 8.42794 2.26093C7.92694 2.18493 6.88494 2.15193 6.47994 2.19993ZM7.96794 4.21993C8.48194 4.28693 9.06494 4.42393 9.55394 4.58993L9.88894 4.70393L9.33394 5.11893C8.34194 5.86293 7.28994 6.80893 6.48894 7.67993C5.98794 8.22393 5.84094 8.46993 5.84094 8.75993C5.84094 9.27593 6.56294 9.87593 7.04794 9.76393C7.29394 9.70693 7.43594 9.59093 8.03994 8.95593C9.20394 7.73093 10.5369 6.62593 11.7089 5.91293L12.0159 5.72593L12.1979 5.83093C13.4859 6.57193 15.2039 8.03193 16.4059 9.40593C17.2999 10.4279 18.3619 11.9429 18.4539 12.3269C18.4679 12.3859 18.5229 12.4849 18.5749 12.5469C18.8599 12.8869 19.4359 14.2929 19.6579 15.1929C19.8199 15.8489 19.8589 16.1569 19.8579 16.7999C19.8579 17.5709 19.7899 17.9189 19.5339 18.4399C19.2009 19.1209 18.6529 19.5529 17.8579 19.7629C16.9889 19.9919 15.6709 19.8689 14.4699 19.4459L14.1589 19.3359L14.6889 18.9379C15.5379 18.2989 16.2059 17.7149 17.0399 16.8799C17.9619 15.9579 18.1279 15.7379 18.1509 15.4089C18.1709 15.1119 18.0909 14.9239 17.8389 14.6789C17.4939 14.3429 17.1179 14.2359 16.7759 14.3789C16.6889 14.4149 16.2979 14.7789 15.7479 15.3339C14.4599 16.6339 13.4399 17.4639 12.2489 18.1819L12.0229 18.3179L11.8419 18.2119C10.5249 17.4469 8.83694 16.0109 7.63294 14.6339C6.72294 13.5909 5.71994 12.1579 5.56294 11.6729C5.53694 11.5919 5.48794 11.5029 5.45394 11.4749C5.30394 11.3509 4.81694 10.2519 4.56194 9.46293C3.92094 7.48193 4.06394 5.85393 4.95894 4.95893C5.61794 4.29993 6.65194 4.04593 7.96794 4.21993ZM17.8349 4.27993C18.6169 4.46993 19.2009 4.92493 19.5359 5.60393C19.7959 6.13093 19.8559 6.43593 19.8549 7.23993C19.8549 7.74793 19.8349 8.00793 19.7679 8.36693C19.6889 8.78893 19.4389 9.69793 19.3649 9.82893C19.3459 9.86393 19.2049 9.70793 18.9379 9.35193C17.7399 7.75893 16.2819 6.30093 14.7059 5.11893L14.1509 4.70393L14.4859 4.58993C14.9339 4.43693 15.5689 4.28493 16.0359 4.21793C16.5549 4.14293 17.3929 4.17193 17.8349 4.27993ZM11.4769 10.0729C10.8059 10.2489 10.2379 10.8229 10.0599 11.5079C9.94094 11.9619 10.0029 12.4919 10.2249 12.9299C10.3609 13.1989 10.8009 13.6389 11.0699 13.7749C11.6589 14.0729 12.3409 14.0729 12.9299 13.7749C13.1959 13.6399 13.6379 13.1999 13.7709 12.9369C13.9939 12.4979 14.0519 12.0069 13.9399 11.5289C13.8579 11.1819 13.7199 10.9269 13.4649 10.6529C13.0739 10.2319 12.5599 10.0049 11.9999 10.0049C11.8569 10.0049 11.6219 10.0359 11.4769 10.0729ZM5.14294 14.7399C6.32594 16.3079 7.76894 17.7469 9.35194 18.9379L9.88094 19.3359L9.63094 19.4279C8.80194 19.7339 7.82694 19.8999 7.03994 19.8679C6.09594 19.8309 5.47894 19.6009 4.97894 19.1009C4.20494 18.3269 3.98194 17.0809 4.32194 15.4389C4.42994 14.9199 4.67594 14.1399 4.71794 14.1849C4.73594 14.2039 4.92694 14.4539 5.14294 14.7399Z" fill="#FF0000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.51 7.687c0 .197.02.357.058.475.042.117.096.245.17.384a.233.233 0 01.037.123c0 .053-.032.107-.1.16l-.336.224a.255.255 0 01-.138.048c-.054 0-.107-.026-.16-.074a1.652 1.652 0 01-.192-.251 4.137 4.137 0 01-.164-.315c-.416.491-.937.737-1.565.737-.447 0-.804-.129-1.064-.385-.261-.256-.394-.598-.394-1.025 0-.454.16-.822.484-1.1.325-.278.756-.416 1.304-.416.18 0 .367.016.564.042.197.027.4.07.612.118v-.39c0-.406-.085-.689-.25-.854-.17-.166-.458-.246-.868-.246-.186 0-.377.022-.574.07a4.23 4.23 0 00-.575.181 1.525 1.525 0 01-.186.07.326.326 0 01-.085.016c-.075 0-.112-.054-.112-.166v-.262c0-.085.01-.15.037-.186a.399.399 0 01.15-.113c.185-.096.409-.176.67-.24.26-.07.537-.101.83-.101.633 0 1.096.144 1.394.432.293.288.442.726.442 1.314v1.73h.01zm-2.161.811c.175 0 .356-.032.548-.096.192-.064.362-.182.505-.342a.848.848 0 00.181-.341c.032-.129.054-.283.054-.465V7.03a4.43 4.43 0 00-.49-.09 3.996 3.996 0 00-.5-.033c-.357 0-.617.07-.793.214-.176.144-.26.347-.26.614 0 .25.063.437.196.566.128.133.314.197.559.197zm4.273.577c-.096 0-.16-.016-.202-.054-.043-.032-.08-.106-.112-.208l-1.25-4.127a.938.938 0 01-.048-.214c0-.085.042-.133.127-.133h.522c.1 0 .17.016.207.053.043.032.075.107.107.208l.894 3.535.83-3.535c.026-.106.058-.176.101-.208a.365.365 0 01.213-.053h.426c.1 0 .17.016.212.053.043.032.08.107.102.208l.84 3.578.92-3.578a.459.459 0 01.107-.208.347.347 0 01.208-.053h.495c.085 0 .133.043.133.133 0 .027-.006.054-.01.086a.768.768 0 01-.038.133l-1.283 4.127c-.031.107-.069.177-.111.209a.34.34 0 01-.203.053h-.457c-.101 0-.17-.016-.213-.053-.043-.038-.08-.107-.101-.214L8.213 5.37l-.82 3.439c-.026.107-.058.176-.1.213-.043.038-.118.054-.213.054h-.458zm6.838.144a3.51 3.51 0 01-.82-.096c-.266-.064-.473-.134-.612-.214-.085-.048-.143-.101-.165-.15a.38.38 0 01-.031-.149v-.272c0-.112.042-.166.122-.166a.3.3 0 01.096.016c.032.011.08.032.133.054.18.08.378.144.585.187.213.042.42.064.633.064.336 0 .596-.059.777-.176a.575.575 0 00.277-.508.52.52 0 00-.144-.373c-.095-.102-.276-.193-.537-.278l-.772-.24c-.388-.123-.676-.305-.851-.545a1.275 1.275 0 01-.266-.774c0-.224.048-.422.143-.593.096-.17.224-.32.384-.438.16-.122.34-.213.553-.277.213-.064.436-.091.67-.091.118 0 .24.005.357.021.122.016.234.038.346.06.106.026.208.052.303.085.096.032.17.064.224.096a.461.461 0 01.16.133.289.289 0 01.047.176v.251c0 .112-.042.171-.122.171a.552.552 0 01-.202-.064 2.428 2.428 0 00-1.022-.208c-.303 0-.543.048-.708.15-.165.1-.25.256-.25.475 0 .149.053.277.16.379.106.101.303.202.585.293l.756.24c.383.123.66.294.825.513.165.219.244.47.244.748 0 .23-.047.437-.138.619a1.435 1.435 0 01-.388.47c-.165.133-.362.23-.591.299-.24.075-.49.112-.761.112z" fill="#FF0000"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.465 11.813c-1.75 1.297-4.294 1.986-6.481 1.986-3.065 0-5.827-1.137-7.913-3.027-.165-.15-.016-.353.18-.235 2.257 1.313 5.04 2.109 7.92 2.109 1.941 0 4.075-.406 6.039-1.239.293-.133.543.192.255.406z" fill="#FF0000"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.194 10.98c-.223-.287-1.479-.138-2.048-.069-.17.022-.197-.128-.043-.24 1-.705 2.645-.502 2.836-.267.192.24-.053 1.89-.99 2.68-.143.123-.281.06-.217-.1.212-.53.686-1.72.462-2.003z" fill="#FF0000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.15 9.51668C5.47222 9.8389 5.86667 10 6.33333 10C6.8 10 7.19444 9.8389 7.51667 9.51668L10.1833 6.85001C10.5056 6.52779 10.6667 6.13334 10.6667 5.66668C10.6667 5.20001 10.5056 4.80557 10.1833 4.48334C9.86111 4.16112 9.46667 4.00001 9 4.00001C8.53333 4.00001 8.13889 4.16112 7.81667 4.48334C7.40556 4.3389 7 4.30557 6.6 4.38334C6.2 4.46112 5.86111 4.6389 5.58333 4.91668C5.30556 5.19445 5.12778 5.53334 5.05 5.93334C4.97222 6.33334 5.00556 6.7389 5.15 7.15001C4.82778 7.47223 4.66667 7.86668 4.66667 8.33334C4.66667 8.80001 4.82778 9.19445 5.15 9.51668ZM4 14.6667V11.8C3.36667 11.2222 2.875 10.5472 2.525 9.77501C2.175 9.00279 2 8.1889 2 7.33334C2 5.66668 2.58333 4.25001 3.75 3.08334C4.91667 1.91668 6.33333 1.33334 8 1.33334C9.38889 1.33334 10.6194 1.74168 11.6917 2.55834C12.7639 3.37501 13.4611 4.4389 13.7833 5.75001L14.65 9.16668C14.7056 9.37779 14.6667 9.56945 14.5333 9.74168C14.4 9.9139 14.2222 10 14 10H12.6667V12C12.6667 12.3667 12.5361 12.6806 12.275 12.9417C12.0139 13.2028 11.7 13.3333 11.3333 13.3333H10V14.6667H8.66667V12H11.3333V8.66668H13.1333L12.5 6.08334C12.2444 5.07223 11.7 4.25001 10.8667 3.61668C10.0333 2.98334 9.07778 2.66668 8 2.66668C6.71111 2.66668 5.61111 3.11668 4.7 4.01668C3.78889 4.91668 3.33333 6.01112 3.33333 7.30001C3.33333 7.96668 3.46944 8.60001 3.74167 9.20001C4.01389 9.80001 4.4 10.3333 4.9 10.8L5.33333 11.2V14.6667H4Z" fill="#121212"/>
|
||||
<path d="M5.15 9.51668C5.47222 9.8389 5.86667 10 6.33333 10C6.8 10 7.19444 9.8389 7.51667 9.51668L10.1833 6.85001C10.5056 6.52779 10.6667 6.13334 10.6667 5.66668C10.6667 5.20001 10.5056 4.80557 10.1833 4.48334C9.86111 4.16112 9.46667 4.00001 9 4.00001C8.53333 4.00001 8.13889 4.16112 7.81667 4.48334C7.40556 4.3389 7 4.30557 6.6 4.38334C6.2 4.46112 5.86111 4.6389 5.58333 4.91668C5.30556 5.19445 5.12778 5.53334 5.05 5.93334C4.97222 6.33334 5.00556 6.7389 5.15 7.15001C4.82778 7.47223 4.66667 7.86668 4.66667 8.33334C4.66667 8.80001 4.82778 9.19445 5.15 9.51668ZM4 14.6667V11.8C3.36667 11.2222 2.875 10.5472 2.525 9.77501C2.175 9.00279 2 8.1889 2 7.33334C2 5.66668 2.58333 4.25001 3.75 3.08334C4.91667 1.91668 6.33333 1.33334 8 1.33334C9.38889 1.33334 10.6194 1.74168 11.6917 2.55834C12.7639 3.37501 13.4611 4.4389 13.7833 5.75001L14.65 9.16668C14.7056 9.37779 14.6667 9.56945 14.5333 9.74168C14.4 9.9139 14.2222 10 14 10H12.6667V12C12.6667 12.3667 12.5361 12.6806 12.275 12.9417C12.0139 13.2028 11.7 13.3333 11.3333 13.3333H10V14.6667H8.66667V12H11.3333V8.66668H13.1333L12.5 6.08334C12.2444 5.07223 11.7 4.25001 10.8667 3.61668C10.0333 2.98334 9.07778 2.66668 8 2.66668C6.71111 2.66668 5.61111 3.11668 4.7 4.01668C3.78889 4.91668 3.33333 6.01112 3.33333 7.30001C3.33333 7.96668 3.46944 8.60001 3.74167 9.20001C4.01389 9.80001 4.4 10.3333 4.9 10.8L5.33333 11.2V14.6667H4Z" fill="#FAF9F6" fill-opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,37 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
@@ -63,4 +31,36 @@
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,62 +1,62 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9321 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9321 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9321 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9321 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9321 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9321 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9321 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9321 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,34 +1,34 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9783 21.9519C12.6569 21.983 12.3306 22 12.001 22L11.4862 21.9874C11.3299 21.9795 11.1739 21.9669 11.0196 21.9519L11.2267 19.8194C11.4803 19.8441 11.7398 19.857 12.001 19.8571C12.262 19.857 12.5199 19.844 12.7733 19.8194L12.9783 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9783 21.9519C12.6569 21.983 12.3306 22 12.001 22L11.4862 21.9874C11.3299 21.9795 11.1739 21.9669 11.0196 21.9519L11.2267 19.8194C11.4803 19.8441 11.7398 19.857 12.001 19.8571C12.262 19.857 12.5199 19.844 12.7733 19.8194L12.9783 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9783 21.9519C12.6569 21.983 12.3306 22 12.001 22L11.4862 21.9874C11.3299 21.9795 11.1739 21.9669 11.0196 21.9519L11.2267 19.8194C11.4803 19.8441 11.7398 19.857 12.001 19.8571C12.262 19.857 12.5199 19.844 12.7733 19.8194L12.9783 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29696 18.9321C8.74626 19.1728 9.22246 19.3721 9.7179 19.5222L9.09637 21.5689C8.46453 21.3773 7.85829 21.1262 7.28618 20.8197L8.29696 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29696 18.9321C8.74626 19.1728 9.22246 19.3721 9.7179 19.5222L9.09637 21.5689C8.46453 21.3773 7.85829 21.1262 7.28618 20.8197L8.29696 18.9321Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29696 18.9321C8.74626 19.1728 9.22246 19.3721 9.7179 19.5222L9.09637 21.5689C8.46453 21.3773 7.85829 21.1262 7.28618 20.8197L8.29696 18.9321Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7138 20.8197C16.1409 21.1265 15.5343 21.3773 14.9015 21.5689L14.2821 19.5222C14.7777 19.3721 15.2536 19.1728 15.703 18.9321L16.7138 20.8197Z" fill="#050505"/>
|
||||
<path d="M16.7138 20.8197C16.1409 21.1265 15.5343 21.3773 14.9015 21.5689L14.2821 19.5222C14.7777 19.3721 15.2536 19.1728 15.703 18.9321L16.7138 20.8197Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7138 20.8197C16.1409 21.1265 15.5343 21.3773 14.9015 21.5689L14.2821 19.5222C14.7777 19.3721 15.2536 19.1728 15.703 18.9321L16.7138 20.8197Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92592 16.9838C6.25292 17.3818 6.61827 17.7491 7.01622 18.0762L5.65596 19.7294C5.1502 19.3137 4.68405 18.8499 4.26849 18.344L5.02187 17.7267L5.92592 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92592 16.9838C6.25292 17.3818 6.61827 17.7491 7.01622 18.0762L5.65596 19.7294C5.1502 19.3137 4.68405 18.8499 4.26849 18.344L5.02187 17.7267L5.92592 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92592 16.9838C6.25292 17.3818 6.61827 17.7491 7.01622 18.0762L5.65596 19.7294C5.1502 19.3137 4.68405 18.8499 4.26849 18.344L5.02187 17.7267L5.92592 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7294 18.3419C19.3132 18.8486 18.8486 19.3132 18.3419 19.7294L16.9838 18.0762C17.382 17.749 17.749 17.382 18.0762 16.9838L19.7294 18.3419Z" fill="#050505"/>
|
||||
<path d="M19.7294 18.3419C19.3132 18.8486 18.8486 19.3132 18.3419 19.7294L16.9838 18.0762C17.382 17.749 17.749 17.382 18.0762 16.9838L19.7294 18.3419Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7294 18.3419C19.3132 18.8486 18.8486 19.3132 18.3419 19.7294L16.9838 18.0762C17.382 17.749 17.749 17.382 18.0762 16.9838L19.7294 18.3419Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47986 14.2821C4.62991 14.7774 4.82742 15.2539 5.06791 15.703L3.18029 16.7138C2.87357 16.1409 2.62054 15.5343 2.429 14.9015L4.47986 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47986 14.2821C4.62991 14.7774 4.82742 15.2539 5.06791 15.703L3.18029 16.7138C2.87357 16.1409 2.62054 15.5343 2.429 14.9015L4.47986 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47986 14.2821C4.62991 14.7774 4.82742 15.2539 5.06791 15.703L3.18029 16.7138C2.87357 16.1409 2.62054 15.5343 2.429 14.9015L4.47986 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.5689 14.9015C21.3773 15.5343 21.1265 16.1409 20.8197 16.7138L18.9321 15.703C19.1728 15.2536 19.3721 14.7777 19.5222 14.2821L21.5689 14.9015Z" fill="#050505"/>
|
||||
<path d="M21.5689 14.9015C21.3773 15.5343 21.1265 16.1409 20.8197 16.7138L18.9321 15.703C19.1728 15.2536 19.3721 14.7777 19.5222 14.2821L21.5689 14.9015Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.5689 14.9015C21.3773 15.5343 21.1265 16.1409 20.8197 16.7138L18.9321 15.703C19.1728 15.2536 19.3721 14.7777 19.5222 14.2821L21.5689 14.9015Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1806 11.2267C4.15593 11.4803 4.14296 11.7398 4.14293 12.001C4.14295 12.262 4.15598 12.52 4.1806 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9783C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.001C2.00001 11.8289 2.00395 11.6563 2.01256 11.4862L2.04813 11.0196L4.1806 11.2267Z" fill="#050505"/>
|
||||
<path d="M4.1806 11.2267C4.15593 11.4803 4.14296 11.7398 4.14293 12.001C4.14295 12.262 4.15598 12.52 4.1806 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9783C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.001C2.00001 11.8289 2.00395 11.6563 2.01256 11.4862L2.04813 11.0196L4.1806 11.2267Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1806 11.2267C4.15593 11.4803 4.14296 11.7398 4.14293 12.001C4.14295 12.262 4.15598 12.52 4.1806 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9783C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.001C2.00001 11.8289 2.00395 11.6563 2.01256 11.4862L2.04813 11.0196L4.1806 11.2267Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22 12.001L21.9874 12.5159C21.9796 12.6708 21.9667 12.8253 21.9519 12.9783L20.8867 12.8779L19.8194 12.7733C19.844 12.5199 19.857 12.262 19.8571 12.001C19.857 11.7398 19.8441 11.4803 19.8194 11.2267L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22 12.001L21.9874 12.5159C21.9796 12.6708 21.9667 12.8253 21.9519 12.9783L20.8867 12.8779L19.8194 12.7733C19.844 12.5199 19.857 12.262 19.8571 12.001C19.857 11.7398 19.8441 11.4803 19.8194 11.2267L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22 12.001L21.9874 12.5159C21.9796 12.6708 21.9667 12.8253 21.9519 12.9783L20.8867 12.8779L19.8194 12.7733C19.844 12.5199 19.857 12.262 19.8571 12.001C19.857 11.7398 19.8441 11.4803 19.8194 11.2267L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79052L5.06791 8.29696C4.82721 8.74635 4.63003 9.22236 4.47986 9.7179L2.429 9.09637C2.62048 8.46454 2.87184 7.85829 3.17819 7.28618L4.1241 7.79052Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79052L5.06791 8.29696C4.82721 8.74635 4.63003 9.22236 4.47986 9.7179L2.429 9.09637C2.62048 8.46454 2.87184 7.85829 3.17819 7.28618L4.1241 7.79052Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79052L5.06791 8.29696C4.82721 8.74635 4.63003 9.22236 4.47986 9.7179L2.429 9.09637C2.62048 8.46454 2.87184 7.85829 3.17819 7.28618L4.1241 7.79052Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8197 7.28618C21.1262 7.85829 21.3773 8.46453 21.5689 9.09637L19.5222 9.7179C19.3721 9.22246 19.1728 8.74626 18.9321 8.29696L20.1145 7.66496L20.8197 7.28618Z" fill="#050505"/>
|
||||
<path d="M20.8197 7.28618C21.1262 7.85829 21.3773 8.46453 21.5689 9.09637L19.5222 9.7179C19.3721 9.22246 19.1728 8.74626 18.9321 8.29696L20.1145 7.66496L20.8197 7.28618Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8197 7.28618C21.1262 7.85829 21.3773 8.46453 21.5689 9.09637L19.5222 9.7179C19.3721 9.22246 19.1728 8.74626 18.9321 8.29696L20.1145 7.66496L20.8197 7.28618Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65596 4.27059L7.01622 5.92592C6.61832 6.2529 6.2529 6.61832 5.92592 7.01622L4.27059 5.65596L4.26849 5.65387C4.6839 5.14838 5.14838 4.6839 5.65387 4.26849L5.65596 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65596 4.27059L7.01622 5.92592C6.61832 6.2529 6.2529 6.61832 5.92592 7.01622L4.27059 5.65596L4.26849 5.65387C4.6839 5.14838 5.14838 4.6839 5.65387 4.26849L5.65596 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65596 4.27059L7.01622 5.92592C6.61832 6.2529 6.2529 6.61832 5.92592 7.01622L4.27059 5.65596L4.26849 5.65387C4.6839 5.14838 5.14838 4.6839 5.65387 4.26849L5.65596 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.344 4.26849C18.8499 4.68405 19.3137 5.1502 19.7294 5.65596L18.0762 7.01622C17.7491 6.61827 17.3818 6.25292 16.9838 5.92592L18.344 4.26849Z" fill="#050505"/>
|
||||
<path d="M18.344 4.26849C18.8499 4.68405 19.3137 5.1502 19.7294 5.65596L18.0762 7.01622C17.7491 6.61827 17.3818 6.25292 16.9838 5.92592L18.344 4.26849Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.344 4.26849C18.8499 4.68405 19.3137 5.1502 19.7294 5.65596L18.0762 7.01622C17.7491 6.61827 17.3818 6.25292 16.9838 5.92592L18.344 4.26849Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.7179 4.47986C9.22236 4.63003 8.74635 4.82721 8.29696 5.06791L7.28618 3.17819C7.85829 2.87184 8.46454 2.62048 9.09637 2.429L9.7179 4.47986Z" fill="#050505"/>
|
||||
<path d="M9.7179 4.47986C9.22236 4.63003 8.74635 4.82721 8.29696 5.06791L7.28618 3.17819C7.85829 2.87184 8.46454 2.62048 9.09637 2.429L9.7179 4.47986Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.7179 4.47986C9.22236 4.63003 8.74635 4.82721 8.29696 5.06791L7.28618 3.17819C7.85829 2.87184 8.46454 2.62048 9.09637 2.429L9.7179 4.47986Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9015 2.429C15.5343 2.62054 16.1409 2.87357 16.7138 3.18029L16.3853 3.79764L15.703 5.06791C15.2539 4.82742 14.7774 4.62991 14.2821 4.47986L14.9015 2.429Z" fill="#050505"/>
|
||||
<path d="M14.9015 2.429C15.5343 2.62054 16.1409 2.87357 16.7138 3.18029L16.3853 3.79764L15.703 5.06791C15.2539 4.82742 14.7774 4.62991 14.2821 4.47986L14.9015 2.429Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9015 2.429C15.5343 2.62054 16.1409 2.87357 16.7138 3.18029L16.3853 3.79764L15.703 5.06791C15.2539 4.82742 14.7774 4.62991 14.2821 4.47986L14.9015 2.429Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9783 2.04813L12.8821 3.07147L12.7733 4.1806C12.52 4.15598 12.262 4.14295 12.001 4.14293C11.7398 4.14296 11.4803 4.15593 11.2267 4.1806L11.0196 2.04813C11.342 2.01674 11.6703 2.00002 12.001 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9783 2.04813L12.8821 3.07147L12.7733 4.1806C12.52 4.15598 12.262 4.14295 12.001 4.14293C11.7398 4.14296 11.4803 4.15593 11.2267 4.1806L11.0196 2.04813C11.342 2.01674 11.6703 2.00002 12.001 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9783 2.04813L12.8821 3.07147L12.7733 4.1806C12.52 4.15598 12.262 4.14295 12.001 4.14293C11.7398 4.14296 11.4803 4.15593 11.2267 4.1806L11.0196 2.04813C11.342 2.01674 11.6703 2.00002 12.001 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
@@ -1,60 +1,60 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9321 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9321 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9321 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9321 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,56 +1,56 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,54 +1,54 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9321L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -1,50 +1,50 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9321C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9321Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -1,46 +1,46 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
@@ -1,44 +1,44 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -1,40 +1,40 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
@@ -1,38 +1,38 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="#050505"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.9784 21.9519C12.657 21.9831 12.3307 22 12.0011 22.0001L11.4863 21.9875C11.3299 21.9796 11.174 21.967 11.0196 21.9519L11.2268 19.8195C11.4803 19.8441 11.7399 19.8571 12.0011 19.8571C12.2621 19.8571 12.52 19.8441 12.7733 19.8195L12.9784 21.9519Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="#050505"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M8.29698 18.9322C8.74629 19.1728 9.22248 19.3721 9.71793 19.5223L9.09639 21.569C8.46455 21.3774 7.85831 21.1262 7.2862 20.8198L8.29698 18.9322Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="#050505"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M16.7139 20.8198C16.141 21.1266 15.5344 21.3774 14.9016 21.569L14.2821 19.5223C14.7777 19.3721 15.2536 19.1728 15.7031 18.9322L16.7139 20.8198Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="#050505"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.92593 16.9838C6.25293 17.3818 6.61828 17.7492 7.01624 18.0762L5.65597 19.7295C5.15021 19.3138 4.68406 18.85 4.2685 18.3441L5.02188 17.7267L5.92593 16.9838Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="#050505"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M19.7295 18.342C19.3133 18.8487 18.8487 19.3133 18.342 19.7295L16.9838 18.0762C17.3821 17.749 17.749 17.3821 18.0762 16.9838L19.7295 18.342Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="#050505"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.47987 14.2821C4.62992 14.7774 4.82743 15.2539 5.06792 15.7031L3.18029 16.7139C2.87357 16.141 2.62054 15.5343 2.42901 14.9016L4.47987 14.2821Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="#050505"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.569 14.9016C21.3774 15.5344 21.1266 16.141 20.8198 16.7139L18.9322 15.7031C19.1728 15.2536 19.3721 14.7777 19.5223 14.2821L21.569 14.9016Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="#050505"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.18061 11.2268C4.15594 11.4803 4.14296 11.7399 4.14294 12.0011C4.14296 12.262 4.15599 12.52 4.18061 12.7733L3.11123 12.8758L3.11332 12.8779L2.04813 12.9784C2.03329 12.8254 2.02042 12.6708 2.01256 12.5159L2 12.0011C2.00001 11.8289 2.00395 11.6563 2.01256 11.4863L2.04813 11.0196L4.18061 11.2268Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="#050505"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M21.9519 11.0196C21.9833 11.3421 22 11.6703 22.0001 12.0011L21.9875 12.5159C21.9796 12.6708 21.9668 12.8254 21.9519 12.9784L20.8867 12.8779L19.8195 12.7733C19.8441 12.52 19.8571 12.2621 19.8571 12.0011C19.8571 11.7399 19.8441 11.4803 19.8195 11.2268L21.9519 11.0196Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="#050505"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M4.1241 7.79054L5.06792 8.29698C4.82722 8.74637 4.63004 9.22238 4.47987 9.71793L2.42901 9.09639C2.62049 8.46456 2.87184 7.85831 3.1782 7.2862L4.1241 7.79054Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="#050505"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M20.8198 7.2862C21.1262 7.85831 21.3774 8.46455 21.569 9.09639L19.5223 9.71793C19.3721 9.22248 19.1728 8.74629 18.9322 8.29698L20.1145 7.66498L20.8198 7.2862Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="#050505"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M5.65597 4.27059L7.01624 5.92593C6.61833 6.25291 6.25291 6.61833 5.92593 7.01624L4.27059 5.65597L4.2685 5.65388C4.68391 5.14839 5.14839 4.68391 5.65388 4.2685L5.65597 4.27059Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="#050505"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M18.3441 4.2685C18.85 4.68406 19.3138 5.15021 19.7295 5.65597L18.0762 7.01624C17.7492 6.61828 17.3818 6.25293 16.9838 5.92593L18.3441 4.2685Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="#050505"/>
|
||||
<path d="M14.9016 2.42901C15.5343 2.62054 16.141 2.87357 16.7139 3.18029L16.3853 3.79764L15.7031 5.06792C15.2539 4.82743 14.7774 4.62992 14.2821 4.47987L14.9016 2.42901Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.9"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="#050505"/>
|
||||
<path d="M9.71793 4.47987C9.22238 4.63004 8.74637 4.82722 8.29698 5.06792L7.2862 3.1782C7.85831 2.87184 8.46456 2.62049 9.09639 2.42901L9.71793 4.47987Z" fill="white" fill-opacity="0.4"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="#050505"/>
|
||||
<path d="M12.5159 2.01256C12.6708 2.02042 12.8254 2.03329 12.9784 2.04813L12.8821 3.07147L12.7733 4.18061C12.52 4.15599 12.262 4.14296 12.0011 4.14294C11.7399 4.14296 11.4803 4.15594 11.2268 4.18061L11.0196 2.04813C11.3421 2.01674 11.6704 2.00002 12.0011 2L12.5159 2.01256Z" fill="white" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="11" height="10" viewBox="0 0 11 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.3645 0.0198916C0.9735 0.0573916 0.691 0.182891 0.435 0.432391C0.2355 0.627391 0.108 0.862892 0.037 1.16739C0.002 1.31889 0 1.46539 0 4.00739V6.68739L0.0455 6.86489C0.1025 7.08939 0.1795 7.24989 0.315 7.42639C0.5055 7.67489 0.7835 7.85389 1.112 7.94089C1.279 7.98489 1.2915 7.98539 3.1845 7.99239C4.467 7.99689 5.114 7.99239 5.1665 7.97839C5.2815 7.94739 5.374 7.87689 5.43 7.77639C5.4735 7.69839 5.48 7.66339 5.4795 7.49739C5.4785 7.23189 5.416 7.12089 5.2175 7.03289C5.173 7.01289 4.7385 7.00539 3.26 6.99739L1.36 6.98739L1.265 6.94039C1.1575 6.88739 1.0605 6.78089 1.0255 6.67739C1.009 6.62689 1.0005 6.17039 0.995 5.05239L0.988 3.49739H5.486H9.9845L9.9965 3.59889C10.027 3.85489 10.203 3.99739 10.49 3.99739C10.704 3.99739 10.8465 3.92539 10.93 3.77639L10.98 3.68739V2.50739C10.98 1.42789 10.977 1.31389 10.943 1.16739C10.834 0.699392 10.5735 0.365892 10.1545 0.157892C9.9995 0.0813916 9.843 0.0428915 9.58 0.0183915C9.3115 -0.00710853 1.6285 -0.0056084 1.3645 0.0198916ZM9.677 1.03439C9.7775 1.06839 9.9205 1.21289 9.9505 1.31039C9.965 1.35739 9.978 1.60289 9.9845 1.94239L9.995 2.49739H5.4925H0.99V2.06239C0.9905 1.51539 1.011 1.31789 1.0795 1.21439C1.1565 1.09789 1.2635 1.03739 1.423 1.01839C1.4985 1.00939 3.369 1.00339 5.58 1.00539C8.8255 1.00789 9.615 1.01339 9.677 1.03439ZM8.5015 4.51739C8.21 4.56989 7.998 4.68539 7.769 4.91539C7.4685 5.21739 7.37 5.47789 7.37 5.97039V6.25639L7.315 6.26839C6.883 6.36089 6.577 6.66939 6.511 7.07739C6.4845 7.24389 6.484 8.24689 6.5105 8.41739C6.556 8.70889 6.773 9.00589 7.0325 9.13139C7.275 9.24839 7.258 9.24739 8.74 9.24739C9.8215 9.24739 10.1225 9.24189 10.21 9.21989C10.5565 9.13439 10.817 8.89739 10.937 8.55939C10.973 8.45939 10.978 8.38289 10.9855 7.84389C10.995 7.14739 10.981 7.01239 10.881 6.80739C10.743 6.52489 10.497 6.33989 10.165 6.26839L10.11 6.25639V5.97039C10.11 5.47139 10.012 5.21639 9.701 4.90689C9.5075 4.71439 9.3295 4.60589 9.11 4.54639C8.974 4.50939 8.634 4.49339 8.5015 4.51739ZM8.9465 5.56239C9.075 5.64789 9.11 5.74639 9.11 6.02389V6.25739H8.7385H8.367L8.3735 6.00089C8.3795 5.76189 8.3835 5.73889 8.4335 5.66389C8.5445 5.49589 8.7765 5.45039 8.9465 5.56239ZM9.99 7.74489V8.23239L9.9275 8.24489C9.893 8.25189 9.359 8.25739 8.74 8.25739C8.121 8.25739 7.587 8.25189 7.5525 8.24489L7.49 8.23239V7.74489V7.25739H8.74H9.99V7.74489Z" fill="#050505"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.3645 0.0198916C0.9735 0.0573916 0.691 0.182891 0.435 0.432391C0.2355 0.627391 0.108 0.862892 0.037 1.16739C0.002 1.31889 0 1.46539 0 4.00739V6.68739L0.0455 6.86489C0.1025 7.08939 0.1795 7.24989 0.315 7.42639C0.5055 7.67489 0.7835 7.85389 1.112 7.94089C1.279 7.98489 1.2915 7.98539 3.1845 7.99239C4.467 7.99689 5.114 7.99239 5.1665 7.97839C5.2815 7.94739 5.374 7.87689 5.43 7.77639C5.4735 7.69839 5.48 7.66339 5.4795 7.49739C5.4785 7.23189 5.416 7.12089 5.2175 7.03289C5.173 7.01289 4.7385 7.00539 3.26 6.99739L1.36 6.98739L1.265 6.94039C1.1575 6.88739 1.0605 6.78089 1.0255 6.67739C1.009 6.62689 1.0005 6.17039 0.995 5.05239L0.988 3.49739H5.486H9.9845L9.9965 3.59889C10.027 3.85489 10.203 3.99739 10.49 3.99739C10.704 3.99739 10.8465 3.92539 10.93 3.77639L10.98 3.68739V2.50739C10.98 1.42789 10.977 1.31389 10.943 1.16739C10.834 0.699392 10.5735 0.365892 10.1545 0.157892C9.9995 0.0813916 9.843 0.0428915 9.58 0.0183915C9.3115 -0.00710853 1.6285 -0.0056084 1.3645 0.0198916ZM9.677 1.03439C9.7775 1.06839 9.9205 1.21289 9.9505 1.31039C9.965 1.35739 9.978 1.60289 9.9845 1.94239L9.995 2.49739H5.4925H0.99V2.06239C0.9905 1.51539 1.011 1.31789 1.0795 1.21439C1.1565 1.09789 1.2635 1.03739 1.423 1.01839C1.4985 1.00939 3.369 1.00339 5.58 1.00539C8.8255 1.00789 9.615 1.01339 9.677 1.03439ZM8.5015 4.51739C8.21 4.56989 7.998 4.68539 7.769 4.91539C7.4685 5.21739 7.37 5.47789 7.37 5.97039V6.25639L7.315 6.26839C6.883 6.36089 6.577 6.66939 6.511 7.07739C6.4845 7.24389 6.484 8.24689 6.5105 8.41739C6.556 8.70889 6.773 9.00589 7.0325 9.13139C7.275 9.24839 7.258 9.24739 8.74 9.24739C9.8215 9.24739 10.1225 9.24189 10.21 9.21989C10.5565 9.13439 10.817 8.89739 10.937 8.55939C10.973 8.45939 10.978 8.38289 10.9855 7.84389C10.995 7.14739 10.981 7.01239 10.881 6.80739C10.743 6.52489 10.497 6.33989 10.165 6.26839L10.11 6.25639V5.97039C10.11 5.47139 10.012 5.21639 9.701 4.90689C9.5075 4.71439 9.3295 4.60589 9.11 4.54639C8.974 4.50939 8.634 4.49339 8.5015 4.51739ZM8.9465 5.56239C9.075 5.64789 9.11 5.74639 9.11 6.02389V6.25739H8.7385H8.367L8.3735 6.00089C8.3795 5.76189 8.3835 5.73889 8.4335 5.66389C8.5445 5.49589 8.7765 5.45039 8.9465 5.56239ZM9.99 7.74489V8.23239L9.9275 8.24489C9.893 8.25189 9.359 8.25739 8.74 8.25739C8.121 8.25739 7.587 8.25189 7.5525 8.24489L7.49 8.23239V7.74489V7.25739H8.74H9.99V7.74489Z" fill="white" fill-opacity="0.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="red" fill-rule="evenodd" clip-rule="evenodd" d="M2.72115 0.608997C1.99081 0.685997 1.4279 1.1655 1.23131 1.87833C1.18231 2.05508 1.17998 2.10991 1.17998 2.91666C1.17998 3.72516 1.18231 3.77766 1.2319 3.955C1.40515 4.57858 1.83506 5.0085 2.46273 5.18525C2.64648 5.23658 2.66573 5.23716 4.5324 5.24475L6.41656 5.25233V6.12325V6.99475L5.17406 7.00641C3.85281 7.01866 3.64631 7.03383 3.31323 7.1435C2.54031 7.39725 1.97856 8.03716 1.82223 8.84333C1.78315 9.04283 1.7499 9.45933 1.7499 9.74341V10.0012L1.5574 10.0992C1.12106 10.3209 0.796147 10.7071 0.64973 11.1767C0.57098 11.4298 0.57098 11.9035 0.64973 12.1567C0.828814 12.7318 1.26806 13.1711 1.84323 13.3502C1.99023 13.3957 2.06898 13.4044 2.33323 13.4044C2.59748 13.4044 2.67623 13.3957 2.82323 13.3502C3.3984 13.1711 3.83765 12.7318 4.01673 12.1567C4.09548 11.9035 4.09548 11.4298 4.01673 11.1767C3.87031 10.7059 3.5454 10.3209 3.10615 10.0975L2.91015 9.99833L2.92648 9.60166C2.94398 9.16533 2.98015 8.94425 3.06123 8.778C3.13006 8.63683 3.38673 8.38016 3.5279 8.31133C3.76881 8.19408 3.8674 8.18591 5.18573 8.17308L6.41656 8.16141V9.08133V10.0012L6.22406 10.0992C5.78773 10.3209 5.46281 10.7071 5.3164 11.1767C5.23765 11.4298 5.23765 11.9035 5.3164 12.1567C5.49548 12.7318 5.93473 13.1711 6.5099 13.3502C6.76306 13.4289 7.23673 13.4289 7.4899 13.3502C8.06506 13.1711 8.50431 12.7318 8.6834 12.1567C8.76215 11.9035 8.76215 11.4298 8.6834 11.1767C8.53698 10.7071 8.21206 10.3209 7.77573 10.0992L7.58323 10.0012V9.08133V8.16141L8.81406 8.17308C10.1324 8.18591 10.231 8.19408 10.4719 8.31133C10.6131 8.38016 10.8697 8.63683 10.9386 8.778C11.0196 8.94425 11.0558 9.16533 11.0733 9.60166L11.0896 9.99833L10.8936 10.0975C10.4544 10.3209 10.1295 10.7059 9.98306 11.1767C9.90431 11.4298 9.90431 11.9035 9.98306 12.1567C10.1621 12.7318 10.6014 13.1711 11.1766 13.3502C11.4297 13.4289 11.9034 13.4289 12.1566 13.3502C12.7317 13.1711 13.171 12.7318 13.3501 12.1567C13.4288 11.9035 13.4288 11.4298 13.3501 11.1767C13.2036 10.7071 12.8787 10.3209 12.4424 10.0992L12.2499 10.0012V9.74341C12.2499 9.28433 12.1997 8.87133 12.107 8.56333C11.9139 7.92225 11.3539 7.36225 10.6866 7.1435C10.3535 7.03383 10.147 7.01866 8.82573 7.00641L7.58323 6.99475V6.12325V5.25233L9.4674 5.24475C11.3341 5.23716 11.3533 5.23658 11.5371 5.18525C12.1636 5.00908 12.5929 4.57916 12.7691 3.95033C12.8169 3.77825 12.8198 3.72166 12.8198 2.91666C12.8198 2.10758 12.8175 2.05566 12.7679 1.8795C12.6279 1.379 12.3269 1.00566 11.8853 0.78458C11.6753 0.67958 11.5166 0.63583 11.2466 0.607247C10.9753 0.579247 2.99065 0.580414 2.72115 0.608997ZM11.3451 1.81825C11.477 1.88533 11.5271 1.93666 11.6001 2.07666C11.6543 2.1805 11.6549 2.191 11.6549 2.91666V3.65166L11.5971 3.76483C11.5336 3.88908 11.442 3.97716 11.3166 4.03433C11.2425 4.06816 10.8306 4.07166 6.9999 4.07166C3.16915 4.07166 2.75731 4.06816 2.68323 4.03433C2.55665 3.97658 2.45865 3.88266 2.40148 3.76483C2.35306 3.6645 2.3484 3.61725 2.33906 3.09458C2.32623 2.39866 2.34956 2.13616 2.43531 2.00666C2.52865 1.86608 2.65756 1.79141 2.84248 1.77216C2.92823 1.76341 4.85148 1.757 7.11656 1.75875L11.2349 1.76166L11.3451 1.81825ZM2.51406 11.1189C2.73456 11.1842 2.91656 11.4322 2.91656 11.6672C2.91656 11.8597 2.79056 12.0727 2.61556 12.1753C2.52456 12.2284 2.4814 12.2383 2.33323 12.2383C2.18506 12.2383 2.1419 12.2284 2.0509 12.1753C1.71198 11.9758 1.65248 11.5348 1.92723 11.2601C2.08998 11.0973 2.28423 11.0507 2.51406 11.1189ZM7.18073 11.1189C7.40123 11.1842 7.58323 11.4322 7.58323 11.6672C7.58323 11.8597 7.45723 12.0727 7.28223 12.1753C7.19123 12.2284 7.14806 12.2383 6.9999 12.2383C6.85173 12.2383 6.80856 12.2284 6.71756 12.1753C6.37865 11.9758 6.31915 11.5348 6.5939 11.2601C6.75665 11.0973 6.9509 11.0507 7.18073 11.1189ZM11.8474 11.1189C12.0679 11.1842 12.2499 11.4322 12.2499 11.6672C12.2499 11.8597 12.1239 12.0727 11.9489 12.1753C11.8579 12.2284 11.8147 12.2383 11.6666 12.2383C11.5184 12.2383 11.4752 12.2284 11.3842 12.1753C11.0453 11.9758 10.9858 11.5348 11.2606 11.2601C11.4233 11.0973 11.6176 11.0507 11.8474 11.1189Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.3334 10.0002C13.3334 10.3668 13.2029 10.6807 12.9417 10.9418C12.6806 11.2029 12.3667 11.3335 12.0001 11.3335C11.6334 11.3335 11.3195 11.2029 11.0584 10.9418C10.7973 10.6807 10.6667 10.3668 10.6667 10.0002C10.6667 9.6335 10.7973 9.31961 11.0584 9.0585C11.3195 8.79739 11.6334 8.66683 12.0001 8.66683C12.3667 8.66683 12.6806 8.79739 12.9417 9.0585C13.2029 9.31961 13.3334 9.6335 13.3334 10.0002ZM13.3334 6.00016C13.3334 6.36683 13.2029 6.68072 12.9417 6.94183C12.6806 7.20294 12.3667 7.3335 12.0001 7.3335C11.6334 7.3335 11.3195 7.20294 11.0584 6.94183C10.7973 6.68072 10.6667 6.36683 10.6667 6.00016C10.6667 5.6335 10.7973 5.31961 11.0584 5.0585C11.3195 4.79739 11.6334 4.66683 12.0001 4.66683C12.3667 4.66683 12.6806 4.79739 12.9417 5.0585C13.2029 5.31961 13.3334 5.6335 13.3334 6.00016ZM9.33342 10.0002C9.33342 10.3668 9.20286 10.6807 8.94175 10.9418C8.68064 11.2029 8.36675 11.3335 8.00008 11.3335C7.63342 11.3335 7.31953 11.2029 7.05842 10.9418C6.7973 10.6807 6.66675 10.3668 6.66675 10.0002C6.66675 9.6335 6.7973 9.31961 7.05842 9.0585C7.31953 8.79739 7.63342 8.66683 8.00008 8.66683C8.36675 8.66683 8.68064 8.79739 8.94175 9.0585C9.20286 9.31961 9.33342 9.6335 9.33342 10.0002ZM9.33342 6.00016C9.33342 6.36683 9.20286 6.68072 8.94175 6.94183C8.68064 7.20294 8.36675 7.3335 8.00008 7.3335C7.63342 7.3335 7.31953 7.20294 7.05842 6.94183C6.7973 6.68072 6.66675 6.36683 6.66675 6.00016C6.66675 5.6335 6.7973 5.31961 7.05842 5.0585C7.31953 4.79739 7.63342 4.66683 8.00008 4.66683C8.36675 4.66683 8.68064 4.79739 8.94175 5.0585C9.20286 5.31961 9.33342 5.6335 9.33342 6.00016ZM5.33342 10.0002C5.33342 10.3668 5.20286 10.6807 4.94175 10.9418C4.68064 11.2029 4.36675 11.3335 4.00008 11.3335C3.63342 11.3335 3.31953 11.2029 3.05842 10.9418C2.7973 10.6807 2.66675 10.3668 2.66675 10.0002C2.66675 9.6335 2.7973 9.31961 3.05842 9.0585C3.31953 8.79739 3.63342 8.66683 4.00008 8.66683C4.36675 8.66683 4.68064 8.79739 4.94175 9.0585C5.20286 9.31961 5.33342 9.6335 5.33342 10.0002ZM5.33342 6.00016C5.33342 6.36683 5.20286 6.68072 4.94175 6.94183C4.68064 7.20294 4.36675 7.3335 4.00008 7.3335C3.63342 7.3335 3.31953 7.20294 3.05842 6.94183C2.7973 6.68072 2.66675 6.36683 2.66675 6.00016C2.66675 5.6335 2.7973 5.31961 3.05842 5.0585C3.31953 4.79739 3.63342 4.66683 4.00008 4.66683C4.36675 4.66683 4.68064 4.79739 4.94175 5.0585C5.20286 5.31961 5.33342 5.6335 5.33342 6.00016Z" transform="rotate(90 8 8)" fill="#121212"/>
|
||||
<path d="M13.3334 10.0002C13.3334 10.3668 13.2029 10.6807 12.9417 10.9418C12.6806 11.2029 12.3667 11.3335 12.0001 11.3335C11.6334 11.3335 11.3195 11.2029 11.0584 10.9418C10.7973 10.6807 10.6667 10.3668 10.6667 10.0002C10.6667 9.6335 10.7973 9.31961 11.0584 9.0585C11.3195 8.79739 11.6334 8.66683 12.0001 8.66683C12.3667 8.66683 12.6806 8.79739 12.9417 9.0585C13.2029 9.31961 13.3334 9.6335 13.3334 10.0002ZM13.3334 6.00016C13.3334 6.36683 13.2029 6.68072 12.9417 6.94183C12.6806 7.20294 12.3667 7.3335 12.0001 7.3335C11.6334 7.3335 11.3195 7.20294 11.0584 6.94183C10.7973 6.68072 10.6667 6.36683 10.6667 6.00016C10.6667 5.6335 10.7973 5.31961 11.0584 5.0585C11.3195 4.79739 11.6334 4.66683 12.0001 4.66683C12.3667 4.66683 12.6806 4.79739 12.9417 5.0585C13.2029 5.31961 13.3334 5.6335 13.3334 6.00016ZM9.33342 10.0002C9.33342 10.3668 9.20286 10.6807 8.94175 10.9418C8.68064 11.2029 8.36675 11.3335 8.00008 11.3335C7.63342 11.3335 7.31953 11.2029 7.05842 10.9418C6.7973 10.6807 6.66675 10.3668 6.66675 10.0002C6.66675 9.6335 6.7973 9.31961 7.05842 9.0585C7.31953 8.79739 7.63342 8.66683 8.00008 8.66683C8.36675 8.66683 8.68064 8.79739 8.94175 9.0585C9.20286 9.31961 9.33342 9.6335 9.33342 10.0002ZM9.33342 6.00016C9.33342 6.36683 9.20286 6.68072 8.94175 6.94183C8.68064 7.20294 8.36675 7.3335 8.00008 7.3335C7.63342 7.3335 7.31953 7.20294 7.05842 6.94183C6.7973 6.68072 6.66675 6.36683 6.66675 6.00016C6.66675 5.6335 6.7973 5.31961 7.05842 5.0585C7.31953 4.79739 7.63342 4.66683 8.00008 4.66683C8.36675 4.66683 8.68064 4.79739 8.94175 5.0585C9.20286 5.31961 9.33342 5.6335 9.33342 6.00016ZM5.33342 10.0002C5.33342 10.3668 5.20286 10.6807 4.94175 10.9418C4.68064 11.2029 4.36675 11.3335 4.00008 11.3335C3.63342 11.3335 3.31953 11.2029 3.05842 10.9418C2.7973 10.6807 2.66675 10.3668 2.66675 10.0002C2.66675 9.6335 2.7973 9.31961 3.05842 9.0585C3.31953 8.79739 3.63342 8.66683 4.00008 8.66683C4.36675 8.66683 4.68064 8.79739 4.94175 9.0585C5.20286 9.31961 5.33342 9.6335 5.33342 10.0002ZM5.33342 6.00016C5.33342 6.36683 5.20286 6.68072 4.94175 6.94183C4.68064 7.20294 4.36675 7.3335 4.00008 7.3335C3.63342 7.3335 3.31953 7.20294 3.05842 6.94183C2.7973 6.68072 2.66675 6.36683 2.66675 6.00016C2.66675 5.6335 2.7973 5.31961 3.05842 5.0585C3.31953 4.79739 3.63342 4.66683 4.00008 4.66683C4.36675 4.66683 4.68064 4.79739 4.94175 5.0585C5.20286 5.31961 5.33342 5.6335 5.33342 6.00016Z" transform="rotate(90 8 8)" fill="#FAF9F6" fill-opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 491 491" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<!-- <path d="M490.16,84.61C490.16,37.912 452.248,0 405.55,0L84.61,0C37.912,0 0,37.912 0,84.61L0,405.55C0,452.248 37.912,490.16 84.61,490.16L405.55,490.16C452.248,490.16 490.16,452.248 490.16,405.55L490.16,84.61Z" style="fill:rgb(255,54,112);"/> -->
|
||||
<path d="M407.48,111.18C335.587,108.103 269.573,152.338 245.08,220C220.587,152.338 154.573,108.103 82.68,111.18C80.285,168.229 107.577,222.632 154.74,254.82C178.908,271.419 193.35,298.951 193.27,328.27L193.27,379.13L296.9,379.13L296.9,328.27C296.816,298.953 311.255,271.42 335.42,254.82C382.596,222.644 409.892,168.233 407.48,111.18Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="24" height="24" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_goose)">
|
||||
<path d="M43.2098 39.2492L39.8118 36.4522C37.9646 34.9318 36.3923 33.1051 35.1631 31.0529C33.4647 28.2172 31.1178 25.8243 28.3164 24.071L26.9482 23.2744C26.4794 22.9485 26.1525 22.4405 26.106 21.8675C26.0762 21.4981 26.1647 21.1678 26.3712 20.8771C27.0837 19.873 30.9617 15.6995 31.6365 15.1411C32.5056 14.4228 33.4739 13.8253 34.3724 13.1412C34.5001 13.0438 34.6283 12.9469 34.7545 12.8481C34.7589 12.8437 34.7653 12.8403 34.7692 12.8363C35.0574 12.6088 35.3295 12.3671 35.5458 12.0803C36.5993 10.8604 36.5783 9.82938 36.5783 9.82938C36.5783 9.82938 36.5793 9.8284 36.5798 9.82791C36.4677 9.5534 36.0645 8.69902 35.2449 8.29825C35.9696 8.28406 36.7872 8.55564 37.1582 8.91873C37.6039 8.21947 37.8922 7.76585 38.3404 7.00347C38.4471 6.82241 38.5038 6.48771 38.2998 6.30029C38.2998 6.30029 38.2988 6.30029 38.2978 6.30029C38.2978 6.30029 38.2978 6.29931 38.2978 6.29833C38.2939 6.29442 38.289 6.29148 38.2851 6.28757C38.2812 6.28365 38.2787 6.27925 38.2743 6.27485C38.2743 6.27485 38.2734 6.27485 38.2724 6.27485C38.2724 6.27485 38.2724 6.27387 38.2724 6.27289C38.2641 6.26457 38.2548 6.25821 38.246 6.25038C38.2406 6.245 38.2362 6.23864 38.2313 6.23276C38.2254 6.22787 38.219 6.22347 38.2137 6.21808C38.2053 6.20977 38.2 6.19949 38.1911 6.19166C38.1911 6.19166 38.1897 6.19117 38.1892 6.19166C38.1892 6.19166 38.1892 6.19068 38.1892 6.1897C38.1853 6.18579 38.1804 6.18285 38.1765 6.17894C38.1726 6.17502 38.1696 6.17013 38.1657 6.16621C38.1657 6.16621 38.1642 6.16572 38.1637 6.16621C38.1637 6.16621 38.1637 6.16524 38.1637 6.16426C37.9763 5.9602 37.6416 6.01697 37.4606 6.12364C36.4692 6.70693 35.5047 7.33475 34.6351 7.88525C34.6351 7.88525 33.6046 7.86372 32.3842 8.91775C32.097 9.13452 31.8552 9.4066 31.6282 9.69433C31.6238 9.69873 31.6199 9.7046 31.6164 9.70901C31.5171 9.83476 31.4202 9.96297 31.3233 10.0912C30.6387 10.9901 30.0417 11.958 29.3234 12.8271C28.7656 13.5023 24.5915 17.3798 23.5874 18.0923C23.2967 18.2988 22.9669 18.3879 22.597 18.3575C22.0245 18.3115 21.5161 17.9842 21.1902 17.5154L20.3935 16.1472C18.6402 13.3448 16.2474 10.9989 13.4117 9.30041C11.3594 8.0712 9.5332 6.49847 8.01234 4.65172L5.2153 1.25377C5.0773 1.08642 4.81306 1.11382 4.71422 1.30662C4.39615 1.92612 3.78888 3.23411 3.32353 4.99327C3.31227 5.03487 3.32206 5.07891 3.34946 5.11218C3.92786 5.81095 5.26717 7.35089 6.8624 8.65351C6.97348 8.74403 6.88246 8.92313 6.74447 8.88496C5.36846 8.51013 4.03062 7.90825 3.02944 7.39542C2.94772 7.35383 2.84887 7.4057 2.83713 7.49672C2.68152 8.74354 2.64139 10.1127 2.80581 11.5562C2.8107 11.6032 2.84055 11.6438 2.8841 11.6629C4.02572 12.1596 5.85143 12.8804 7.76816 13.3389C7.90713 13.3722 7.90273 13.5723 7.76229 13.5992C6.26982 13.8801 4.70688 13.9427 3.43656 13.9261C3.3475 13.9251 3.28193 14.0093 3.30591 14.0949C3.56085 14.9958 3.90632 15.9118 4.3619 16.8342C4.55029 17.2487 4.75532 17.6553 4.97504 18.0532C5.01271 18.1212 5.08562 18.1618 5.16343 18.1584C6.21207 18.107 7.47946 17.9964 8.75369 17.7713C8.97095 17.7331 9.08741 18.0189 8.90391 18.1417C8.03877 18.7201 7.11588 19.2183 6.25758 19.6283C6.14259 19.6836 6.1054 19.8304 6.18125 19.9327C6.69114 20.6236 7.25191 21.2774 7.86114 21.8866C7.86114 21.8866 10.8608 24.9763 10.9425 25.2185C12.6928 23.4261 15.5701 21.4394 18.7474 19.7047C14.4921 23.1672 12.1198 25.7235 10.9258 27.1758L10.0935 28.3439C9.66091 28.9507 9.2856 29.5951 8.97193 30.2709C7.92231 32.5292 6.19201 37.0966 6.19201 37.0966C6.0594 37.4543 6.16755 37.8072 6.39753 38.0371C6.40292 38.0425 6.4083 38.0479 6.41417 38.0528C6.41955 38.0582 6.42445 38.0641 6.42983 38.0694C6.66031 38.2999 7.01263 38.4076 7.37033 38.275C7.37033 38.275 11.9368 36.5447 14.1961 35.495C14.8719 35.1814 15.5168 34.8056 16.1231 34.3735L17.4095 33.457C18.0907 32.9715 19.0234 33.0489 19.615 33.6405L22.5804 36.6058C23.1896 37.2151 23.8433 37.7758 24.5343 38.2857C24.637 38.3611 24.7833 38.3244 24.8386 38.2094C25.2492 37.3516 25.7473 36.4277 26.3252 35.5631C26.4481 35.3796 26.7343 35.4965 26.6957 35.7133C26.4701 36.988 26.3605 38.2554 26.3086 39.3035C26.3047 39.3809 26.3458 39.4543 26.4138 39.4919C26.8112 39.7121 27.2178 39.9172 27.6327 40.1051C28.5551 40.5606 29.4717 40.9066 30.3721 41.1611C30.4577 41.185 30.5419 41.1195 30.5409 41.0304C30.5237 39.7596 30.5864 38.1967 30.8677 36.7047C30.8942 36.5638 31.0943 36.5593 31.1281 36.6988C31.5861 38.616 32.3069 40.4417 32.8041 41.5829C32.8231 41.6264 32.8633 41.6558 32.9107 41.6612C34.3543 41.8256 35.7234 41.7855 36.9703 41.6298C37.0618 41.6186 37.1136 41.5197 37.0715 41.4375C36.5587 40.4364 35.9568 39.0975 35.582 37.7225C35.5443 37.584 35.7229 37.4935 35.8135 37.6046C37.1161 39.1998 38.656 40.5391 39.3548 41.1175C39.3881 41.1449 39.4326 41.1542 39.4737 41.1434C41.2333 40.6786 42.5413 40.0713 43.1604 39.7528C43.3532 39.6539 43.3806 39.3897 43.2132 39.2517L43.2098 39.2492Z" fill="#FF0000"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_goose">
|
||||
<rect width="45" height="45" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_6094_28714)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.66673 0.768651C7.43339 0.856651 0.973392 4.09865 0.886725 4.17198C0.774725 4.26598 0.666725 4.49332 0.648058 4.67465C0.628725 4.86465 0.706058 5.03665 0.863392 5.15198C0.924058 5.19598 2.48539 5.98732 4.33339 6.91065C7.94806 8.71598 7.84473 8.66998 8.15339 8.61598C8.27273 8.59465 9.05472 8.21532 11.6674 6.91065C13.5161 5.98798 15.0667 5.20065 15.1127 5.16198C15.2254 5.06732 15.3334 4.84065 15.3521 4.65865C15.3714 4.46865 15.2941 4.29665 15.1367 4.18132C15.0761 4.13732 13.5127 3.34398 11.6621 2.41865L8.29673 0.735985L8.04873 0.727318C7.85473 0.720651 7.77073 0.729985 7.66673 0.768651ZM10.5734 3.36665C11.9887 4.07398 13.1467 4.65932 13.1467 4.66665C13.1467 4.67398 11.9887 5.25932 10.5734 5.96665L8.00006 7.25332L5.42673 5.96665C4.01139 5.25932 2.85339 4.67398 2.85339 4.66665C2.85339 4.65665 7.95739 2.08932 7.99339 2.08132C7.99673 2.08065 9.15806 2.65865 10.5734 3.36665ZM1.04673 7.36132C0.842059 7.46532 0.676725 7.73532 0.648058 8.01065C0.628725 8.19798 0.707392 8.37065 0.863392 8.48532C0.924058 8.52932 2.48539 9.32065 4.33339 10.244C6.94673 11.5493 7.72739 11.928 7.84673 11.9493C8.15539 12.0033 8.05206 12.0493 11.6667 10.244C13.5147 9.32065 15.0761 8.52932 15.1367 8.48532C15.3807 8.30665 15.4201 8.01865 15.2481 7.68132C15.1027 7.39665 14.8974 7.27798 14.6227 7.31932C14.5354 7.33198 13.5254 7.82398 11.2494 8.96198L8.00006 10.5867L4.74006 8.95732C1.72939 7.45265 1.46806 7.32732 1.32006 7.31598C1.19139 7.30598 1.13739 7.31465 1.04673 7.36132ZM1.04673 10.6947C0.842059 10.7987 0.676725 11.0687 0.648058 11.344C0.628725 11.5313 0.707392 11.704 0.863392 11.8187C0.924058 11.8627 2.48539 12.654 4.33339 13.5773C6.94673 14.8827 7.72739 15.2613 7.84673 15.2826C8.15539 15.3367 8.05206 15.3827 11.6667 13.5773C13.5147 12.654 15.0761 11.8627 15.1367 11.8187C15.3807 11.64 15.4201 11.352 15.2481 11.0147C15.1027 10.73 14.8974 10.6113 14.6227 10.6527C14.5354 10.6653 13.5254 11.1573 11.2494 12.2953L8.00006 13.92L4.74006 12.2907C1.72939 10.786 1.46806 10.6607 1.32006 10.6493C1.19139 10.6393 1.13739 10.648 1.04673 10.6947Z" fill="#FAF9F6"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_6094_28714">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="11" viewBox="0 0 8 11" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.58119 0.0137297C1.32819 0.0472298 1.13069 0.14373 0.970691 0.31123C0.779691 0.51123 0.682691 0.77823 0.705691 1.03673C0.723191 1.22923 0.783191 1.40373 1.14019 2.29773L1.45769 3.09423V3.70573V4.31773L0.920191 4.99023C0.174691 5.92223 0.0946913 6.03923 0.0266913 6.29523C-0.0508087 6.58623 0.0436912 6.92723 0.268191 7.16973C0.394191 7.30623 0.518691 7.37773 0.731191 7.43723C0.883191 7.47973 0.922691 7.48123 2.17219 7.48823L3.45669 7.49473L3.46219 9.08873C3.46769 10.6747 3.46769 10.6832 3.51069 10.7627C3.60019 10.9297 3.70519 10.9812 3.95769 10.9822C4.12369 10.9827 4.15869 10.9762 4.23669 10.9327C4.33519 10.8777 4.40669 10.7857 4.43719 10.6752C4.45019 10.6297 4.45769 10.0282 4.45769 9.04873V7.49473L5.74269 7.48823C6.99269 7.48123 7.03219 7.47973 7.18419 7.43723C7.39719 7.37773 7.52119 7.30623 7.64819 7.16873C7.87269 6.92623 7.96619 6.58673 7.88869 6.29523C7.82069 6.03923 7.74069 5.92223 6.99519 4.99023L6.45769 4.31773V3.70573V3.09423L6.77519 2.29773C7.13219 1.40373 7.19219 1.22923 7.20969 1.03673C7.23419 0.76223 7.12019 0.47423 6.90869 0.27723C6.77469 0.15223 6.67069 0.0937297 6.48769 0.0392298C6.37619 0.00622975 6.20569 0.00322971 4.03769 0.000229715C2.75619 -0.00127029 1.65069 0.00472975 1.58119 0.0137297ZM6.18769 1.01273C6.19469 1.02423 6.04519 1.42223 5.85519 1.89773C5.66569 2.37373 5.49869 2.81223 5.48419 2.87273C5.45069 3.01523 5.44669 4.35873 5.47969 4.51673C5.52319 4.72573 5.60469 4.85023 6.11069 5.48323C6.75119 6.28423 6.87969 6.45323 6.86619 6.47473C6.85169 6.49873 1.06369 6.49873 1.04869 6.47473C1.03619 6.45373 1.19219 6.24973 1.84469 5.43323C2.30969 4.85173 2.39269 4.72273 2.43569 4.51673C2.46869 4.35873 2.46469 3.01523 2.43119 2.87273C2.41669 2.81223 2.24969 2.37373 2.06019 1.89773C1.87019 1.42223 1.72069 1.02423 1.72769 1.01273C1.74469 0.98473 6.17069 0.98473 6.18769 1.01273Z" fill="#121212"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.58119 0.0137297C1.32819 0.0472298 1.13069 0.14373 0.970691 0.31123C0.779691 0.51123 0.682691 0.77823 0.705691 1.03673C0.723191 1.22923 0.783191 1.40373 1.14019 2.29773L1.45769 3.09423V3.70573V4.31773L0.920191 4.99023C0.174691 5.92223 0.0946913 6.03923 0.0266913 6.29523C-0.0508087 6.58623 0.0436912 6.92723 0.268191 7.16973C0.394191 7.30623 0.518691 7.37773 0.731191 7.43723C0.883191 7.47973 0.922691 7.48123 2.17219 7.48823L3.45669 7.49473L3.46219 9.08873C3.46769 10.6747 3.46769 10.6832 3.51069 10.7627C3.60019 10.9297 3.70519 10.9812 3.95769 10.9822C4.12369 10.9827 4.15869 10.9762 4.23669 10.9327C4.33519 10.8777 4.40669 10.7857 4.43719 10.6752C4.45019 10.6297 4.45769 10.0282 4.45769 9.04873V7.49473L5.74269 7.48823C6.99269 7.48123 7.03219 7.47973 7.18419 7.43723C7.39719 7.37773 7.52119 7.30623 7.64819 7.16873C7.87269 6.92623 7.96619 6.58673 7.88869 6.29523C7.82069 6.03923 7.74069 5.92223 6.99519 4.99023L6.45769 4.31773V3.70573V3.09423L6.77519 2.29773C7.13219 1.40373 7.19219 1.22923 7.20969 1.03673C7.23419 0.76223 7.12019 0.47423 6.90869 0.27723C6.77469 0.15223 6.67069 0.0937297 6.48769 0.0392298C6.37619 0.00622975 6.20569 0.00322971 4.03769 0.000229715C2.75619 -0.00127029 1.65069 0.00472975 1.58119 0.0137297ZM6.18769 1.01273C6.19469 1.02423 6.04519 1.42223 5.85519 1.89773C5.66569 2.37373 5.49869 2.81223 5.48419 2.87273C5.45069 3.01523 5.44669 4.35873 5.47969 4.51673C5.52319 4.72573 5.60469 4.85023 6.11069 5.48323C6.75119 6.28423 6.87969 6.45323 6.86619 6.47473C6.85169 6.49873 1.06369 6.49873 1.04869 6.47473C1.03619 6.45373 1.19219 6.24973 1.84469 5.43323C2.30969 4.85173 2.39269 4.72273 2.43569 4.51673C2.46869 4.35873 2.46469 3.01523 2.43119 2.87273C2.41669 2.81223 2.24969 2.37373 2.06019 1.89773C1.87019 1.42223 1.72069 1.02423 1.72769 1.01273C1.74469 0.98473 6.17069 0.98473 6.18769 1.01273Z" fill="#FAF9F6" fill-opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<g clip-path="url(#clip0_6228_17678)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2191 8.48528L13.5761 6.12826L14.0475 6.59966C14.3068 6.85894 14.7311 6.85894 14.9904 6.59966C15.2496 6.34039 15.2496 5.91613 14.9904 5.65685L10.2763 0.942809C10.017 0.683537 9.59277 0.683537 9.3335 0.942809C9.07422 1.20208 9.07422 1.62635 9.3335 1.88562L9.8049 2.35702L7.44788 4.71405C6.66535 5.49658 5.40198 5.49658 4.61945 4.71405L3.67664 5.65685L6.49093 8.47114L3.1911 11.771V12.7138H4.1339L7.43374 9.41395L10.2763 12.2565L11.2191 11.3137C10.4366 10.5312 10.4366 9.26781 11.2191 8.48528Z" fill="#121212"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2191 8.48528L13.5761 6.12826L14.0475 6.59966C14.3068 6.85894 14.7311 6.85894 14.9904 6.59966C15.2496 6.34039 15.2496 5.91613 14.9904 5.65685L10.2763 0.942809C10.017 0.683537 9.59277 0.683537 9.3335 0.942809C9.07422 1.20208 9.07422 1.62635 9.3335 1.88562L9.8049 2.35702L7.44788 4.71405C6.66535 5.49658 5.40198 5.49658 4.61945 4.71405L3.67664 5.65685L6.49093 8.47114L3.1911 11.771V12.7138H4.1339L7.43374 9.41395L10.2763 12.2565L11.2191 11.3137C10.4366 10.5312 10.4366 9.26781 11.2191 8.48528Z" fill="#FAF9F6" fill-opacity="0.4"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_6228_17678">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="7" height="10" viewBox="0 0 7 10" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5 3.5V1H6C6.275 1 6.5 0.775 6.5 0.5C6.5 0.225 6.275 0 6 0H1C0.725 0 0.5 0.225 0.5 0.5C0.5 0.775 0.725 1 1 1H1.5V3.5C1.5 4.33 0.83 5 0 5V6H2.985V9.5L3.485 10L3.985 9.5V6H7V5C6.17 5 5.5 4.33 5.5 3.5Z" fill="#121212"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5 3.5V1H6C6.275 1 6.5 0.775 6.5 0.5C6.5 0.225 6.275 0 6 0H1C0.725 0 0.5 0.225 0.5 0.5C0.5 0.775 0.725 1 1 1H1.5V3.5C1.5 4.33 0.83 5 0 5V6H2.985V9.5L3.485 10L3.985 9.5V6H7V5C6.17 5 5.5 4.33 5.5 3.5Z" fill="#FAF9F6" fill-opacity="0.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 660 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4H10V10H4V4Z" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M14 4H20V10H14V4Z" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M4 14H10V20H4V14Z" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M14 14H16V16H14V14Z" fill="white"/>
|
||||
<path d="M18 14H20V16H18V14Z" fill="white"/>
|
||||
<path d="M14 18H16V20H14V18Z" fill="white"/>
|
||||
<path d="M18 18H20V20H18V18Z" fill="white"/>
|
||||
<path d="M6.5 6.5H7.5V7.5H6.5V6.5Z" stroke="white" stroke-width="1.5"/>
|
||||
<path d="M16.5 6.5H17.5V7.5H16.5V6.5Z" stroke="white" stroke-width="1.5"/>
|
||||
<path d="M6.5 16.5H7.5V17.5H6.5V16.5Z" stroke="white" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 767 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.626 2.04492C15.257 2.23892 14.951 2.67292 14.951 2.99992C14.951 3.33292 15.0561 3.46692 16.3531 4.76992L17.577 5.99992H10.695C3.30605 5.99992 3.61205 5.99192 3.32205 6.19892C3.10005 6.35692 3.02005 6.56792 3.02005 6.99992C3.02005 7.33092 3.03305 7.40292 3.12005 7.55792C3.23005 7.75492 3.41405 7.89792 3.63505 7.95892C3.73005 7.98592 6.18105 7.99992 10.679 7.99992H17.577L16.3531 9.22992C15.548 10.0389 15.099 10.5209 15.04 10.6369C14.863 10.9889 14.952 11.3539 15.299 11.7009C15.646 12.0479 16.0111 12.1369 16.3631 11.9599C16.4841 11.8989 17.1821 11.2339 18.5611 9.86592C19.673 8.76292 20.634 7.82592 20.696 7.78392C20.85 7.68192 21.049 7.23892 21.049 6.99992C21.049 6.74492 20.851 6.32092 20.6761 6.20092C20.601 6.14992 19.6401 5.21292 18.5401 4.11792C17.2321 2.81692 16.482 2.09892 16.372 2.04392C16.152 1.93392 15.835 1.93492 15.626 2.04492ZM7.62005 12.0459C7.52505 12.0949 6.65205 12.9319 5.46005 14.1179C4.36005 15.2129 3.39905 16.1499 3.32405 16.2009C3.14905 16.3209 2.95105 16.7449 2.95105 16.9999C2.95105 17.2389 3.15005 17.6819 3.30405 17.7839C3.36605 17.8259 4.32705 18.7629 5.43905 19.8659C6.81805 21.2339 7.51605 21.8989 7.63705 21.9599C7.98905 22.1369 8.35405 22.0479 8.70105 21.7009C9.04805 21.3539 9.13705 20.9889 8.96005 20.6369C8.90105 20.5209 8.45205 20.0389 7.64705 19.2299L6.42305 17.9999H13.321C17.819 17.9999 20.2701 17.9859 20.365 17.9589C20.5861 17.8979 20.77 17.7549 20.88 17.5579C20.967 17.4019 20.98 17.3319 20.979 16.9999C20.9781 16.5669 20.898 16.3549 20.678 16.1989C20.388 15.9919 20.694 15.9999 13.305 15.9999H6.42305L7.64705 14.7699C8.45205 13.9609 8.90105 13.4789 8.96005 13.3629C9.13705 13.0109 9.04805 12.6459 8.70105 12.2989C8.36105 11.9589 7.96705 11.8669 7.62005 12.0459Z" fill="white" transform="rotate(90 12 12)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.742 21.75l-7.563-11.179 7.056-8.321h-2.456l-5.691 6.714-4.54-6.714H2.359l7.29 10.776L2.25 21.75h2.456l6.035-7.118 4.818 7.118h6.191-.008zM7.739 3.818L18.81 20.182h-2.447L5.29 3.818h2.447z" fill="#FF0000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 324 B |
@@ -3,17 +3,15 @@
|
||||
// Windows).
|
||||
#![allow(clippy::disallowed_types)]
|
||||
|
||||
use cfg_aliases::cfg_aliases;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::{env, fs};
|
||||
|
||||
use anyhow::Result;
|
||||
use galaxy_util::assets::{
|
||||
ASSETS_DIR, ASYNC_ASSETS_DIR, CONPTY_DLL_FILE, DXCOMPILER_DLL_FILE, DXIL_DLL_FILE,
|
||||
OPEN_CONSOLE_EXE_FILE, REMOTE_ASSETS_DIR, WINDOWS_ASSETS_DIR,
|
||||
};
|
||||
use cfg_aliases::cfg_aliases;
|
||||
use galaxy_util::assets::*;
|
||||
use galaxy_util::path::app_target_dir;
|
||||
use sha2::Digest;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{env, fs, process::Command};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -36,13 +34,10 @@ fn main() -> Result<()> {
|
||||
println!("cargo:rustc-link-lib=framework=UserNotifications");
|
||||
build_and_link_sentry();
|
||||
|
||||
println!("cargo:rerun-if-changed=src/platform/mac/objc/app_bundle.h");
|
||||
println!("cargo:rerun-if-changed=src/platform/mac/objc/app_bundle.m");
|
||||
println!("cargo:rerun-if-changed=src/platform/mac/objc/services.h");
|
||||
println!("cargo:rerun-if-changed=src/platform/mac/objc/services.m");
|
||||
|
||||
cc::Build::new()
|
||||
.file("src/platform/mac/objc/app_bundle.m")
|
||||
.file("src/platform/mac/objc/services.m")
|
||||
.compile("warp_objc");
|
||||
|
||||
@@ -209,6 +204,14 @@ fn build_and_link_sentry() {
|
||||
"cargo:rustc-link-search=all={}",
|
||||
swift_library_path.display()
|
||||
);
|
||||
// Embed /usr/lib/swift as LC_RPATH so the deployed standalone CLI binary can
|
||||
// find Swift runtime dylibs on the remote host. On macOS 12.3+, Apple ships
|
||||
// the Swift runtime as an OS-level framework at /usr/lib/swift. Without this,
|
||||
// remote macOS hosts fail with:
|
||||
// dyld: Library not loaded: @rpath/libswiftCore.dylib
|
||||
// Reason: no LC_RPATH's found
|
||||
// See: https://github.com/warpdotdev/warp/issues/12631
|
||||
println!("cargo:rustc-link-arg=-Wl,-rpath,/usr/lib/swift");
|
||||
}
|
||||
|
||||
compile_sentry_objc_lib(&sentry_framework_path);
|
||||
|
||||
@@ -7,7 +7,7 @@ Type=Application
|
||||
Name=WarpOss
|
||||
GenericName=TerminalEmulator
|
||||
|
||||
Exec=warp-oss %U
|
||||
Exec=warp-terminal-oss %U
|
||||
StartupWMClass=dev.warp.WarpOss
|
||||
|
||||
Keywords=shell;prompt;command;commandline;cmd;
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::ai::blocklist::agent_view::{AgentViewController, AgentViewControllerEvent};
|
||||
use crate::ai::blocklist::BlocklistAIHistoryModel;
|
||||
use crate::terminal::model::session::active_session::ActiveSession;
|
||||
use galaxyui::{
|
||||
AppContext, Entity, EntityId, ModelContext, ModelHandle, SingletonEntity, WeakModelHandle,
|
||||
WindowId,
|
||||
};
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::agent_conversations_model::{AgentConversationEntry, AgentConversationEntryId};
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::ai::blocklist::agent_view::{AgentViewController, AgentViewControllerEvent};
|
||||
use crate::ai::blocklist::orchestration_event_streamer::{
|
||||
register_agent_event_consumer, unregister_agent_event_consumer,
|
||||
};
|
||||
use crate::ai::blocklist::BlocklistAIHistoryModel;
|
||||
use crate::terminal::model::session::active_session::ActiveSession;
|
||||
|
||||
/// Contains the handles needed to track an active agent view.
|
||||
struct ActiveAgentViewHandles {
|
||||
controller: WeakModelHandle<AgentViewController>,
|
||||
@@ -44,6 +48,17 @@ pub enum ConversationOrTaskId {
|
||||
TaskId(AmbientAgentTaskId),
|
||||
}
|
||||
|
||||
impl From<AgentConversationEntryId> for ConversationOrTaskId {
|
||||
fn from(id: AgentConversationEntryId) -> Self {
|
||||
match id {
|
||||
AgentConversationEntryId::Conversation(conversation_id) => {
|
||||
ConversationOrTaskId::ConversationId(conversation_id)
|
||||
}
|
||||
AgentConversationEntryId::AmbientRun(task_id) => ConversationOrTaskId::TaskId(task_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConversationOrTaskId {
|
||||
pub fn conversation_id(&self) -> Option<AIConversationId> {
|
||||
match self {
|
||||
@@ -94,6 +109,34 @@ impl ActiveAgentViewsModel {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_focused_conversation_for_terminal(
|
||||
&mut self,
|
||||
terminal_view_id: EntityId,
|
||||
conversation_id: Option<ConversationOrTaskId>,
|
||||
) {
|
||||
for focused_terminal_state in self.focused_terminal_states.values_mut() {
|
||||
if focused_terminal_state.focused_terminal_id == terminal_view_id
|
||||
&& !matches!(
|
||||
focused_terminal_state.active_conversation_id,
|
||||
Some(ConversationOrTaskId::TaskId(_))
|
||||
)
|
||||
{
|
||||
focused_terminal_state.active_conversation_id = conversation_id;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(last_focused_terminal_state) = &mut self.last_focused_terminal_state {
|
||||
if last_focused_terminal_state.focused_terminal_id == terminal_view_id
|
||||
&& !matches!(
|
||||
last_focused_terminal_state.active_conversation_id,
|
||||
Some(ConversationOrTaskId::TaskId(_))
|
||||
)
|
||||
{
|
||||
last_focused_terminal_state.active_conversation_id = conversation_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register an agent view controller to track when the agent view is entered/exited.
|
||||
#[cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
pub fn register_agent_view_controller(
|
||||
@@ -122,7 +165,19 @@ impl ActiveAgentViewsModel {
|
||||
},
|
||||
);
|
||||
|
||||
ctx.subscribe_to_model(controller, move |model, event, ctx| match event {
|
||||
// On pane re-attach the controller's `agent_view_state` is still
|
||||
// `Active` while the unregister path has already torn down the
|
||||
// streamer consumer. Re-register here; the `EnteredAgentView`
|
||||
// subscription only fires on subsequent state transitions.
|
||||
if let Some(conversation_id) = controller
|
||||
.as_ref(ctx)
|
||||
.agent_view_state()
|
||||
.active_conversation_id()
|
||||
{
|
||||
register_agent_event_consumer(conversation_id, terminal_view_id, ctx);
|
||||
}
|
||||
|
||||
ctx.subscribe_to_model(controller, move |model, _, event, ctx| match event {
|
||||
AgentViewControllerEvent::EnteredAgentView {
|
||||
conversation_id, ..
|
||||
} => {
|
||||
@@ -132,37 +187,31 @@ impl ActiveAgentViewsModel {
|
||||
// Update the focused conversation in whichever window owns this terminal view.
|
||||
// We ignore agent view changes if we are focused on an ambient conversation,
|
||||
// as ambient conversation navigation operates at the task level instead of the conversation level.
|
||||
for focused_terminal_state in model.focused_terminal_states.values_mut() {
|
||||
if focused_terminal_state.focused_terminal_id == terminal_view_id
|
||||
&& !matches!(
|
||||
focused_terminal_state.active_conversation_id,
|
||||
Some(ConversationOrTaskId::TaskId(_))
|
||||
)
|
||||
{
|
||||
focused_terminal_state.active_conversation_id = Some(conv_id);
|
||||
}
|
||||
}
|
||||
model.update_focused_conversation_for_terminal(terminal_view_id, Some(conv_id));
|
||||
// Bridge the controller's lifecycle into the streamer's
|
||||
// per-conversation consumer registry.
|
||||
register_agent_event_consumer(*conversation_id, terminal_view_id, ctx);
|
||||
// Emit so subscribers can move this conversation to the Active section.
|
||||
ctx.emit(ActiveAgentViewsEvent::TerminalViewFocused);
|
||||
}
|
||||
AgentViewControllerEvent::ExitedAgentView {
|
||||
conversation_id, ..
|
||||
conversation_id,
|
||||
is_exit_before_new_entrance,
|
||||
..
|
||||
} => {
|
||||
// Skip if this exit is part of an in-place switch — the follow-up
|
||||
// entrance will register the new conversation's consumer.
|
||||
if *is_exit_before_new_entrance {
|
||||
return;
|
||||
}
|
||||
|
||||
model
|
||||
.last_opened_times
|
||||
.remove(&ConversationOrTaskId::ConversationId(*conversation_id));
|
||||
|
||||
// Clear the focused conversation in whichever window owns this terminal view.
|
||||
for state in model.focused_terminal_states.values_mut() {
|
||||
if state.focused_terminal_id == terminal_view_id
|
||||
&& !matches!(
|
||||
state.active_conversation_id,
|
||||
Some(ConversationOrTaskId::TaskId(_))
|
||||
)
|
||||
{
|
||||
state.active_conversation_id = None;
|
||||
}
|
||||
}
|
||||
model.update_focused_conversation_for_terminal(terminal_view_id, None);
|
||||
unregister_agent_event_consumer(*conversation_id, terminal_view_id, ctx);
|
||||
// Emit so subscribers can move this conversation to the Past section.
|
||||
ctx.emit(ActiveAgentViewsEvent::ConversationClosed {
|
||||
conversation_id: *conversation_id,
|
||||
@@ -198,6 +247,9 @@ impl ActiveAgentViewsModel {
|
||||
}
|
||||
|
||||
if let Some(conversation_id) = closed_conversation_id {
|
||||
// The pane-close path bypasses exit_agent_view_internal, so
|
||||
// unregister the streamer consumer here.
|
||||
unregister_agent_event_consumer(conversation_id, terminal_pane_id, ctx);
|
||||
ctx.emit(ActiveAgentViewsEvent::ConversationClosed { conversation_id });
|
||||
}
|
||||
}
|
||||
@@ -252,6 +304,12 @@ impl ActiveAgentViewsModel {
|
||||
.and_then(|state| state.active_conversation_id)
|
||||
}
|
||||
|
||||
pub fn get_focused_terminal_view_id(&self, window_id: WindowId) -> Option<EntityId> {
|
||||
self.focused_terminal_states
|
||||
.get(&window_id)
|
||||
.map(|state| state.focused_terminal_id)
|
||||
}
|
||||
|
||||
/// Get the last focused terminal view id (persisted across non-terminal focus changes).
|
||||
pub fn get_last_focused_terminal_id(&self) -> Option<EntityId> {
|
||||
self.last_focused_terminal_state
|
||||
@@ -259,6 +317,14 @@ impl ActiveAgentViewsModel {
|
||||
.map(|state| state.focused_terminal_id)
|
||||
}
|
||||
|
||||
/// Get the most recent focused conversation or ambient task ID, persisted
|
||||
/// across non-terminal focus changes.
|
||||
pub fn get_last_focused_conversation(&self) -> Option<ConversationOrTaskId> {
|
||||
self.last_focused_terminal_state
|
||||
.as_ref()
|
||||
.and_then(|state| state.active_conversation_id)
|
||||
}
|
||||
|
||||
/// Returns the focused conversation ID if it's a new/empty conversation view.
|
||||
/// Only returns Some if the focused agent view was just created to start a new
|
||||
/// conversation (i.e. has no exchanges yet).
|
||||
@@ -307,6 +373,8 @@ impl ActiveAgentViewsModel {
|
||||
task_id: AmbientAgentTaskId,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.ambient_sessions
|
||||
.retain(|view_id, id| *view_id == terminal_view_id || *id != task_id);
|
||||
let existing = self.ambient_sessions.insert(terminal_view_id, task_id);
|
||||
if existing != Some(task_id) {
|
||||
self.last_opened_times
|
||||
@@ -323,9 +391,11 @@ impl ActiveAgentViewsModel {
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if let Some(task_id) = self.ambient_sessions.remove(&terminal_view_id) {
|
||||
self.last_opened_times
|
||||
.remove(&ConversationOrTaskId::TaskId(task_id));
|
||||
ctx.emit(ActiveAgentViewsEvent::AmbientSessionClosed { task_id });
|
||||
if !self.ambient_sessions.values().any(|id| *id == task_id) {
|
||||
self.last_opened_times
|
||||
.remove(&ConversationOrTaskId::TaskId(task_id));
|
||||
ctx.emit(ActiveAgentViewsEvent::AmbientSessionClosed { task_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +513,23 @@ impl ActiveAgentViewsModel {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_terminal_view_id_for_entry(
|
||||
&self,
|
||||
entry: &AgentConversationEntry,
|
||||
ctx: &AppContext,
|
||||
) -> Option<EntityId> {
|
||||
if let Some(task_id) = entry.identity.ambient_agent_task_id {
|
||||
if let Some(terminal_view_id) = self.get_terminal_view_id_for_ambient_task(task_id) {
|
||||
return Some(terminal_view_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(conversation_id) = entry.identity.local_conversation_id {
|
||||
return self.get_terminal_view_id_for_conversation(conversation_id, ctx);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
/// Get all currently active conversation IDs.
|
||||
/// A conversation is active if it is open and a query has been sent since it was last opened.
|
||||
/// New (empty) conversations and ambient sessions are always considered active when open.
|
||||
|
||||
@@ -12,6 +12,46 @@ fn new_task_id() -> AmbientAgentTaskId {
|
||||
AmbientAgentTaskId::from_str(&uuid::Uuid::new_v4().to_string()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversation_switch_updates_last_focused_terminal_state() {
|
||||
App::test((), |mut app| async move {
|
||||
let model = setup_model(&mut app);
|
||||
let window = WindowId::new();
|
||||
let terminal = EntityId::new();
|
||||
let conversation_1 = AIConversationId::new();
|
||||
let conversation_2 = AIConversationId::new();
|
||||
|
||||
model.update(&mut app, |model, ctx| {
|
||||
model.handle_pane_focus_change(window, Some(terminal), None, ctx);
|
||||
model.update_focused_conversation_for_terminal(
|
||||
terminal,
|
||||
Some(ConversationOrTaskId::ConversationId(conversation_1)),
|
||||
);
|
||||
});
|
||||
model.read(&app, |model, _| {
|
||||
assert_eq!(model.get_last_focused_terminal_id(), Some(terminal));
|
||||
assert_eq!(
|
||||
model.get_last_focused_conversation(),
|
||||
Some(ConversationOrTaskId::ConversationId(conversation_1))
|
||||
);
|
||||
});
|
||||
|
||||
model.update(&mut app, |model, _| {
|
||||
model.update_focused_conversation_for_terminal(
|
||||
terminal,
|
||||
Some(ConversationOrTaskId::ConversationId(conversation_2)),
|
||||
);
|
||||
});
|
||||
model.read(&app, |model, _| {
|
||||
assert_eq!(model.get_last_focused_terminal_id(), Some(terminal));
|
||||
assert_eq!(
|
||||
model.get_last_focused_conversation(),
|
||||
Some(ConversationOrTaskId::ConversationId(conversation_2))
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn per_window_focused_state_is_independent() {
|
||||
App::test((), |mut app| async move {
|
||||
@@ -88,6 +128,10 @@ fn last_focused_terminal_tracks_most_recent_globally() {
|
||||
});
|
||||
model.read(&app, |model, _| {
|
||||
assert_eq!(model.get_last_focused_terminal_id(), Some(terminal_a));
|
||||
assert_eq!(
|
||||
model.get_last_focused_conversation(),
|
||||
Some(ConversationOrTaskId::TaskId(task_a))
|
||||
);
|
||||
});
|
||||
|
||||
model.update(&mut app, |model, ctx| {
|
||||
@@ -95,6 +139,10 @@ fn last_focused_terminal_tracks_most_recent_globally() {
|
||||
});
|
||||
model.read(&app, |model, _| {
|
||||
assert_eq!(model.get_last_focused_terminal_id(), Some(terminal_b));
|
||||
assert_eq!(
|
||||
model.get_last_focused_conversation(),
|
||||
Some(ConversationOrTaskId::TaskId(task_b))
|
||||
);
|
||||
});
|
||||
|
||||
// Clearing window B's focus should NOT clear last_focused (it persists).
|
||||
@@ -103,6 +151,10 @@ fn last_focused_terminal_tracks_most_recent_globally() {
|
||||
});
|
||||
model.read(&app, |model, _| {
|
||||
assert_eq!(model.get_last_focused_terminal_id(), Some(terminal_b));
|
||||
assert_eq!(
|
||||
model.get_last_focused_conversation(),
|
||||
Some(ConversationOrTaskId::TaskId(task_b))
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -144,6 +196,72 @@ fn focus_change_without_task_id_has_no_conversation() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ambient_session_registration_replaces_stale_terminal_for_same_task() {
|
||||
App::test((), |mut app| async move {
|
||||
let model = setup_model(&mut app);
|
||||
let terminal_a = EntityId::new();
|
||||
let terminal_b = EntityId::new();
|
||||
let task = new_task_id();
|
||||
|
||||
model.update(&mut app, |model, ctx| {
|
||||
model.register_ambient_session(terminal_a, task, ctx);
|
||||
model.register_ambient_session(terminal_b, task, ctx);
|
||||
});
|
||||
|
||||
model.read(&app, |model, _| {
|
||||
assert_eq!(
|
||||
model.get_terminal_view_id_for_ambient_task(task),
|
||||
Some(terminal_b)
|
||||
);
|
||||
assert_eq!(model.ambient_sessions.len(), 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ambient_session_unregister_keeps_task_open_until_last_terminal_is_removed() {
|
||||
App::test((), |mut app| async move {
|
||||
let model = setup_model(&mut app);
|
||||
let terminal_a = EntityId::new();
|
||||
let terminal_b = EntityId::new();
|
||||
let task = new_task_id();
|
||||
|
||||
model.update(&mut app, |model, _| {
|
||||
model.ambient_sessions.insert(terminal_a, task);
|
||||
model.ambient_sessions.insert(terminal_b, task);
|
||||
model
|
||||
.last_opened_times
|
||||
.insert(ConversationOrTaskId::TaskId(task), Utc::now());
|
||||
});
|
||||
|
||||
model.update(&mut app, |model, ctx| {
|
||||
model.unregister_ambient_session(terminal_a, ctx);
|
||||
});
|
||||
|
||||
model.read(&app, |model, _| {
|
||||
assert_eq!(
|
||||
model.get_terminal_view_id_for_ambient_task(task),
|
||||
Some(terminal_b)
|
||||
);
|
||||
assert!(model
|
||||
.last_opened_times
|
||||
.contains_key(&ConversationOrTaskId::TaskId(task)));
|
||||
});
|
||||
|
||||
model.update(&mut app, |model, ctx| {
|
||||
model.unregister_ambient_session(terminal_b, ctx);
|
||||
});
|
||||
|
||||
model.read(&app, |model, _| {
|
||||
assert_eq!(model.get_terminal_view_id_for_ambient_task(task), None);
|
||||
assert!(!model
|
||||
.last_opened_times
|
||||
.contains_key(&ConversationOrTaskId::TaskId(task)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_focused_state_for_window_cleans_up() {
|
||||
App::test((), |mut app| async move {
|
||||
|
||||
@@ -3,40 +3,38 @@ mod convert_from;
|
||||
mod convert_to;
|
||||
mod r#impl;
|
||||
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use ai::agent::convert::ConvertToAPITypeError;
|
||||
use ai::api_keys::ApiKeyManager;
|
||||
pub use convert_from::{
|
||||
user_inputs_from_messages, ConversionParams, ConvertAPIMessageToClientOutputMessage,
|
||||
MaybeAIAgentOutputMessage, MessageToAIAgentOutputMessageError,
|
||||
};
|
||||
|
||||
pub use r#impl::generate_multi_agent_output;
|
||||
|
||||
use futures_lite::Stream;
|
||||
use mcp::TemplatableMCPServerInfo;
|
||||
pub use r#impl::generate_multi_agent_output;
|
||||
use serde::Serialize;
|
||||
use galaxy_core::channel::ChannelState;
|
||||
use galaxy_core::execution_mode::AppExecutionMode;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::{
|
||||
ai::{blocklist::SessionContext, llms::LLMId},
|
||||
server::server_api::AIApiError,
|
||||
};
|
||||
use galaxy_core::user_preferences::GetUserPreferences;
|
||||
use galaxyui::{AppContext, EntityId, SingletonEntity as _};
|
||||
|
||||
use super::{AIAgentInput, MCPContext, MCPServer, RequestMetadata, Suggestions};
|
||||
use crate::ai::blocklist::{BlocklistAIPermissions, RequestInput};
|
||||
use crate::ai::mcp::templatable_manager::TemplatableMCPServerInfo;
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::ai::blocklist::{BlocklistAIPermissions, RequestInput, SessionContext};
|
||||
use crate::ai::execution_profiles::profiles::AIExecutionProfilesModel;
|
||||
use crate::ai::execution_profiles::AIExecutionProfileAppExt;
|
||||
use crate::ai::llms::{LLMId, LLMPreferences};
|
||||
use crate::ai::mcp::TemplatableMCPServerManager;
|
||||
use crate::server::server_api::AIApiError;
|
||||
use crate::settings::AISettings;
|
||||
use crate::terminal::safe_mode_settings::get_secret_obfuscation_mode;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use galaxy_core::user_preferences::GetUserPreferences;
|
||||
use galaxyui::{AppContext, EntityId, SingletonEntity as _};
|
||||
|
||||
/// Unique, server-generated conversation-scoped token to be roundtripped to the API when sending
|
||||
/// requests that follow-up within a given conversation.
|
||||
@@ -109,13 +107,21 @@ pub struct RequestParams {
|
||||
pub computer_use_model: LLMId,
|
||||
pub is_memory_enabled: bool,
|
||||
pub warp_drive_context_enabled: bool,
|
||||
pub context_window_limit: Option<u32>,
|
||||
pub mcp_context: Option<MCPContext>,
|
||||
pub planning_enabled: bool,
|
||||
should_redact_secrets: bool,
|
||||
|
||||
/// User-provided API keys for AI providers (BYO API Key).
|
||||
pub api_keys: Option<warp_multi_agent_api::request::settings::ApiKeys>,
|
||||
pub allow_use_of_warp_credits_with_byok: bool,
|
||||
/// User-provided custom model providers (BYOK endpoints).
|
||||
pub custom_model_providers:
|
||||
Option<warp_multi_agent_api::request::settings::CustomModelProviders>,
|
||||
/// User-defined custom model routers referenced by the current selection. Mirrors
|
||||
/// `custom_model_providers`: the selected model's `config_key` indexes into this
|
||||
/// registry. `None` when no custom router is selected.
|
||||
pub custom_model_routers: Option<warp_multi_agent_api::request::settings::CustomModelRouters>,
|
||||
pub allow_use_of_warp_credits: bool,
|
||||
pub autonomy_level: warp_multi_agent_api::AutonomyLevel,
|
||||
pub isolation_level: warp_multi_agent_api::IsolationLevel,
|
||||
pub web_search_enabled: bool,
|
||||
@@ -170,6 +176,44 @@ pub struct ConversationData {
|
||||
}
|
||||
|
||||
impl RequestParams {
|
||||
#[cfg(test)]
|
||||
pub fn new_for_test() -> Self {
|
||||
Self {
|
||||
input: vec![],
|
||||
conversation_token: None,
|
||||
forked_from_conversation_token: None,
|
||||
ambient_agent_task_id: None,
|
||||
tasks: vec![],
|
||||
existing_suggestions: None,
|
||||
metadata: None,
|
||||
session_context: SessionContext::new_for_test(),
|
||||
model: LLMId::from("test-model"),
|
||||
coding_model: LLMId::from("test-model"),
|
||||
cli_agent_model: LLMId::from("test-model"),
|
||||
computer_use_model: LLMId::from("test-model"),
|
||||
is_memory_enabled: false,
|
||||
warp_drive_context_enabled: false,
|
||||
context_window_limit: None,
|
||||
mcp_context: None,
|
||||
planning_enabled: false,
|
||||
should_redact_secrets: false,
|
||||
api_keys: None,
|
||||
custom_model_providers: None,
|
||||
custom_model_routers: None,
|
||||
allow_use_of_warp_credits: false,
|
||||
autonomy_level: Default::default(),
|
||||
isolation_level: Default::default(),
|
||||
web_search_enabled: false,
|
||||
computer_use_enabled: false,
|
||||
ask_user_question_enabled: false,
|
||||
research_agent_enabled: false,
|
||||
orchestration_enabled: false,
|
||||
supported_tools_override: None,
|
||||
parent_agent_id: None,
|
||||
agent_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
terminal_view_id: Option<EntityId>,
|
||||
session_context: SessionContext,
|
||||
@@ -251,12 +295,31 @@ impl RequestParams {
|
||||
let should_redact_secrets = get_secret_obfuscation_mode(app).should_redact_secret();
|
||||
|
||||
let user_workspaces = UserWorkspaces::as_ref(app);
|
||||
let api_keys = ApiKeyManager::as_ref(app).api_keys_for_request(
|
||||
user_workspaces.is_byo_api_key_enabled(),
|
||||
user_workspaces.is_bedrock_enabled(app),
|
||||
let api_key_manager = ApiKeyManager::as_ref(app);
|
||||
let is_byo_enabled = user_workspaces.is_byo_api_key_enabled(app);
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
let geap_binding = crate::ai::geap_credentials::current_geap_policy(app).mint_binding();
|
||||
#[cfg(target_family = "wasm")]
|
||||
let geap_binding: Option<::ai::api_keys::GeapMintBinding> = None;
|
||||
let api_keys = api_key_manager.api_keys_for_request(
|
||||
is_byo_enabled,
|
||||
user_workspaces.is_aws_bedrock_credentials_enabled(app),
|
||||
geap_binding,
|
||||
);
|
||||
let allow_use_of_warp_credits_with_byok =
|
||||
*AISettings::as_ref(app).can_use_warp_credits_with_byok;
|
||||
let is_custom_inference_enabled = user_workspaces.is_custom_inference_enabled(app);
|
||||
let custom_model_providers = FeatureFlag::CustomInferenceEndpoints
|
||||
.is_enabled()
|
||||
.then(|| {
|
||||
api_key_manager.custom_model_providers_for_request(is_custom_inference_enabled)
|
||||
})
|
||||
.flatten();
|
||||
let custom_model_routers = FeatureFlag::CustomModelRouters.is_enabled().then(|| {
|
||||
LLMPreferences::as_ref(app).custom_model_routers_for_request(
|
||||
&request_input.model_id,
|
||||
&request_input.coding_model_id,
|
||||
)
|
||||
});
|
||||
let allow_use_of_warp_credits = *AISettings::as_ref(app).can_use_warp_credits_for_fallback;
|
||||
|
||||
let app_execution_mode = AppExecutionMode::as_ref(app);
|
||||
let autonomy_level = if app_execution_mode.is_autonomous() {
|
||||
@@ -292,11 +355,25 @@ impl RequestParams {
|
||||
!= crate::ai::execution_profiles::AskUserQuestionPermission::Never;
|
||||
|
||||
let orchestration_enabled = ai_settings.is_orchestration_enabled(app)
|
||||
&& BlocklistAIPermissions::as_ref(app)
|
||||
.get_run_agents_setting(app, terminal_view_id)
|
||||
.is_enabled()
|
||||
&& session_context
|
||||
.session_type()
|
||||
.as_ref()
|
||||
.is_none_or(|t| matches!(t, crate::terminal::model::session::SessionType::Local));
|
||||
|
||||
// Reconcile the persisted override against the active base model's
|
||||
// current `LLMContextWindow` instead of trusting whatever was stored
|
||||
// last. If the active model isn't configurable or has been removed
|
||||
// server-side, drop the override; otherwise clamp it to the model's
|
||||
// current `[min, max]` range. This closes the window between an
|
||||
// in-flight model metadata refresh and the next request.
|
||||
let context_window_limit = AIExecutionProfilesModel::as_ref(app)
|
||||
.active_profile(terminal_view_id, app)
|
||||
.data()
|
||||
.context_window_limit_for_request(app);
|
||||
|
||||
Self {
|
||||
input: request_input.all_inputs().cloned().collect(),
|
||||
conversation_token: conversation.server_conversation_token,
|
||||
@@ -304,6 +381,7 @@ impl RequestParams {
|
||||
ambient_agent_task_id: conversation.ambient_agent_task_id,
|
||||
tasks: conversation.tasks,
|
||||
existing_suggestions: conversation.existing_suggestions,
|
||||
context_window_limit,
|
||||
metadata,
|
||||
session_context,
|
||||
model: request_input.model_id.clone(),
|
||||
@@ -316,7 +394,9 @@ impl RequestParams {
|
||||
planning_enabled: true,
|
||||
should_redact_secrets,
|
||||
api_keys,
|
||||
allow_use_of_warp_credits_with_byok,
|
||||
custom_model_providers,
|
||||
custom_model_routers,
|
||||
allow_use_of_warp_credits,
|
||||
autonomy_level,
|
||||
isolation_level,
|
||||
web_search_enabled,
|
||||
|
||||
@@ -4,12 +4,28 @@
|
||||
//! If some UI state is stored in the client, it needs to also be represented in the proto tasks somehow so it can be restored.
|
||||
//! Some conversions may be lossy if it's not important to recover that UI state.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ai::agent::action_result::{
|
||||
AskUserQuestionAnswerItem, AskUserQuestionResult, FetchConversationResult, ReadSkillResult,
|
||||
RequestComputerUseResult, SendMessageToAgentResult, StartAgentResult, StartAgentVersion,
|
||||
UseComputerResult,
|
||||
};
|
||||
use ai::skills::{ParsedSkill, SkillPathOrigin};
|
||||
use chrono::{DateTime, Local, TimeZone};
|
||||
use persistence::model::AgentConversationData;
|
||||
use galaxy_core::command::ExitCode;
|
||||
use warp_multi_agent_api as api;
|
||||
use warp_multi_agent_api::ask_user_question_result::answer_item::Answer as AskUserQuestionAnswer;
|
||||
|
||||
use crate::ai::agent::api::convert_from::{
|
||||
convert_user_query_mode, ConversionParams, ConvertAPIMessageToClientOutputMessage,
|
||||
MaybeAIAgentOutputMessage,
|
||||
};
|
||||
use crate::ai::agent::conversation::update_todo_list_from_todo_op;
|
||||
use crate::ai::agent::conversation::{AIConversation, AIConversationId};
|
||||
use crate::ai::agent::conversation::{
|
||||
update_todo_list_from_todo_op, AIConversation, AIConversationId, ServerAIConversationMetadata,
|
||||
};
|
||||
use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::todos::AIAgentTodoList;
|
||||
use crate::ai::agent::{
|
||||
@@ -24,7 +40,7 @@ use crate::ai::agent::{
|
||||
RequestFileEditsResult, SearchCodebaseFailureReason, SearchCodebaseResult, ServerOutputId,
|
||||
Shared, ShellCommandCompletedTrigger, ShellCommandError, SuggestNewConversationResult,
|
||||
SuggestPromptResult, TransferShellCommandControlToUserResult, UpdatedFileContext,
|
||||
UploadArtifactResult, WriteToLongRunningShellCommandResult,
|
||||
UploadArtifactResult, UserQueryMode, WriteToLongRunningShellCommandResult,
|
||||
};
|
||||
use crate::ai::block_context::BlockContext;
|
||||
use crate::ai::document::ai_document_model::{AIDocumentId, AIDocumentVersion};
|
||||
@@ -32,22 +48,6 @@ use crate::ai::llms::LLMId;
|
||||
use crate::ai_assistant::execution_context::{WarpAiExecutionContext, WarpAiOsContext};
|
||||
use crate::terminal::model::block::BlockId;
|
||||
use crate::terminal::model::terminal_model::BlockIndex;
|
||||
use ai::agent::action_result::{
|
||||
AskUserQuestionAnswerItem, AskUserQuestionResult, FetchConversationResult, ReadSkillResult,
|
||||
RequestComputerUseResult, SendMessageToAgentResult, StartAgentResult, StartAgentVersion,
|
||||
UseComputerResult,
|
||||
};
|
||||
use ai::skills::ParsedSkill;
|
||||
use chrono::{DateTime, Local, TimeZone};
|
||||
use galaxy_core::command::ExitCode;
|
||||
use persistence::model::AgentConversationData;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use warp_multi_agent_api as api;
|
||||
use warp_multi_agent_api::ask_user_question_result::answer_item::Answer as AskUserQuestionAnswer;
|
||||
|
||||
use crate::ai::agent::conversation::ServerAIConversationMetadata;
|
||||
use crate::ai::agent::UserQueryMode;
|
||||
|
||||
/// How to restore a conversation from the cloud.
|
||||
pub enum RestorationMode {
|
||||
@@ -81,12 +81,14 @@ pub fn convert_conversation_data_to_ai_conversation(
|
||||
artifacts_json: None,
|
||||
parent_agent_id: None,
|
||||
agent_name: None,
|
||||
orchestration_harness_type: None,
|
||||
parent_conversation_id: None,
|
||||
is_remote_child: false,
|
||||
root_task_is_optimistic: None,
|
||||
run_id: None,
|
||||
autoexecute_override: None,
|
||||
last_event_sequence: None,
|
||||
progressive_summary: None,
|
||||
messages_summarized_up_to: 0,
|
||||
pinned: false,
|
||||
},
|
||||
RestorationMode::Continue => AgentConversationData {
|
||||
server_conversation_token: Some(
|
||||
@@ -98,16 +100,16 @@ pub fn convert_conversation_data_to_ai_conversation(
|
||||
artifacts_json: serde_json::to_string(&metadata.artifacts).ok(),
|
||||
parent_agent_id: None,
|
||||
agent_name: None,
|
||||
orchestration_harness_type: None,
|
||||
parent_conversation_id: None,
|
||||
// TODO: Populate run_id from server metadata once it is exposed
|
||||
// in ServerAIConversationMetadata. For cloud conversations that
|
||||
// were spawned via the server API, the run_id is created at task
|
||||
// dispatch time; adding it here would avoid a round-trip to StreamInit.
|
||||
run_id: None,
|
||||
is_remote_child: false,
|
||||
root_task_is_optimistic: None,
|
||||
run_id: metadata
|
||||
.ambient_agent_task_id
|
||||
.map(|task_id| task_id.to_string()),
|
||||
autoexecute_override: None,
|
||||
last_event_sequence: None,
|
||||
progressive_summary: None,
|
||||
messages_summarized_up_to: 0,
|
||||
pinned: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -239,7 +241,8 @@ pub(crate) fn convert_input_context(context: Option<&api::InputContext>) -> Arc<
|
||||
};
|
||||
|
||||
// Convert binary data to base64
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use base64::engine::general_purpose;
|
||||
use base64::Engine;
|
||||
let data = general_purpose::STANDARD.encode(&image.data);
|
||||
|
||||
result.push(AIAgentContext::Image(ImageContext {
|
||||
@@ -436,7 +439,10 @@ impl ConvertToExchanges for &api::Task {
|
||||
api::message::system_query::Type::ResumeConversation(_)
|
||||
| api::message::system_query::Type::GeneratePassiveSuggestions(_)
|
||||
// TODO: Implement this for real. ZB adding this to bump proto version for unrelated API changes.
|
||||
| api::message::system_query::Type::SummarizeConversation(_)=> false,
|
||||
| api::message::system_query::Type::SummarizeConversation(_)
|
||||
// HandoffRehydration is injected by the server for agent-only
|
||||
// context; the client must never render it as user input.
|
||||
| api::message::system_query::Type::HandoffRehydration(_) => false,
|
||||
}
|
||||
}
|
||||
api::message::Message::ToolCallResult(tool_call_result) => {
|
||||
@@ -462,7 +468,10 @@ impl ConvertToExchanges for &api::Task {
|
||||
}
|
||||
api::message::Message::InvokeSkill(invoke_skill) => {
|
||||
if let Some(api_skill) = invoke_skill.skill.clone() {
|
||||
if let Ok(parsed_skill) = ParsedSkill::try_from(api_skill) {
|
||||
if let Ok(parsed_skill) = ParsedSkill::try_from_api_with_origin(
|
||||
api_skill,
|
||||
&SkillPathOrigin::RestoredDisplayOnly,
|
||||
) {
|
||||
let user_query = invoke_skill
|
||||
.user_query
|
||||
.clone()
|
||||
@@ -513,7 +522,8 @@ impl ConvertToExchanges for &api::Task {
|
||||
| api::message::Message::DebugOutput(_)
|
||||
| api::message::Message::ArtifactEvent(_)
|
||||
| api::message::Message::MessagesReceivedFromAgents(_)
|
||||
| api::message::Message::ModelUsed(_) => false,
|
||||
| api::message::Message::ModelUsed(_)
|
||||
| api::message::Message::OrchestrationConfigSnapshot(_) => false,
|
||||
};
|
||||
|
||||
if !added_message_as_exchange_input {
|
||||
@@ -524,6 +534,7 @@ impl ConvertToExchanges for &api::Task {
|
||||
// TODO(alokedesai): Support persistence for the code review state.
|
||||
active_code_review: None,
|
||||
task_id: &TaskId::new(api_message.task_id.clone()),
|
||||
skill_path_origin: &SkillPathOrigin::Unavailable,
|
||||
})
|
||||
{
|
||||
current_outputs.push(output_msg);
|
||||
@@ -581,6 +592,14 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
command: result.command.clone(),
|
||||
output: finished.output.clone(),
|
||||
exit_code: ExitCode::from(finished.exit_code),
|
||||
start_ts: finished
|
||||
.start_ts
|
||||
.as_ref()
|
||||
.map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
completed_ts: finished
|
||||
.finish_ts
|
||||
.as_ref()
|
||||
.map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
}
|
||||
}
|
||||
Some(api::run_shell_command_result::Result::LongRunningCommandSnapshot(
|
||||
@@ -627,6 +646,8 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
block_id: finished.command_id.clone().into(),
|
||||
output: finished.output.clone(),
|
||||
exit_code: ExitCode::from(finished.exit_code),
|
||||
start_ts: finished.start_ts.as_ref().map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
completed_ts: finished.finish_ts.as_ref().map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
},
|
||||
Some(api::write_to_long_running_shell_command_result::Result::Error(api::ShellCommandError{
|
||||
r#type: Some(api::shell_command_error::Type::CommandNotFound(()))
|
||||
@@ -1217,6 +1238,14 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
block_id: finished.command_id.clone().into(),
|
||||
output: finished.output.clone(),
|
||||
exit_code: ExitCode::from(finished.exit_code),
|
||||
start_ts: finished
|
||||
.start_ts
|
||||
.as_ref()
|
||||
.map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
completed_ts: finished
|
||||
.finish_ts
|
||||
.as_ref()
|
||||
.map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
}
|
||||
}
|
||||
Some(
|
||||
@@ -1267,6 +1296,8 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
block_id: finished.command_id.clone().into(),
|
||||
output: finished.output.clone(),
|
||||
exit_code: ExitCode::from(finished.exit_code),
|
||||
start_ts: finished.start_ts.as_ref().map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
completed_ts: finished.finish_ts.as_ref().map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
},
|
||||
Some(api::transfer_shell_command_control_to_user_result::Result::Error(
|
||||
api::ShellCommandError {
|
||||
@@ -1315,34 +1346,52 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
})
|
||||
}
|
||||
Some(ToolCallResultType::UseComputer(result)) => {
|
||||
let use_computer_result = match &result.result {
|
||||
Some(api::use_computer_result::Result::Success(success)) => {
|
||||
let screenshot = success.screenshot.as_ref().map(|s| {
|
||||
// The original dimensions are not preserved through the API, so we use
|
||||
// the current dimensions for both.
|
||||
computer_use::Screenshot {
|
||||
width: s.width as usize,
|
||||
height: s.height as usize,
|
||||
original_width: s.width as usize,
|
||||
original_height: s.height as usize,
|
||||
data: s.data.clone(),
|
||||
mime_type: s.mime_type.clone().into(),
|
||||
}
|
||||
});
|
||||
let cursor_position = success
|
||||
.cursor_position
|
||||
.as_ref()
|
||||
.map(|c| computer_use::Vector2I::new(c.x, c.y));
|
||||
UseComputerResult::Success(computer_use::ActionResult {
|
||||
screenshot,
|
||||
cursor_position,
|
||||
})
|
||||
}
|
||||
Some(api::use_computer_result::Result::Error(error)) => {
|
||||
UseComputerResult::Error(error.message.clone())
|
||||
}
|
||||
None => UseComputerResult::Cancelled,
|
||||
};
|
||||
let use_computer_result =
|
||||
match &result.result {
|
||||
Some(api::use_computer_result::Result::Success(success)) => {
|
||||
let screenshot = success.screenshot.as_ref().map(|s| {
|
||||
// The original dimensions are not preserved through the API, so we use
|
||||
// the current dimensions for both.
|
||||
computer_use::Screenshot {
|
||||
width: s.width as usize,
|
||||
height: s.height as usize,
|
||||
original_width: s.width as usize,
|
||||
original_height: s.height as usize,
|
||||
data: s.data.clone(),
|
||||
mime_type: s.mime_type.clone().into(),
|
||||
}
|
||||
});
|
||||
let cursor_position = success
|
||||
.cursor_position
|
||||
.as_ref()
|
||||
.map(|c| computer_use::Vector2I::new(c.x, c.y));
|
||||
let windows = success
|
||||
.windows
|
||||
.iter()
|
||||
.map(convert_api_window_info)
|
||||
.collect();
|
||||
// A present captured-window message indicates a window screenshot was taken.
|
||||
// The window id is an opaque string on the wire; on macOS it is a CGWindowID,
|
||||
// so parse it back to a u32, defaulting to 0 when it is not parseable.
|
||||
let captured_window = success.captured_window.as_ref().map(|c| {
|
||||
computer_use::CapturedWindow {
|
||||
window_id: c.window_id.parse().unwrap_or(0),
|
||||
width_px: c.width_px,
|
||||
height_px: c.height_px,
|
||||
}
|
||||
});
|
||||
UseComputerResult::Success(computer_use::ActionResult {
|
||||
screenshot,
|
||||
cursor_position,
|
||||
windows,
|
||||
captured_window,
|
||||
})
|
||||
}
|
||||
Some(api::use_computer_result::Result::Error(error)) => {
|
||||
UseComputerResult::Error(error.message.clone())
|
||||
}
|
||||
None => UseComputerResult::Cancelled,
|
||||
};
|
||||
|
||||
Some(AIAgentInput::ActionResult {
|
||||
result: AIAgentActionResult {
|
||||
@@ -1361,6 +1410,7 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
api::request_computer_use_result::Approved {
|
||||
screen_dimensions: Some(screen_dimensions),
|
||||
initial_screenshot: Some(initial_screenshot),
|
||||
windows,
|
||||
..
|
||||
},
|
||||
Some(platform),
|
||||
@@ -1374,6 +1424,7 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
mime_type: initial_screenshot.mime_type.clone().into(),
|
||||
},
|
||||
platform,
|
||||
windows: windows.iter().map(convert_api_window_info).collect(),
|
||||
},
|
||||
_ => RequestComputerUseResult::Error(
|
||||
"Missing screen dimensions, initial screenshot, or valid platform"
|
||||
@@ -1544,6 +1595,76 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
context,
|
||||
})
|
||||
}
|
||||
Some(ToolCallResultType::RunAgentsResult(result)) => {
|
||||
use ai::agent::action_result::{
|
||||
RunAgentsAgentOutcome, RunAgentsAgentOutcomeKind, RunAgentsLaunchedExecutionMode,
|
||||
RunAgentsResult,
|
||||
};
|
||||
let run_agents_result = match &result.outcome {
|
||||
Some(api::run_agents_result::Outcome::Launched(launched)) => {
|
||||
let execution_mode = match &launched.resolved_execution_mode {
|
||||
Some(api::run_agents_result::launched::ResolvedExecutionMode::Remote(
|
||||
remote,
|
||||
)) => RunAgentsLaunchedExecutionMode::Remote {
|
||||
environment_id: remote.environment_id.clone(),
|
||||
worker_host: remote.worker_host.clone(),
|
||||
computer_use_enabled: remote.computer_use_enabled,
|
||||
},
|
||||
Some(api::run_agents_result::launched::ResolvedExecutionMode::Local(_))
|
||||
| None => RunAgentsLaunchedExecutionMode::Local,
|
||||
};
|
||||
let agents = launched
|
||||
.agents
|
||||
.iter()
|
||||
.map(|outcome| RunAgentsAgentOutcome {
|
||||
name: outcome.name.clone(),
|
||||
kind: match &outcome.result {
|
||||
Some(api::run_agents_result::agent_outcome::Result::Launched(
|
||||
launched_agent,
|
||||
)) => RunAgentsAgentOutcomeKind::Launched {
|
||||
agent_id: launched_agent.agent_id.clone(),
|
||||
},
|
||||
Some(api::run_agents_result::agent_outcome::Result::Failed(
|
||||
failed,
|
||||
)) => RunAgentsAgentOutcomeKind::Failed {
|
||||
error: failed.error.clone(),
|
||||
},
|
||||
None => RunAgentsAgentOutcomeKind::Failed {
|
||||
error: String::new(),
|
||||
},
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
RunAgentsResult::Launched {
|
||||
model_id: launched.resolved_model_id.clone(),
|
||||
harness_type:
|
||||
crate::ai::agent::api::convert_from::convert_run_agents_harness(
|
||||
launched.resolved_harness.as_ref(),
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
execution_mode,
|
||||
agents,
|
||||
}
|
||||
}
|
||||
Some(api::run_agents_result::Outcome::Denied(denied)) => RunAgentsResult::Denied {
|
||||
reason: denied.reason.clone(),
|
||||
},
|
||||
Some(api::run_agents_result::Outcome::Failure(failure)) => {
|
||||
RunAgentsResult::Failure {
|
||||
error: failure.error.clone(),
|
||||
}
|
||||
}
|
||||
None => RunAgentsResult::Cancelled,
|
||||
};
|
||||
Some(AIAgentInput::ActionResult {
|
||||
result: AIAgentActionResult {
|
||||
id: tool_call_id.into(),
|
||||
task_id: task_id.clone(),
|
||||
result: AIAgentActionResultType::RunAgents(run_agents_result),
|
||||
},
|
||||
context,
|
||||
})
|
||||
}
|
||||
// Deprecated/unused result types or absent result.
|
||||
Some(ToolCallResultType::SuggestCreatePlan(..))
|
||||
| Some(ToolCallResultType::SuggestPlan(..))
|
||||
@@ -1551,6 +1672,7 @@ pub(crate) fn convert_tool_call_result_to_input(
|
||||
log::warn!("No result present for tool call ID: {tool_call_id}");
|
||||
None
|
||||
}
|
||||
Some(ToolCallResultType::WaitForEvents(_)) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1678,8 +1800,14 @@ fn create_cancelled_result_for_tool_call(
|
||||
ToolType::SendMessageToAgent(_) => {
|
||||
AIAgentActionResultType::SendMessageToAgent(SendMessageToAgentResult::Cancelled)
|
||||
}
|
||||
ToolType::RunAgents(_) => {
|
||||
AIAgentActionResultType::RunAgents(ai::agent::action_result::RunAgentsResult::Cancelled)
|
||||
}
|
||||
// These tools are deprecated.
|
||||
ToolType::SuggestCreatePlan(_) | ToolType::SuggestPlan(_) => return None,
|
||||
ToolType::WaitForEvents(_) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(AIAgentInput::ActionResult {
|
||||
@@ -1777,6 +1905,10 @@ fn create_exchange_from_messages(
|
||||
model_id: model.model_id.clone().into(),
|
||||
display_name: model.model_display_name.clone(),
|
||||
is_fallback: model.is_fallback,
|
||||
prompt_cache_expires_at: model
|
||||
.prompt_cache_expires_at
|
||||
.as_ref()
|
||||
.map(|ts| proto_timestamp_to_local_datetime(ts.seconds, ts.nanos)),
|
||||
}),
|
||||
request_cost: None,
|
||||
};
|
||||
@@ -1879,7 +2011,8 @@ where
|
||||
| api::message::Message::DebugOutput(_)
|
||||
| api::message::Message::ArtifactEvent(_)
|
||||
| api::message::Message::InvokeSkill(_)
|
||||
| api::message::Message::ModelUsed(_) => {
|
||||
| api::message::Message::ModelUsed(_)
|
||||
| api::message::Message::OrchestrationConfigSnapshot(_) => {
|
||||
message.timestamp.as_ref().map(|timestamp| {
|
||||
proto_timestamp_to_local_datetime(timestamp.seconds, timestamp.nanos)
|
||||
})
|
||||
@@ -1985,7 +2118,7 @@ fn convert_passive_suggestion_result_to_input(
|
||||
context,
|
||||
})
|
||||
}
|
||||
fn proto_timestamp_to_local_datetime(seconds: i64, nanos: i32) -> DateTime<Local> {
|
||||
pub(crate) fn proto_timestamp_to_local_datetime(seconds: i64, nanos: i32) -> DateTime<Local> {
|
||||
let nanos = if nanos < 0 { 0 } else { nanos as u32 };
|
||||
|
||||
Local
|
||||
@@ -2014,6 +2147,19 @@ fn convert_api_platform(platform: i32) -> Option<computer_use::Platform> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconstructs the internal computer_use window record from the API `WindowInfo` message.
|
||||
fn convert_api_window_info(window: &api::WindowInfo) -> computer_use::WindowInfo {
|
||||
computer_use::WindowInfo {
|
||||
// The window id arrives as an opaque string; on macOS it is a CGWindowID (u32). Default to
|
||||
// 0 when it is not parseable.
|
||||
window_id: window.window_id.parse().unwrap_or(0),
|
||||
pid: window.pid,
|
||||
app_name: window.app_name.clone(),
|
||||
title: window.title.clone(),
|
||||
layer: window.layer,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "convert_conversation_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,7 +1,56 @@
|
||||
use crate::ai::agent::api::convert_conversation::*;
|
||||
use crate::ai::agent::{AIAgentInput, UserQueryMode};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::Utc;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::agent::api::convert_conversation::*;
|
||||
use crate::ai::agent::api::ServerConversationToken;
|
||||
use crate::ai::agent::conversation::{
|
||||
AIAgentHarness, AIConversationId, ServerAIConversationMetadata,
|
||||
};
|
||||
use crate::ai::agent::{AIAgentInput, UserQueryMode};
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::cloud_object::{Revision, ServerMetadata, ServerPermissions};
|
||||
use crate::persistence::model::ConversationUsageMetadata;
|
||||
use crate::server::ids::ServerId;
|
||||
|
||||
fn test_server_metadata(
|
||||
server_token: &str,
|
||||
ambient_agent_task_id: Option<AmbientAgentTaskId>,
|
||||
) -> ServerAIConversationMetadata {
|
||||
ServerAIConversationMetadata {
|
||||
title: "test conversation".to_string(),
|
||||
working_directory: None,
|
||||
harness: AIAgentHarness::Oz,
|
||||
usage: ConversationUsageMetadata {
|
||||
was_summarized: false,
|
||||
context_window_usage: 0.0,
|
||||
credits_spent: 0.0,
|
||||
platform_credits_spent: 0.0,
|
||||
credits_spent_for_last_block: None,
|
||||
token_usage: vec![],
|
||||
tool_usage_metadata: Default::default(),
|
||||
context_window_segments: Vec::new(),
|
||||
},
|
||||
metadata: ServerMetadata {
|
||||
uid: ServerId::default(),
|
||||
revision: Revision::now(),
|
||||
metadata_last_updated_ts: Utc::now().into(),
|
||||
trashed_ts: None,
|
||||
folder_id: None,
|
||||
is_welcome_object: false,
|
||||
creator_uid: None,
|
||||
last_editor_uid: None,
|
||||
current_editor_uid: None,
|
||||
},
|
||||
permissions: ServerPermissions::mock_personal(),
|
||||
creator: None,
|
||||
ambient_agent_task_id,
|
||||
server_conversation_token: ServerConversationToken::new(server_token.to_string()),
|
||||
artifacts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn test_skill() -> api::Skill {
|
||||
api::Skill {
|
||||
descriptor: Some(api::SkillDescriptor {
|
||||
@@ -25,6 +74,40 @@ fn test_skill() -> api::Skill {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_convert_conversation_data_to_ai_conversation_sets_restored_run_id() {
|
||||
let conversation_id = AIConversationId::new();
|
||||
let ambient_agent_task_id: AmbientAgentTaskId =
|
||||
"550e8400-e29b-41d4-a716-446655440000".parse().unwrap();
|
||||
let conversation_data = api::ConversationData {
|
||||
tasks: vec![api::Task {
|
||||
id: "root".to_string(),
|
||||
messages: vec![],
|
||||
dependencies: None,
|
||||
description: String::new(),
|
||||
summary: String::new(),
|
||||
server_data: String::new(),
|
||||
}],
|
||||
ordered_message_ids: vec![],
|
||||
};
|
||||
|
||||
let conversation = convert_conversation_data_to_ai_conversation(
|
||||
conversation_id,
|
||||
&conversation_data,
|
||||
test_server_metadata("server-token", Some(ambient_agent_task_id)),
|
||||
RestorationMode::Continue,
|
||||
)
|
||||
.expect("conversation should restore");
|
||||
|
||||
assert_eq!(conversation.id(), conversation_id);
|
||||
assert_eq!(conversation.task_id(), Some(ambient_agent_task_id));
|
||||
assert_eq!(
|
||||
conversation.run_id(),
|
||||
Some(ambient_agent_task_id.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_tool_call_result_to_input_transfer_control_snapshot() {
|
||||
let task_id = crate::ai::agent::task::TaskId::new("task".to_string());
|
||||
@@ -277,6 +360,7 @@ fn test_into_exchanges_basic() {
|
||||
// Create minimal test data
|
||||
let messages = vec![
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -292,6 +376,7 @@ fn test_into_exchanges_basic() {
|
||||
timestamp: None,
|
||||
},
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -305,6 +390,7 @@ fn test_into_exchanges_basic() {
|
||||
timestamp: None,
|
||||
},
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_msg2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -320,6 +406,7 @@ fn test_into_exchanges_basic() {
|
||||
timestamp: None,
|
||||
},
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_msg2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -333,6 +420,7 @@ fn test_into_exchanges_basic() {
|
||||
timestamp: None,
|
||||
},
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_msg3".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -348,6 +436,7 @@ fn test_into_exchanges_basic() {
|
||||
timestamp: None,
|
||||
},
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_msg3".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -392,6 +481,7 @@ fn test_invoke_skill_arguments_round_trip() {
|
||||
let query = "arg1 arg2".to_string();
|
||||
let messages = vec![
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "invoke_skill_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -412,6 +502,7 @@ fn test_invoke_skill_arguments_round_trip() {
|
||||
timestamp: None,
|
||||
},
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -448,7 +539,7 @@ fn test_invoke_skill_arguments_round_trip() {
|
||||
Some("arg1 arg2")
|
||||
);
|
||||
assert_eq!(
|
||||
exchanges[0].input[0].user_query().as_deref(),
|
||||
exchanges[0].input[0].display_query().as_deref(),
|
||||
Some("/test-skill arg1 arg2")
|
||||
);
|
||||
}
|
||||
@@ -459,6 +550,7 @@ fn test_invoke_skill_arguments_round_trip() {
|
||||
#[test]
|
||||
fn test_invoke_skill_missing_user_query_maps_to_none() {
|
||||
let messages = vec![api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "invoke_skill_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -492,7 +584,7 @@ fn test_invoke_skill_missing_user_query_maps_to_none() {
|
||||
assert_eq!(skill.name, "test-skill");
|
||||
assert_eq!(user_query, &None);
|
||||
assert_eq!(
|
||||
exchanges[0].input[0].user_query().as_deref(),
|
||||
exchanges[0].input[0].display_query().as_deref(),
|
||||
Some("/test-skill")
|
||||
);
|
||||
}
|
||||
@@ -505,6 +597,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
let messages = vec![
|
||||
// User query
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_query".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -521,6 +614,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Agent response
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_response".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -535,6 +629,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Tool call 1
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "tool_call_1".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -558,6 +653,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Tool call 2
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "tool_call_2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -581,6 +677,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Tool call 3
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "tool_call_3".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -604,6 +701,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Tool call result - cancelled (call_2)
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "result_cancelled".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -620,6 +718,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Tool call result - success (call_1)
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "result_success_1".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -639,6 +738,8 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
command_id: "command_1".to_string(),
|
||||
output: "1".to_string(),
|
||||
exit_code: 0,
|
||||
start_ts: None,
|
||||
finish_ts: None,
|
||||
},
|
||||
)),
|
||||
},
|
||||
@@ -650,6 +751,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Tool call result - success (call_3)
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "result_success_3".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -669,6 +771,8 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
command_id: "command_2".to_string(),
|
||||
output: "3".to_string(),
|
||||
exit_code: 0,
|
||||
start_ts: None,
|
||||
finish_ts: None,
|
||||
},
|
||||
)),
|
||||
},
|
||||
@@ -680,6 +784,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Final agent response
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "final_response".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -694,6 +799,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Follow-up user query
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "followup_query".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -710,6 +816,7 @@ fn test_into_exchanges_with_tool_calls_and_cancellation() {
|
||||
},
|
||||
// Final agent response
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "final_response2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -811,6 +918,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
let messages = vec![
|
||||
// User query asking for code changes
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_query".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -827,6 +935,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// Agent response
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_response".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -841,6 +950,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// File diff tool call
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "diff_call".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -862,6 +972,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// User cancels the diff
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "diff_cancelled".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -878,6 +989,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// User provides feedback
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_feedback".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -894,6 +1006,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// Agent response
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_response_2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -908,6 +1021,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// Second file diff tool call
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "diff_call_2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -929,6 +1043,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// User accepts the diff
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "diff_accepted".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -956,6 +1071,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// Final agent response
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "final_response".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -970,6 +1086,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// Follow-up user query
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "followup".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -986,6 +1103,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
},
|
||||
// Final agent response
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "final_response_2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1083,6 +1201,7 @@ fn test_into_exchanges_with_code_diffs() {
|
||||
fn test_user_query_mode_conversion() {
|
||||
// Test conversion with Plan mode
|
||||
let messages = vec![api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1129,6 +1248,7 @@ fn test_user_query_mode_conversion() {
|
||||
|
||||
// Test conversion with Normal mode (no type set)
|
||||
let messages_normal = vec![api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1173,6 +1293,7 @@ fn test_user_query_mode_conversion() {
|
||||
|
||||
// Test conversion with no mode field (should default to Normal)
|
||||
let messages_default = vec![api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1226,6 +1347,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
let messages = vec![
|
||||
// Message 0: Server message (should be ignored or handled gracefully)
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "2512077c-0ede-46b0-8f69-230c8792df07".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "78e236b8-84a2-45df-876e-ebfb86ceafc4".to_string(),
|
||||
@@ -1243,6 +1365,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
},
|
||||
// Message 1: User query with request_id 78e236b8
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "4d6c450d-3d54-446f-974c-5c414e6083e9".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "78e236b8-84a2-45df-876e-ebfb86ceafc4".to_string(),
|
||||
@@ -1259,6 +1382,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
},
|
||||
// Message 2: Agent output with same request_id
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "10210d1a-5298-45ef-90ba-df6367805080".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "78e236b8-84a2-45df-876e-ebfb86ceafc4".to_string(),
|
||||
@@ -1273,6 +1397,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
},
|
||||
// Message 3: Tool call with same request_id
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "936c7c86-eb4a-4edf-97c0-22f5c61b35a6".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "78e236b8-84a2-45df-876e-ebfb86ceafc4".to_string(),
|
||||
@@ -1296,6 +1421,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
},
|
||||
// Message 4: Tool call result with NEW request_id 59a3947f (starts new exchange)
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "cbebf5fb-4dd8-4aef-be45-bb916eff552c".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "59a3947f-fc7e-413a-96b5-baecd7e406dc".to_string(),
|
||||
@@ -1330,6 +1456,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
},
|
||||
// Message 5: Agent output with same request_id
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "7a89857d-fa33-4d45-88e3-5fa9cbce3f20".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "59a3947f-fc7e-413a-96b5-baecd7e406dc".to_string(),
|
||||
@@ -1344,6 +1471,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
},
|
||||
// Message 6: Write to long running command with NEW request_id 9f85acb2 (starts new exchange)
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "dac6d336-9fcb-4e34-bc2b-b06e70f52ec5".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "9f85acb2-0b1f-41b1-a0de-3623e131758a".to_string(),
|
||||
@@ -1374,6 +1502,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
},
|
||||
// Message 7: Final tool call result with same request_id
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "ad319d66-fac0-4169-8bf1-e6004aca1619".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "9f85acb2-0b1f-41b1-a0de-3623e131758a".to_string(),
|
||||
@@ -1395,6 +1524,8 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
command_id: "cmd1".to_string(),
|
||||
output: "Done".to_string(),
|
||||
exit_code: 0,
|
||||
start_ts: None,
|
||||
finish_ts: None,
|
||||
},
|
||||
)),
|
||||
},
|
||||
@@ -1404,6 +1535,7 @@ fn test_exchanges_grouped_by_request_id() {
|
||||
},
|
||||
// Message 8: Final agent output with same request_id
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "f15f8a59-2e9c-416e-b216-83b3bd52d6be".to_string(),
|
||||
task_id: "d02463e1-2429-48de-ac8f-552df4acc4d0".to_string(),
|
||||
request_id: "9f85acb2-0b1f-41b1-a0de-3623e131758a".to_string(),
|
||||
@@ -1492,6 +1624,7 @@ fn test_multiple_create_documents_get_default_version() {
|
||||
let messages = vec![
|
||||
// User query
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1508,6 +1641,7 @@ fn test_multiple_create_documents_get_default_version() {
|
||||
},
|
||||
// Agent output
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_text".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1522,6 +1656,7 @@ fn test_multiple_create_documents_get_default_version() {
|
||||
},
|
||||
// First CreateDocuments tool call
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "tool_call_create_a".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1544,6 +1679,7 @@ fn test_multiple_create_documents_get_default_version() {
|
||||
},
|
||||
// First CreateDocuments result
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "result_create_a".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1572,6 +1708,7 @@ fn test_multiple_create_documents_get_default_version() {
|
||||
},
|
||||
// Agent output before second plan
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_text_2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1586,6 +1723,7 @@ fn test_multiple_create_documents_get_default_version() {
|
||||
},
|
||||
// Second CreateDocuments tool call
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "tool_call_create_b".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1608,6 +1746,7 @@ fn test_multiple_create_documents_get_default_version() {
|
||||
},
|
||||
// Second CreateDocuments result
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "result_create_b".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1694,7 +1833,6 @@ fn test_multiple_create_documents_get_default_version() {
|
||||
#[test]
|
||||
fn test_create_then_edit_then_create_version_tracking() {
|
||||
use crate::ai::agent::{AIAgentActionResultType, CreateDocumentsResult, EditDocumentsResult};
|
||||
use crate::ai::document::ai_document_model::AIDocumentVersion;
|
||||
|
||||
let doc_id_a = uuid::Uuid::new_v4().to_string();
|
||||
let doc_id_b = uuid::Uuid::new_v4().to_string();
|
||||
@@ -1702,6 +1840,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
let messages = vec![
|
||||
// User query
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "user_msg".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1718,6 +1857,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Agent output
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_text".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1732,6 +1872,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Create doc A tool call
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "tool_call_create_a".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1754,6 +1895,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Create doc A result
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "result_create_a".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1782,6 +1924,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Agent output before edit
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_text_2".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1796,6 +1939,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Edit doc A tool call
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "tool_call_edit_a".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1817,6 +1961,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Edit doc A result
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "result_edit_a".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1845,6 +1990,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Agent output before second create
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "agent_text_3".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1859,6 +2005,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Create doc B tool call
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "tool_call_create_b".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1881,6 +2028,7 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
},
|
||||
// Create doc B result
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "result_create_b".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
@@ -1979,3 +2127,74 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
"Created doc B should have default version (v1), independent of doc A"
|
||||
);
|
||||
}
|
||||
|
||||
/// Verify that a `SystemQuery::HandoffRehydration` message does not produce
|
||||
/// a displayed input when restoring a conversation. It must be treated as
|
||||
/// hidden, so the exchange should have zero user-visible inputs.
|
||||
#[test]
|
||||
fn test_handoff_rehydration_system_query_is_hidden() {
|
||||
let messages = vec![
|
||||
// HandoffRehydration system query – should be hidden
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "msg_handoff".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
citations: vec![],
|
||||
message: Some(api::message::Message::SystemQuery(
|
||||
api::message::SystemQuery {
|
||||
r#type: Some(api::message::system_query::Type::HandoffRehydration(
|
||||
api::message::HandoffRehydration {
|
||||
instructions: "restore handoff state".to_string(),
|
||||
},
|
||||
)),
|
||||
context: None,
|
||||
},
|
||||
)),
|
||||
request_id: "req1".to_string(),
|
||||
timestamp: None,
|
||||
},
|
||||
// Agent output that follows the hidden system query
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "msg_output".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
server_message_data: "".to_string(),
|
||||
citations: vec![],
|
||||
message: Some(api::message::Message::AgentOutput(
|
||||
api::message::AgentOutput {
|
||||
text: "I have restored the handoff state.".to_string(),
|
||||
},
|
||||
)),
|
||||
request_id: "req1".to_string(),
|
||||
timestamp: None,
|
||||
},
|
||||
];
|
||||
|
||||
let task = api::Task {
|
||||
id: "task1".to_string(),
|
||||
messages,
|
||||
dependencies: None,
|
||||
description: "".to_string(),
|
||||
summary: "".to_string(),
|
||||
server_data: "".to_string(),
|
||||
};
|
||||
|
||||
let exchanges = task.into_exchanges();
|
||||
assert_eq!(exchanges.len(), 1, "Should produce exactly one exchange");
|
||||
|
||||
let exchange = &exchanges[0];
|
||||
// The HandoffRehydration should NOT appear as input
|
||||
assert!(
|
||||
exchange.input.is_empty(),
|
||||
"HandoffRehydration must not produce a displayed input, got: {:?}",
|
||||
exchange.input
|
||||
);
|
||||
|
||||
// The agent output should still be present
|
||||
let output = exchange.output_status.output().expect("should have output");
|
||||
assert!(
|
||||
!output.get().messages.is_empty(),
|
||||
"Agent output should still be rendered"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,34 +2,33 @@
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use ai::agent::action::{LifecycleEventType as StartAgentLifecycleEventType, ReadSkillRequest};
|
||||
use ai::agent::action_result::StartAgentVersion;
|
||||
use ai::agent::convert::ToolToAIAgentActionError;
|
||||
use ai::agent::UnknownCitationTypeError;
|
||||
use ai::skills::{
|
||||
skill_reference_from_api_skill_ref, skill_reference_from_read_skill_ref, SkillPathOrigin,
|
||||
};
|
||||
use api::ask_user_question::question::QuestionType;
|
||||
use galaxy_core::channel::ChannelState;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::agent::api::convert_conversation::{
|
||||
convert_input_context, convert_tool_call_result_to_input,
|
||||
};
|
||||
use crate::ai::agent::comment::CodeReview;
|
||||
use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::todos::AIAgentTodoList;
|
||||
use crate::ai::agent::util::parse_markdown_into_text_and_code_sections;
|
||||
use crate::ai::agent::{
|
||||
util::parse_markdown_into_text_and_code_sections, AIAgentAction, AIAgentActionType,
|
||||
AIAgentCitation, AIAgentInput, AIAgentOutputMessage, AIAgentText, AIAgentTodo,
|
||||
ArtifactCreatedData, MessageId, StartAgentExecutionMode, SuggestedAgentModeWorkflow,
|
||||
SuggestedRule, Suggestions, TodoOperation,
|
||||
};
|
||||
use crate::ai::agent::{
|
||||
CloneRepositoryURL, SubagentCall, SubagentType, SummarizationType, WebFetchStatus,
|
||||
WebSearchStatus,
|
||||
AIAgentAction, AIAgentActionType, AIAgentAttachment, AIAgentCitation, AIAgentInput,
|
||||
AIAgentOutputMessage, AIAgentText, AIAgentTodo, ArtifactCreatedData, CloneRepositoryURL,
|
||||
MessageId, RunAgentsAgentRunConfig, RunAgentsExecutionMode, RunAgentsRequest,
|
||||
StartAgentExecutionMode, SubagentCall, SubagentType, SuggestedAgentModeWorkflow, SuggestedRule,
|
||||
Suggestions, SummarizationType, TodoOperation, UserQueryMode, WebFetchStatus, WebSearchStatus,
|
||||
};
|
||||
use crate::ai::artifact_download::sanitized_basename;
|
||||
use crate::ai::document::ai_document_model::{AIDocumentId, AIDocumentVersion};
|
||||
use ai::agent::action::LifecycleEventType as StartAgentLifecycleEventType;
|
||||
use ai::agent::action_result::StartAgentVersion;
|
||||
use ai::agent::convert::ToolToAIAgentActionError;
|
||||
use ai::agent::UnknownCitationTypeError;
|
||||
use ai::skills::SkillReference;
|
||||
use api::ask_user_question::question::QuestionType;
|
||||
use galaxy_core::channel::ChannelState;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::agent::{AIAgentAttachment, UserQueryMode};
|
||||
|
||||
impl TryFrom<api::Attachment> for AIAgentAttachment {
|
||||
type Error = anyhow::Error;
|
||||
@@ -53,6 +52,18 @@ impl TryFrom<api::Attachment> for AIAgentAttachment {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_read_skill(
|
||||
read_skill: api::message::tool_call::ReadSkill,
|
||||
skill_path_origin: &SkillPathOrigin,
|
||||
) -> Result<AIAgentActionType, ToolToAIAgentActionError> {
|
||||
let Some(reference) = read_skill.skill_reference else {
|
||||
return Err(ToolToAIAgentActionError::MissingSkillReference);
|
||||
};
|
||||
let skill = skill_reference_from_read_skill_ref(reference, skill_path_origin)
|
||||
.map_err(|_| ToolToAIAgentActionError::MissingSkillReference)?;
|
||||
Ok(AIAgentActionType::ReadSkill(ReadSkillRequest { skill }))
|
||||
}
|
||||
|
||||
/// Converts proto UserQueryMode to the internal UserQueryMode type
|
||||
pub(crate) fn convert_user_query_mode(mode: Option<&api::UserQueryMode>) -> UserQueryMode {
|
||||
let Some(mode) = mode else {
|
||||
@@ -81,6 +92,22 @@ fn convert_start_agent_v2_harness_type(
|
||||
.filter(|harness_type| !harness_type.trim().is_empty())
|
||||
}
|
||||
|
||||
/// Maps the proto `Harness` oneof to a client-side string identifier
|
||||
/// (e.g. "oz", "claude"). Returns `None` for an unset variant.
|
||||
pub(crate) fn convert_run_agents_harness(harness: Option<&api::Harness>) -> Option<String> {
|
||||
let variant = harness?.variant.as_ref()?;
|
||||
Some(
|
||||
match variant {
|
||||
api::harness::Variant::Oz(_) => "oz",
|
||||
api::harness::Variant::ClaudeCode(_) => "claude",
|
||||
api::harness::Variant::OpenCode(_) => "opencode",
|
||||
api::harness::Variant::Gemini(_) => "gemini",
|
||||
api::harness::Variant::Codex(_) => "codex",
|
||||
}
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
fn convert_start_agent_execution_mode(
|
||||
execution_mode: Option<api::start_agent::ExecutionMode>,
|
||||
) -> StartAgentExecutionMode {
|
||||
@@ -94,8 +121,62 @@ fn convert_start_agent_execution_mode(
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_run_agents_execution_mode(
|
||||
execution_mode: Option<api::run_agents::ExecutionMode>,
|
||||
) -> RunAgentsExecutionMode {
|
||||
match execution_mode {
|
||||
Some(api::run_agents::ExecutionMode::Remote(remote)) => RunAgentsExecutionMode::Remote {
|
||||
environment_id: remote.environment_id,
|
||||
worker_host: remote.worker_host,
|
||||
computer_use_enabled: remote.computer_use_enabled,
|
||||
},
|
||||
Some(api::run_agents::ExecutionMode::Local(_)) | None => RunAgentsExecutionMode::Local,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_run_agents(
|
||||
run_agents: api::RunAgents,
|
||||
skill_path_origin: &SkillPathOrigin,
|
||||
) -> AIAgentActionType {
|
||||
let api::RunAgents {
|
||||
summary,
|
||||
base_prompt,
|
||||
skills,
|
||||
model_id,
|
||||
harness,
|
||||
agent_run_configs,
|
||||
execution_mode,
|
||||
plan_id,
|
||||
} = run_agents;
|
||||
AIAgentActionType::RunAgents(RunAgentsRequest {
|
||||
summary,
|
||||
base_prompt,
|
||||
skills: skills
|
||||
.into_iter()
|
||||
.filter_map(|skill| skill_reference_from_api_skill_ref(skill, skill_path_origin))
|
||||
.collect(),
|
||||
model_id,
|
||||
harness_type: convert_run_agents_harness(harness.as_ref()).unwrap_or_default(),
|
||||
execution_mode: convert_run_agents_execution_mode(execution_mode),
|
||||
agent_run_configs: agent_run_configs
|
||||
.into_iter()
|
||||
.map(|config| RunAgentsAgentRunConfig {
|
||||
name: config.name,
|
||||
prompt: config.prompt,
|
||||
title: config.title,
|
||||
})
|
||||
.collect(),
|
||||
plan_id,
|
||||
// Auth secret is a client-side dispatch concern populated by the
|
||||
// confirmation card from `CloudAgentSettings.last_selected_auth_secret`
|
||||
// before Accept. The proto does not carry it.
|
||||
harness_auth_secret_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_start_agent_v2_execution_mode(
|
||||
execution_mode: Option<api::start_agent_v2::ExecutionMode>,
|
||||
skill_path_origin: &SkillPathOrigin,
|
||||
) -> StartAgentExecutionMode {
|
||||
match execution_mode.and_then(|execution_mode| execution_mode.mode) {
|
||||
Some(api::start_agent_v2::execution_mode::Mode::Remote(remote)) => {
|
||||
@@ -104,7 +185,9 @@ fn convert_start_agent_v2_execution_mode(
|
||||
skill_references: remote
|
||||
.skills
|
||||
.into_iter()
|
||||
.filter_map(convert_skill_reference)
|
||||
.filter_map(|skill| {
|
||||
skill_reference_from_api_skill_ref(skill, skill_path_origin)
|
||||
})
|
||||
.collect(),
|
||||
model_id: remote.model_id,
|
||||
computer_use_enabled: remote.computer_use_enabled,
|
||||
@@ -112,6 +195,9 @@ fn convert_start_agent_v2_execution_mode(
|
||||
harness_type: convert_start_agent_v2_harness_type(remote.harness)
|
||||
.unwrap_or_default(),
|
||||
title: remote.title,
|
||||
// Auth secret is plumbed client-side via `RunAgentsRequest`;
|
||||
// StartAgentV2 from the server never carries it.
|
||||
auth_secret_name: None,
|
||||
}
|
||||
}
|
||||
Some(api::start_agent_v2::execution_mode::Mode::Local(local)) => {
|
||||
@@ -123,16 +209,6 @@ fn convert_start_agent_v2_execution_mode(
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_skill_reference(skill_ref: api::SkillRef) -> Option<SkillReference> {
|
||||
match skill_ref.skill_reference {
|
||||
Some(api::skill_ref::SkillReference::Path(path)) => Some(SkillReference::Path(path.into())),
|
||||
Some(api::skill_ref::SkillReference::BundledSkillId(id)) => {
|
||||
Some(SkillReference::BundledSkillId(id))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unexpected errors when trying to convert an [`api::Message`] to an [`AIAgentOutputMessage`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MessageToAIAgentOutputMessageError {
|
||||
@@ -167,6 +243,7 @@ pub struct ConversionParams<'a> {
|
||||
pub task_id: &'a TaskId,
|
||||
pub current_todo_list: Option<&'a AIAgentTodoList>,
|
||||
pub active_code_review: Option<&'a CodeReview>,
|
||||
pub skill_path_origin: &'a SkillPathOrigin,
|
||||
}
|
||||
|
||||
/// Trait for converting an [`api::Message`] to an [`AIAgentOutputMessage`].
|
||||
@@ -569,7 +646,11 @@ impl ConvertAPIMessageToClientOutputMessage for api::Message {
|
||||
| api::message::Message::CodeReview(_)
|
||||
| api::message::Message::ServerEvent(_)
|
||||
| api::message::Message::InvokeSkill(_)
|
||||
| api::message::Message::PassiveSuggestionResult(_) => {
|
||||
| api::message::Message::PassiveSuggestionResult(_)
|
||||
// Stage 2 plan-card config snapshot: hydrated separately by the
|
||||
// plan card's `AIDocumentModel` subscription, not via the
|
||||
// exchange/output stream. No client output message representation.
|
||||
| api::message::Message::OrchestrationConfigSnapshot(_) => {
|
||||
Ok(MaybeAIAgentOutputMessage::NoClientRepresentation)
|
||||
}
|
||||
}
|
||||
@@ -600,7 +681,7 @@ trait ConvertAPIToolCallToAIAgentAction {
|
||||
) -> Result<MaybeAIAgentAction, ToolToAIAgentActionError>;
|
||||
}
|
||||
|
||||
/// Trys to convert an [`api::message::ToolCall`] to an [`AIAgentAction`].
|
||||
/// Tries to convert an [`api::message::ToolCall`] to an [`AIAgentAction`].
|
||||
///
|
||||
/// A [`Result::Error`] indicates an unexpected problem, while [`Ok(None)`]
|
||||
/// indicates a tool call that we aren't expected to parse.
|
||||
@@ -700,6 +781,7 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
|
||||
create_standard_action(request_computer_use.into())
|
||||
}
|
||||
api::message::tool_call::Tool::Subagent(subagent) => {
|
||||
use api::message::tool_call::subagent::conversation_search_metadata::Target;
|
||||
use api::message::tool_call::subagent::Metadata;
|
||||
let subagent_type = match subagent.metadata {
|
||||
Some(Metadata::Cli(_)) => SubagentType::Cli,
|
||||
@@ -713,14 +795,23 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
|
||||
} else {
|
||||
Some(cs_meta.query)
|
||||
};
|
||||
let conversation_id = if cs_meta.conversation_id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(cs_meta.conversation_id)
|
||||
let (conversation_id, agent_run_id) = match cs_meta.target {
|
||||
Some(Target::ConversationId(conversation_id))
|
||||
if !conversation_id.is_empty() =>
|
||||
{
|
||||
(Some(conversation_id), None)
|
||||
}
|
||||
Some(Target::AgentRunId(agent_run_id)) if !agent_run_id.is_empty() => {
|
||||
(None, Some(agent_run_id))
|
||||
}
|
||||
Some(Target::ConversationId(_))
|
||||
| Some(Target::AgentRunId(_))
|
||||
| None => (None, None),
|
||||
};
|
||||
SubagentType::ConversationSearch {
|
||||
query,
|
||||
conversation_id,
|
||||
agent_run_id,
|
||||
}
|
||||
}
|
||||
Some(Metadata::WarpDocumentationSearch(_)) => {
|
||||
@@ -757,6 +848,7 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
|
||||
prompt: start_agent.prompt,
|
||||
execution_mode: convert_start_agent_v2_execution_mode(
|
||||
start_agent.execution_mode,
|
||||
params.skill_path_origin,
|
||||
),
|
||||
lifecycle_subscription: start_agent.lifecycle_subscription.map(
|
||||
|subscription| {
|
||||
@@ -769,6 +861,9 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
|
||||
),
|
||||
})
|
||||
}
|
||||
api::message::tool_call::Tool::RunAgents(orchestrate) => {
|
||||
create_standard_action(convert_run_agents(orchestrate, params.skill_path_origin))
|
||||
}
|
||||
api::message::tool_call::Tool::SendMessageToAgent(send_message) => {
|
||||
create_standard_action(AIAgentActionType::SendMessageToAgent {
|
||||
addresses: send_message.addresses,
|
||||
@@ -780,7 +875,7 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
|
||||
create_standard_action(insert_review_comments.into())
|
||||
}
|
||||
api::message::tool_call::Tool::ReadSkill(read_skill) => {
|
||||
create_standard_action(read_skill.try_into()?)
|
||||
create_standard_action(convert_read_skill(read_skill, params.skill_path_origin)?)
|
||||
}
|
||||
api::message::tool_call::Tool::FetchConversation(fetch_conversation) => {
|
||||
create_standard_action(fetch_conversation.into())
|
||||
@@ -798,6 +893,12 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall {
|
||||
api::message::tool_call::Tool::Server(_) => {
|
||||
Ok(MaybeAIAgentAction::NoClientRepresentation)
|
||||
}
|
||||
api::message::tool_call::Tool::WaitForEvents(payload) => {
|
||||
create_standard_action(AIAgentActionType::WaitForEvents {
|
||||
tool_call_id: self.tool_call_id.clone(),
|
||||
idle_timeout_seconds: payload.idle_timeout_seconds,
|
||||
})
|
||||
}
|
||||
_ => Err(ToolToAIAgentActionError::UnexpectedTool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use ai::agent::action::AskUserQuestionType;
|
||||
use ai::skills::{SkillPathOrigin, SkillReference};
|
||||
use warp_multi_agent_api as api;
|
||||
use warp_util::local_or_remote_path::LocalOrRemotePath;
|
||||
|
||||
use super::{
|
||||
convert_api_question, ConversionParams, ConvertAPIMessageToClientOutputMessage,
|
||||
MaybeAIAgentOutputMessage,
|
||||
@@ -6,9 +13,6 @@ use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::{
|
||||
AIAgentActionType, AIAgentOutputMessageType, LifecycleEventType, StartAgentExecutionMode,
|
||||
};
|
||||
use ai::agent::action::AskUserQuestionType;
|
||||
use ai::skills::SkillReference;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
fn start_agent_tool_call_message(
|
||||
name: &str,
|
||||
@@ -17,6 +21,7 @@ fn start_agent_tool_call_message(
|
||||
lifecycle_subscription_event_types: Option<Vec<i32>>,
|
||||
) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "message-id".to_string(),
|
||||
task_id: "task-id".to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -63,6 +68,7 @@ fn start_agent_v2_tool_call_message(
|
||||
lifecycle_subscription_event_types: Option<Vec<i32>>,
|
||||
) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "message-id".to_string(),
|
||||
task_id: "task-id".to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -87,6 +93,7 @@ fn start_agent_v2_tool_call_message(
|
||||
|
||||
fn upload_artifact_tool_call_message(path: &str, description: &str) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "message-id".to_string(),
|
||||
task_id: "task-id".to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -140,6 +147,7 @@ fn remote_start_agent_v2_execution_mode(
|
||||
|
||||
fn file_artifact_created_message(filepath: &str, description: &str) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "message-id".to_string(),
|
||||
task_id: "task-id".to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -298,6 +306,7 @@ fn converts_start_agent_tool_call_to_action_with_prompt() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -327,6 +336,7 @@ fn converts_local_start_agent_v2_without_harness_type_to_defaults() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -354,6 +364,7 @@ fn converts_upload_artifact_tool_call_to_action() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -377,6 +388,7 @@ fn converts_file_artifact_created_message_with_filename() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -407,6 +419,7 @@ fn converts_start_agent_tool_calls_with_different_prompt_lengths() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("partial conversion should succeed");
|
||||
let updated_output = updated_message
|
||||
@@ -414,6 +427,7 @@ fn converts_start_agent_tool_calls_with_different_prompt_lengths() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("updated conversion should succeed");
|
||||
|
||||
@@ -442,6 +456,7 @@ fn converts_start_agent_with_explicit_empty_lifecycle_subscription() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -471,6 +486,7 @@ fn converts_start_agent_with_cancelled_and_blocked_lifecycle_subscription() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -503,6 +519,7 @@ fn converts_remote_start_agent_with_environment_id() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -520,6 +537,7 @@ fn converts_remote_start_agent_with_environment_id() {
|
||||
worker_host: String::new(),
|
||||
harness_type: String::new(),
|
||||
title: String::new(),
|
||||
auth_secret_name: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(lifecycle_subscription, None);
|
||||
@@ -540,6 +558,7 @@ fn converts_remote_start_agent_v2_with_skill_references() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -552,7 +571,7 @@ fn converts_remote_start_agent_v2_with_skill_references() {
|
||||
StartAgentExecutionMode::Remote {
|
||||
environment_id: "env-123".to_string(),
|
||||
skill_references: vec![
|
||||
SkillReference::Path("/tmp/SKILL.md".into()),
|
||||
SkillReference::Path(LocalOrRemotePath::Local(PathBuf::from("/tmp/SKILL.md",))),
|
||||
SkillReference::BundledSkillId("review-comments".to_string()),
|
||||
],
|
||||
model_id: "gpt-test".to_string(),
|
||||
@@ -560,6 +579,7 @@ fn converts_remote_start_agent_v2_with_skill_references() {
|
||||
worker_host: "worker-host".to_string(),
|
||||
harness_type: "claude-code".to_string(),
|
||||
title: "Remote child".to_string(),
|
||||
auth_secret_name: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(lifecycle_subscription, None);
|
||||
@@ -580,6 +600,7 @@ fn converts_local_start_agent_v2_with_harness_type() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("conversion should succeed");
|
||||
|
||||
@@ -599,6 +620,7 @@ fn transfer_control_tool_call_converts_to_action_message() {
|
||||
let task_id = TaskId::new("task".to_string());
|
||||
let reason = "Please finish the interactive flow".to_string();
|
||||
let message = api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: "message".to_string(),
|
||||
task_id: "task".to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -622,6 +644,7 @@ fn transfer_control_tool_call_converts_to_action_message() {
|
||||
task_id: &task_id,
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
})
|
||||
.expect("transfer-control conversion should succeed");
|
||||
|
||||
|
||||
@@ -5,14 +5,12 @@ use anyhow::anyhow;
|
||||
use chrono::{DateTime, Local, Timelike};
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::{
|
||||
agent::{
|
||||
AIAgentActionResult, AIAgentActionResultType, AIAgentAttachment, AIAgentContext,
|
||||
AIAgentInput, DriveObjectPayload, MCPContext, PassiveSuggestionResultType,
|
||||
PassiveSuggestionTrigger, RunningCommand, StaticQueryType, Suggestions, UserQueryMode,
|
||||
},
|
||||
block_context::BlockContext,
|
||||
use crate::ai::agent::{
|
||||
AIAgentActionResult, AIAgentActionResultType, AIAgentAttachment, AIAgentContext, AIAgentInput,
|
||||
DriveObjectPayload, MCPContext, PassiveSuggestionResultType, PassiveSuggestionTrigger,
|
||||
RunningCommand, StaticQueryType, Suggestions, UserQueryMode,
|
||||
};
|
||||
use crate::ai::block_context::BlockContext;
|
||||
|
||||
fn local_datetime_to_timestamp(timestamp: DateTime<Local>) -> prost_types::Timestamp {
|
||||
prost_types::Timestamp {
|
||||
@@ -46,11 +44,6 @@ impl TryFrom<StaticQueryType> for api::request::input::query_with_canned_respons
|
||||
api::request::input::query_with_canned_response::SomethingElse {},
|
||||
),
|
||||
),
|
||||
StaticQueryType::CustomOnboardingRequest => Ok(
|
||||
api::request::input::query_with_canned_response::Type::CustomOnboardingRequest(
|
||||
api::request::input::query_with_canned_response::CustomOnboardingRequest {},
|
||||
),
|
||||
),
|
||||
StaticQueryType::EvaluationSuite => {
|
||||
Err(anyhow::anyhow!("EvaluationSuite StaticQueryType not yet supported").into())
|
||||
}
|
||||
@@ -209,9 +202,9 @@ pub(super) fn convert_input(
|
||||
)),
|
||||
});
|
||||
}
|
||||
AIAgentInput::SummarizeConversation { prompt } => {
|
||||
AIAgentInput::SummarizeConversation { prompt, context } => {
|
||||
return Ok(api::request::Input {
|
||||
context: None,
|
||||
context: Some(convert_context(context.as_ref())),
|
||||
r#type: Some(api::request::input::Type::SummarizeConversation(
|
||||
api::request::input::SummarizeConversation {
|
||||
prompt: prompt.unwrap_or_default(),
|
||||
@@ -435,6 +428,19 @@ fn convert_input_to_user_input(
|
||||
),
|
||||
)
|
||||
}
|
||||
AIAgentInput::OrchestrationConfigUpdate {
|
||||
plan_id,
|
||||
config,
|
||||
status,
|
||||
} => Ok(
|
||||
api::request::input::user_inputs::user_input::Input::OrchestrationConfigUpdate(
|
||||
api::OrchestrationConfigUpdate {
|
||||
plan_id,
|
||||
config: Some(config.to_proto()),
|
||||
status: status.to_proto(),
|
||||
},
|
||||
),
|
||||
),
|
||||
AIAgentInput::ResumeConversation { .. } => Err(ConvertToAPITypeError::Ignore),
|
||||
AIAgentInput::InitProjectRules { .. } => Err(ConvertToAPITypeError::Ignore),
|
||||
AIAgentInput::CodeReview { .. } => Err(ConvertToAPITypeError::Ignore),
|
||||
@@ -692,6 +698,12 @@ impl TryFrom<AIAgentActionResult> for api::request::input::user_inputs::user_inp
|
||||
AIAgentActionResultType::AskUserQuestion(ask_user_question_result) => {
|
||||
Some(ask_user_question_result.into())
|
||||
}
|
||||
AIAgentActionResultType::RunAgents(orchestrate_result) => {
|
||||
Some(orchestrate_result.try_into()?)
|
||||
}
|
||||
AIAgentActionResultType::WaitForEvents(wait_for_events_result) => {
|
||||
Some(wait_for_events_result.try_into()?)
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
api::request::input::user_inputs::user_input::Input::ToolCallResult(
|
||||
@@ -706,6 +718,7 @@ impl TryFrom<AIAgentActionResult> for api::request::input::user_inputs::user_inp
|
||||
|
||||
fn convert_context(context: &[AIAgentContext]) -> api::InputContext {
|
||||
let mut api_context = api::InputContext::default();
|
||||
let mut git_context = None;
|
||||
for context in context.iter().cloned() {
|
||||
match context {
|
||||
AIAgentContext::Block(block) => {
|
||||
@@ -789,11 +802,40 @@ fn convert_context(context: &[AIAgentContext]) -> api::InputContext {
|
||||
}
|
||||
}
|
||||
AIAgentContext::Git { head, branch } => {
|
||||
api_context.git = Some(api::input_context::Git {
|
||||
head,
|
||||
branch: branch.unwrap_or_default(),
|
||||
let api_git_context =
|
||||
git_context.get_or_insert_with(api::input_context::Git::default);
|
||||
api_git_context.head = head;
|
||||
api_git_context.branch = branch.unwrap_or_default();
|
||||
}
|
||||
AIAgentContext::Repository { name, owner } => {
|
||||
let api_git_context =
|
||||
git_context.get_or_insert_with(api::input_context::Git::default);
|
||||
api_git_context.repository = Some(api::input_context::git::Repository {
|
||||
name,
|
||||
owner: owner.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
AIAgentContext::PullRequest {
|
||||
number,
|
||||
state,
|
||||
draft,
|
||||
base_branch,
|
||||
} => {
|
||||
if number <= 0 {
|
||||
continue;
|
||||
}
|
||||
let Some(state) = api_pull_request_state(&state, draft) else {
|
||||
continue;
|
||||
};
|
||||
let pull_request = api::input_context::git::PullRequest {
|
||||
number,
|
||||
state: state as i32,
|
||||
base_branch,
|
||||
};
|
||||
let api_git_context =
|
||||
git_context.get_or_insert_with(api::input_context::Git::default);
|
||||
api_git_context.pull_request = Some(pull_request);
|
||||
}
|
||||
AIAgentContext::Skills { skills } => {
|
||||
api_context.updated_skills_context = Some(api::input_context::SkillsContext {
|
||||
available_skills: skills
|
||||
@@ -810,9 +852,34 @@ fn convert_context(context: &[AIAgentContext]) -> api::InputContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
api_context.git = git_context;
|
||||
api_context
|
||||
}
|
||||
|
||||
/// Maps a GitHub PR state plus draft flag to the proto `State` enum.
|
||||
///
|
||||
/// Returns `None` for unknown states so the caller can skip emitting a
|
||||
/// `pull_request` sub-message rather than sending `STATE_UNSPECIFIED` to the
|
||||
/// server.
|
||||
fn api_pull_request_state(
|
||||
state: &str,
|
||||
draft: bool,
|
||||
) -> Option<api::input_context::git::pull_request::State> {
|
||||
use api::input_context::git::pull_request::State;
|
||||
match state.to_ascii_uppercase().as_str() {
|
||||
"OPEN" => {
|
||||
if draft {
|
||||
Some(State::OpenDraft)
|
||||
} else {
|
||||
Some(State::Open)
|
||||
}
|
||||
}
|
||||
"CLOSED" => Some(State::Closed),
|
||||
"MERGED" => Some(State::Merged),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Suggestions> for api::Suggestions {
|
||||
fn from(value: Suggestions) -> Self {
|
||||
Self {
|
||||
@@ -940,12 +1007,13 @@ impl From<BlockContext> for api::ExecutedShellCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trys to convert a [`serde_json::Value`] to a [`prost_types::Value`].
|
||||
/// Tries to convert a [`serde_json::Value`] to a [`prost_types::Value`].
|
||||
#[cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
fn serde_json_to_prost(value: serde_json::Value) -> Result<prost_types::Value, String> {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use prost_types::value::Kind::*;
|
||||
use serde_json::Value::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
Ok(prost_types::Value {
|
||||
kind: Some(match value {
|
||||
|
||||
@@ -1,11 +1,111 @@
|
||||
use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::{
|
||||
AIAgentActionResult, AIAgentActionResultType, TransferShellCommandControlToUserResult,
|
||||
};
|
||||
use crate::terminal::model::block::BlockId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use galaxy_core::command::ExitCode;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::{
|
||||
AIAgentActionResult, AIAgentActionResultType, AIAgentContext,
|
||||
TransferShellCommandControlToUserResult,
|
||||
};
|
||||
use crate::terminal::model::block::BlockId;
|
||||
|
||||
#[test]
|
||||
fn git_context_converts_repository_and_pull_request_metadata() {
|
||||
let context = vec![
|
||||
AIAgentContext::Git {
|
||||
head: "abc123".to_string(),
|
||||
branch: Some("feature/repo-pr".to_string()),
|
||||
},
|
||||
AIAgentContext::Repository {
|
||||
name: "warp-internal".to_string(),
|
||||
owner: Some("warpdotdev".to_string()),
|
||||
},
|
||||
AIAgentContext::PullRequest {
|
||||
number: 42,
|
||||
state: "OPEN".to_string(),
|
||||
draft: true,
|
||||
base_branch: "main".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let api_context = super::convert_context(&context);
|
||||
let git = api_context.git.expect("expected git context");
|
||||
assert_eq!(git.head, "abc123");
|
||||
assert_eq!(git.branch, "feature/repo-pr");
|
||||
|
||||
let repository = git.repository.expect("expected repository context");
|
||||
assert_eq!(repository.name, "warp-internal");
|
||||
assert_eq!(repository.owner, "warpdotdev");
|
||||
|
||||
let pull_request = git.pull_request.expect("expected pull request context");
|
||||
assert_eq!(pull_request.number, 42);
|
||||
assert_eq!(
|
||||
pull_request.state,
|
||||
api::input_context::git::pull_request::State::OpenDraft as i32
|
||||
);
|
||||
assert_eq!(pull_request.base_branch, "main");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_context_skips_pull_request_metadata_with_invalid_number() {
|
||||
for number in [0, -1] {
|
||||
let context = vec![
|
||||
AIAgentContext::Git {
|
||||
head: "abc123".to_string(),
|
||||
branch: Some("feature/repo-pr".to_string()),
|
||||
},
|
||||
AIAgentContext::PullRequest {
|
||||
number,
|
||||
state: "OPEN".to_string(),
|
||||
draft: false,
|
||||
base_branch: "main".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let api_context = super::convert_context(&context);
|
||||
let git = api_context.git.expect("expected git context");
|
||||
assert_eq!(git.head, "abc123");
|
||||
assert_eq!(git.branch, "feature/repo-pr");
|
||||
assert_eq!(git.pull_request, None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_context_skips_pull_request_metadata_with_unknown_state() {
|
||||
let context = vec![
|
||||
AIAgentContext::Git {
|
||||
head: "abc123".to_string(),
|
||||
branch: Some("feature/repo-pr".to_string()),
|
||||
},
|
||||
AIAgentContext::PullRequest {
|
||||
number: 42,
|
||||
state: "SOMETHING_ELSE".to_string(),
|
||||
draft: false,
|
||||
base_branch: "main".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let api_context = super::convert_context(&context);
|
||||
let git = api_context.git.expect("expected git context");
|
||||
assert_eq!(git.pull_request, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_context_deserializes_legacy_string_pull_request_number() {
|
||||
let pull_request = serde_json::from_str::<AIAgentContext>(
|
||||
r#"{"PullRequest":{"number":"42","state":"OPEN","draft":false,"base_branch":"main"}}"#,
|
||||
)
|
||||
.expect("expected legacy serialized pull request context");
|
||||
|
||||
let api_context = super::convert_context(&[pull_request]);
|
||||
let pull_request = api_context
|
||||
.git
|
||||
.expect("expected git context")
|
||||
.pull_request
|
||||
.expect("expected pull request context");
|
||||
assert_eq!(pull_request.number, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_control_snapshot_result_converts_to_tool_call_result_input() {
|
||||
let block_id = BlockId::default();
|
||||
@@ -51,6 +151,8 @@ fn transfer_control_snapshot_result_converts_to_tool_call_result_input() {
|
||||
#[test]
|
||||
fn transfer_control_finished_result_converts_to_tool_call_result_input() {
|
||||
let block_id = BlockId::default();
|
||||
let start_ts = DateTime::from(Utc::now());
|
||||
let completed_ts = DateTime::from(Utc::now());
|
||||
let input =
|
||||
api::request::input::user_inputs::user_input::Input::try_from(AIAgentActionResult {
|
||||
id: "tool_call".to_string().into(),
|
||||
@@ -60,6 +162,8 @@ fn transfer_control_finished_result_converts_to_tool_call_result_input() {
|
||||
block_id: block_id.clone(),
|
||||
output: "done".to_string(),
|
||||
exit_code: ExitCode::from(17),
|
||||
start_ts: Some(start_ts),
|
||||
completed_ts: Some(completed_ts),
|
||||
},
|
||||
),
|
||||
})
|
||||
@@ -78,6 +182,8 @@ fn transfer_control_finished_result_converts_to_tool_call_result_input() {
|
||||
assert_eq!(finished.command_id, block_id.to_string());
|
||||
assert_eq!(finished.output, "done");
|
||||
assert_eq!(finished.exit_code, 17);
|
||||
assert_eq!(finished.start_ts, Some(super::local_datetime_to_timestamp(start_ts)));
|
||||
assert_eq!(finished.finish_ts, Some(super::local_datetime_to_timestamp(completed_ts)));
|
||||
}
|
||||
other => panic!("Expected command-finished result, got {other:?}"),
|
||||
},
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{ai::agent::redaction, terminal::model::session::SessionType};
|
||||
use futures_util::StreamExt;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::bedrock::translator::{self, TranslatorRequest};
|
||||
use crate::ai::openai::translator as openai_translator;
|
||||
use crate::ai::provider::ProviderConfig;
|
||||
|
||||
use super::{convert_to::convert_input, ConvertToAPITypeError, RequestParams, ResponseStream};
|
||||
use super::convert_to::convert_input;
|
||||
use super::{ConvertToAPITypeError, RequestParams, ResponseStream};
|
||||
use crate::ai::agent::redaction;
|
||||
use crate::server::server_api::{AIApiError, ServerApi};
|
||||
use crate::terminal::model::session::SessionType;
|
||||
|
||||
pub async fn generate_multi_agent_output(
|
||||
provider_config: ProviderConfig,
|
||||
@@ -53,10 +53,10 @@ pub async fn generate_multi_agent_output(
|
||||
redaction::redact_inputs(&mut params.input);
|
||||
}
|
||||
|
||||
let mut api_keys = params.api_keys;
|
||||
if let Some(api_keys) = &mut api_keys {
|
||||
api_keys.allow_use_of_warp_credits = params.allow_use_of_warp_credits_with_byok;
|
||||
}
|
||||
let api_keys = api_keys_with_warp_credit_fallback_setting(
|
||||
params.api_keys,
|
||||
params.allow_use_of_warp_credits,
|
||||
);
|
||||
|
||||
let mut request = api::Request {
|
||||
task_context: Some(api::request::TaskContext {
|
||||
@@ -68,6 +68,7 @@ pub async fn generate_multi_agent_output(
|
||||
base: params.model.into(),
|
||||
cli_agent: params.cli_agent_model.into(),
|
||||
computer_use_agent: params.computer_use_model.into(),
|
||||
base_model_context_window_limit: params.context_window_limit.unwrap_or(0),
|
||||
..Default::default()
|
||||
}),
|
||||
rules_enabled: params.is_memory_enabled,
|
||||
@@ -99,7 +100,11 @@ pub async fn generate_multi_agent_output(
|
||||
FeatureFlag::SummarizationViaMessageReplacement.is_enabled(),
|
||||
supports_bundled_skills: FeatureFlag::BundledSkills.is_enabled(),
|
||||
supports_research_agent: params.research_agent_enabled,
|
||||
supports_orchestration_v2: FeatureFlag::OrchestrationV2.is_enabled(),
|
||||
supports_orchestration_v2: supports_orchestration_v2(params.orchestration_enabled),
|
||||
supports_background_computer_use: FeatureFlag::BackgroundComputerUse.is_enabled()
|
||||
&& computer_use::background_supported(),
|
||||
custom_model_providers: params.custom_model_providers,
|
||||
custom_model_routers: params.custom_model_routers,
|
||||
}),
|
||||
metadata: Some(api::request::Metadata {
|
||||
logging: logging_metadata,
|
||||
@@ -113,6 +118,8 @@ pub async fn generate_multi_agent_output(
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or_default(),
|
||||
forked_from_conversation_id: if params.conversation_token.is_none() {
|
||||
// We only include this param on our initial request to the server
|
||||
// (when the forked conversation has not been assigned a new id yet).
|
||||
params
|
||||
.forked_from_conversation_token
|
||||
.map(|token| token.as_str().to_string())
|
||||
@@ -129,41 +136,19 @@ pub async fn generate_multi_agent_output(
|
||||
mcp_context: params.mcp_context.map(Into::into),
|
||||
};
|
||||
|
||||
let model_id = request
|
||||
.settings
|
||||
.as_ref()
|
||||
.and_then(|s| s.model_config.as_ref())
|
||||
.map(|mc| mc.base.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
match provider_config {
|
||||
ProviderConfig::Bedrock(config) => {
|
||||
let translator_request = TranslatorRequest {
|
||||
config,
|
||||
model_id,
|
||||
root_task_id: params.root_task_id.clone(),
|
||||
bedrock_message_history: params.bedrock_message_history.clone(),
|
||||
bedrock_tool_result_archive: params.bedrock_tool_result_archive.clone(),
|
||||
bedrock_progressive_summary: params.bedrock_progressive_summary.clone(),
|
||||
bedrock_messages_sent: params.bedrock_messages_sent.clone(),
|
||||
};
|
||||
|
||||
match translator::execute(translator_request, &mut request).await {
|
||||
Ok(stream) => {
|
||||
let output_stream = stream.take_until(cancellation_rx);
|
||||
Ok(Box::pin(output_stream))
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("[bedrock] Translator error: {e}");
|
||||
let err = Arc::new(crate::server::server_api::AIApiError::Stream {
|
||||
stream_type: "bedrock_converse",
|
||||
source: anyhow::anyhow!("{e}"),
|
||||
});
|
||||
let (tx, rx) = async_channel::unbounded();
|
||||
let _ = tx.send(Err(err)).await;
|
||||
Ok(Box::pin(rx))
|
||||
}
|
||||
}
|
||||
let response_stream =
|
||||
warp_multi_agent_client::generate_multi_agent_output(server_api.as_ref(), &request).await;
|
||||
match response_stream {
|
||||
Ok(stream) => {
|
||||
let output_stream = stream
|
||||
.then(|result| async {
|
||||
match result {
|
||||
Ok(event) => Ok(event),
|
||||
Err(error) => Err(convert_multi_agent_client_error(error).await),
|
||||
}
|
||||
})
|
||||
.take_until(cancellation_rx);
|
||||
Ok(Box::pin(output_stream))
|
||||
}
|
||||
ProviderConfig::OpenAI(config) => {
|
||||
let translator_request = openai_translator::TranslatorRequest {
|
||||
@@ -202,12 +187,53 @@ pub async fn generate_multi_agent_output(
|
||||
),
|
||||
});
|
||||
let (tx, rx) = async_channel::unbounded();
|
||||
let _ = tx.send(Err(err)).await;
|
||||
let _ = tx
|
||||
.send(Err(convert_multi_agent_client_error(e).await))
|
||||
.await;
|
||||
Ok(Box::pin(rx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn convert_multi_agent_client_error(
|
||||
error: warp_multi_agent_client::Error,
|
||||
) -> Arc<AIApiError> {
|
||||
let error = match error {
|
||||
warp_multi_agent_client::Error::Authentication(error)
|
||||
| warp_multi_agent_client::Error::AmbientHeaders(error) => AIApiError::Other(error),
|
||||
warp_multi_agent_client::Error::Base64Decode(error) => {
|
||||
AIApiError::Other(anyhow::Error::from(error))
|
||||
}
|
||||
warp_multi_agent_client::Error::ProtobufDecode(error) => {
|
||||
AIApiError::Other(anyhow::Error::from(error))
|
||||
}
|
||||
warp_multi_agent_client::Error::EventSource(error) => {
|
||||
AIApiError::from_stream_error("GenerateMultiAgentOutput", *error).await
|
||||
}
|
||||
};
|
||||
Arc::new(error)
|
||||
}
|
||||
|
||||
fn api_keys_with_warp_credit_fallback_setting(
|
||||
api_keys: Option<api::request::settings::ApiKeys>,
|
||||
allow_use_of_warp_credits: bool,
|
||||
) -> Option<api::request::settings::ApiKeys> {
|
||||
match api_keys {
|
||||
Some(mut api_keys) => {
|
||||
api_keys.allow_use_of_warp_credits = allow_use_of_warp_credits;
|
||||
Some(api_keys)
|
||||
}
|
||||
None if allow_use_of_warp_credits => Some(api::request::settings::ApiKeys {
|
||||
allow_use_of_warp_credits: true,
|
||||
..Default::default()
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_orchestration_v2(orchestration_enabled: bool) -> bool {
|
||||
orchestration_enabled
|
||||
}
|
||||
fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
|
||||
let mut supported_tools = vec![
|
||||
api::ToolType::Grep,
|
||||
@@ -245,7 +271,14 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
|
||||
}
|
||||
}
|
||||
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => {
|
||||
// Remote session with a known host — enable tools that route
|
||||
// through RemoteServerClient. The host_id is only populated
|
||||
// after a successful connection handshake, so its presence is a
|
||||
// sufficient proxy for client availability.
|
||||
supported_tools.extend(&[api::ToolType::ReadFiles, api::ToolType::ApplyFileDiffs]);
|
||||
if FeatureFlag::RemoteCodebaseIndexing.is_enabled() {
|
||||
supported_tools.push(api::ToolType::SearchCodebase);
|
||||
}
|
||||
}
|
||||
Some(SessionType::WarpifiedRemote { host_id: None }) => {}
|
||||
}
|
||||
@@ -264,12 +297,10 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
|
||||
}
|
||||
|
||||
if params.orchestration_enabled {
|
||||
supported_tools.push(if FeatureFlag::OrchestrationV2.is_enabled() {
|
||||
api::ToolType::StartAgentV2
|
||||
} else {
|
||||
api::ToolType::StartAgent
|
||||
});
|
||||
supported_tools.push(api::ToolType::SendMessageToAgent);
|
||||
supported_tools.extend([api::ToolType::RunAgents, api::ToolType::SendMessageToAgent]);
|
||||
// Declare client-handled wait_for_events so the server doesn't
|
||||
// fall back to the legacy server-handled form.
|
||||
supported_tools.push(api::ToolType::WaitForEvents);
|
||||
}
|
||||
|
||||
if FeatureFlag::AskUserQuestion.is_enabled() && params.ask_user_question_enabled {
|
||||
@@ -299,6 +330,9 @@ fn get_supported_cli_agent_tools(params: &RequestParams) -> Vec<api::ToolType> {
|
||||
}
|
||||
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => {
|
||||
supported_cli_agent_tools.push(api::ToolType::ReadFiles);
|
||||
if FeatureFlag::RemoteCodebaseIndexing.is_enabled() {
|
||||
supported_cli_agent_tools.push(api::ToolType::SearchCodebase);
|
||||
}
|
||||
}
|
||||
Some(SessionType::WarpifiedRemote { host_id: None }) => {}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxy_core::HostId;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::{
|
||||
api_keys_with_warp_credit_fallback_setting, get_supported_cli_agent_tools, get_supported_tools,
|
||||
supports_orchestration_v2,
|
||||
};
|
||||
use crate::ai::agent::api::RequestParams;
|
||||
use crate::ai::blocklist::SessionContext;
|
||||
use crate::ai::llms::LLMId;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::get_supported_tools;
|
||||
use crate::terminal::model::session::SessionType;
|
||||
|
||||
fn request_params_with_ask_user_question_enabled(ask_user_question_enabled: bool) -> RequestParams {
|
||||
let model = LLMId::from("test-model");
|
||||
@@ -24,11 +29,14 @@ fn request_params_with_ask_user_question_enabled(ask_user_question_enabled: bool
|
||||
computer_use_model: model,
|
||||
is_memory_enabled: false,
|
||||
warp_drive_context_enabled: false,
|
||||
context_window_limit: None,
|
||||
mcp_context: None,
|
||||
planning_enabled: true,
|
||||
should_redact_secrets: false,
|
||||
api_keys: None,
|
||||
allow_use_of_warp_credits_with_byok: false,
|
||||
custom_model_providers: None,
|
||||
custom_model_routers: None,
|
||||
allow_use_of_warp_credits: false,
|
||||
autonomy_level: api::AutonomyLevel::Supervised,
|
||||
isolation_level: api::IsolationLevel::None,
|
||||
web_search_enabled: false,
|
||||
@@ -47,6 +55,85 @@ fn request_params_with_ask_user_question_enabled(ask_user_question_enabled: bool
|
||||
}
|
||||
}
|
||||
|
||||
fn request_params_for_remote(host_id: Option<HostId>) -> RequestParams {
|
||||
let mut params = request_params_with_ask_user_question_enabled(false);
|
||||
params.session_context =
|
||||
SessionContext::new_with_session_type_for_test(Some(SessionType::WarpifiedRemote {
|
||||
host_id,
|
||||
}));
|
||||
params
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_keys_with_warp_credit_fallback_setting_returns_none_without_keys_or_fallback() {
|
||||
let api_keys = api_keys_with_warp_credit_fallback_setting(None, false);
|
||||
|
||||
assert!(api_keys.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_keys_with_warp_credit_fallback_setting_creates_fallback_only_api_keys() {
|
||||
let api_keys = api_keys_with_warp_credit_fallback_setting(None, true)
|
||||
.expect("fallback setting should create ApiKeys");
|
||||
|
||||
assert!(api_keys.allow_use_of_warp_credits);
|
||||
assert!(api_keys.anthropic.is_empty());
|
||||
assert!(api_keys.openai.is_empty());
|
||||
assert!(api_keys.google.is_empty());
|
||||
assert!(api_keys.open_router.is_empty());
|
||||
assert!(api_keys.aws_credentials.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_keys_with_warp_credit_fallback_setting_preserves_existing_keys() {
|
||||
let api_keys = api_keys_with_warp_credit_fallback_setting(
|
||||
Some(api::request::settings::ApiKeys {
|
||||
anthropic: "anthropic-key".to_string(),
|
||||
openai: String::new(),
|
||||
google: String::new(),
|
||||
open_router: String::new(),
|
||||
grok_oauth_access_token: String::new(),
|
||||
allow_use_of_warp_credits: false,
|
||||
aws_credentials: None,
|
||||
google_cloud_credentials: None,
|
||||
}),
|
||||
true,
|
||||
)
|
||||
.expect("existing ApiKeys should be preserved");
|
||||
|
||||
assert_eq!(api_keys.anthropic, "anthropic-key");
|
||||
assert!(api_keys.allow_use_of_warp_credits);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_orchestration_v2_matches_request_orchestration_setting() {
|
||||
assert!(supports_orchestration_v2(true));
|
||||
assert!(!supports_orchestration_v2(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supported_tools_include_orchestration_tools_when_orchestration_enabled() {
|
||||
let mut params = request_params_with_ask_user_question_enabled(false);
|
||||
params.orchestration_enabled = true;
|
||||
|
||||
let supported_tools = get_supported_tools(¶ms);
|
||||
|
||||
assert!(supported_tools.contains(&api::ToolType::RunAgents));
|
||||
assert!(supported_tools.contains(&api::ToolType::SendMessageToAgent));
|
||||
assert!(!supported_tools.contains(&api::ToolType::StartAgent));
|
||||
assert!(!supported_tools.contains(&api::ToolType::StartAgentV2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supported_tools_omit_orchestration_tools_when_orchestration_disabled() {
|
||||
let params = request_params_with_ask_user_question_enabled(false);
|
||||
let supported_tools = get_supported_tools(¶ms);
|
||||
|
||||
assert!(!supported_tools.contains(&api::ToolType::RunAgents));
|
||||
assert!(!supported_tools.contains(&api::ToolType::SendMessageToAgent));
|
||||
assert!(!supported_tools.contains(&api::ToolType::StartAgent));
|
||||
assert!(!supported_tools.contains(&api::ToolType::StartAgentV2));
|
||||
}
|
||||
#[test]
|
||||
fn supported_tools_omits_ask_user_question_when_disabled() {
|
||||
let params = request_params_with_ask_user_question_enabled(false);
|
||||
@@ -84,3 +171,35 @@ fn supported_tools_omit_upload_artifact_when_feature_flag_is_disabled() {
|
||||
|
||||
assert!(!supported_tools.contains(&api::ToolType::UploadFileArtifact));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_supported_tools_include_search_codebase_when_connected_and_feature_flag_is_enabled() {
|
||||
let _flag = FeatureFlag::RemoteCodebaseIndexing.override_enabled(true);
|
||||
let params = request_params_for_remote(Some(HostId::new("host".to_string())));
|
||||
let supported_tools = get_supported_tools(¶ms);
|
||||
let supported_cli_agent_tools = get_supported_cli_agent_tools(¶ms);
|
||||
|
||||
assert!(supported_tools.contains(&api::ToolType::SearchCodebase));
|
||||
assert!(supported_cli_agent_tools.contains(&api::ToolType::SearchCodebase));
|
||||
}
|
||||
#[test]
|
||||
fn remote_supported_tools_omit_search_codebase_when_feature_flag_is_disabled() {
|
||||
let _flag = FeatureFlag::RemoteCodebaseIndexing.override_enabled(false);
|
||||
let params = request_params_for_remote(Some(HostId::new("host".to_string())));
|
||||
let supported_tools = get_supported_tools(¶ms);
|
||||
let supported_cli_agent_tools = get_supported_cli_agent_tools(¶ms);
|
||||
|
||||
assert!(!supported_tools.contains(&api::ToolType::SearchCodebase));
|
||||
assert!(!supported_cli_agent_tools.contains(&api::ToolType::SearchCodebase));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_supported_tools_omit_search_codebase_when_remote_is_not_connected() {
|
||||
let _flag = FeatureFlag::RemoteCodebaseIndexing.override_enabled(true);
|
||||
let params = request_params_for_remote(None);
|
||||
let supported_tools = get_supported_tools(¶ms);
|
||||
let supported_cli_agent_tools = get_supported_cli_agent_tools(¶ms);
|
||||
|
||||
assert!(!supported_tools.contains(&api::ToolType::SearchCodebase));
|
||||
assert!(!supported_cli_agent_tools.contains(&api::ToolType::SearchCodebase));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::code::buffer_location::LocalOrRemotePath;
|
||||
use crate::code_review::comments::CommentId;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// The current state of a code review.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -31,18 +31,14 @@ impl ReviewComment {
|
||||
pub fn title(&self) -> String {
|
||||
match (&self.diff.file_path, self.diff.line_number) {
|
||||
(Some(file_path), Some(line_number)) => {
|
||||
let file_name = file_path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("Invalid File Name");
|
||||
let path_component = file_path.path_component();
|
||||
let file_name = path_component.file_name().unwrap_or("Invalid File Name");
|
||||
let display_line = line_number + 1;
|
||||
format!("{file_name}:{display_line}")
|
||||
}
|
||||
(Some(file_path), None) => {
|
||||
let file_name = file_path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("Invalid File Name");
|
||||
let path_component = file_path.path_component();
|
||||
let file_name = path_component.file_name().unwrap_or("Invalid File Name");
|
||||
file_name.to_string()
|
||||
}
|
||||
(None, _) => self
|
||||
@@ -101,6 +97,6 @@ impl From<crate::code_review::comments::AttachedReviewCommentTarget> for ReviewD
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ReviewDiff {
|
||||
pub file_path: Option<PathBuf>,
|
||||
pub file_path: Option<LocalOrRemotePath>,
|
||||
pub line_number: Option<usize>,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{
|
||||
artifact_from_fork_proto, AIConversation, AIConversationAutoexecuteMode, AIConversationId,
|
||||
};
|
||||
use crate::ai::artifacts::Artifact;
|
||||
use crate::persistence::model::AgentConversationData;
|
||||
use ai::api_keys::ApiKeyManager;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use warp_multi_agent_api as api;
|
||||
use warpui::{App, SingletonEntity};
|
||||
|
||||
use super::{
|
||||
artifact_from_fork_proto, footer_model_token_usage, AIConversation,
|
||||
AIConversationAutoexecuteMode, AIConversationId, ConversationStatus, RestoreConversationError,
|
||||
};
|
||||
use crate::ai::artifacts::Artifact;
|
||||
use crate::ai::llms::LLMPreferences;
|
||||
use crate::auth::auth_manager::AuthManager;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::network::NetworkStatus;
|
||||
use crate::persistence::model::AgentConversationData;
|
||||
use crate::server::server_api::ServerApiProvider;
|
||||
use crate::test_util::settings::initialize_settings_for_tests;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
|
||||
fn restored_conversation(conversation_data: Option<AgentConversationData>) -> AIConversation {
|
||||
AIConversation::new_restored(
|
||||
@@ -24,8 +35,25 @@ fn restored_conversation(conversation_data: Option<AgentConversationData>) -> AI
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn restored_conversation_with_root_description(description: &str) -> AIConversation {
|
||||
AIConversation::new_restored(
|
||||
AIConversationId::new(),
|
||||
vec![api::Task {
|
||||
id: "root-task".to_string(),
|
||||
messages: vec![],
|
||||
dependencies: None,
|
||||
description: description.to_string(),
|
||||
summary: String::new(),
|
||||
server_data: String::new(),
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn user_query_message(id: &str, request_id: &str, query: &str) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: "root-task".to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -44,6 +72,7 @@ fn user_query_message(id: &str, request_id: &str, query: &str) -> api::Message {
|
||||
|
||||
fn agent_output_message(id: &str, request_id: &str) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: "root-task".to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -86,6 +115,43 @@ fn restored_conversation_with_queries(queries: &[&str]) -> AIConversation {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn initialize_custom_endpoint_usage_test_app(app: &mut App) {
|
||||
initialize_settings_for_tests(app);
|
||||
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
|
||||
app.add_singleton_model(|_| NetworkStatus::new());
|
||||
app.add_singleton_model(UserWorkspaces::default_mock);
|
||||
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
|
||||
app.add_singleton_model(AuthManager::new_for_test);
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn custom_endpoint_usage_metadata(
|
||||
config_key: &str,
|
||||
total_tokens: u32,
|
||||
) -> api::response_event::stream_finished::ConversationUsageMetadata {
|
||||
let category = "primary_agent".to_string();
|
||||
api::response_event::stream_finished::ConversationUsageMetadata {
|
||||
context_window_usage: 0.0,
|
||||
credits_spent: 0.0,
|
||||
platform_credits_spent: 0.0,
|
||||
summarized: false,
|
||||
token_usage: vec![],
|
||||
tool_usage_metadata: None,
|
||||
total_input_tokens: 0,
|
||||
warp_token_usage: HashMap::new(),
|
||||
byok_token_usage: HashMap::new(),
|
||||
context_window_segments: Vec::new(),
|
||||
custom_endpoint_token_usage: HashMap::from([(
|
||||
config_key.to_string(),
|
||||
api::response_event::stream_finished::ModelTokenUsage {
|
||||
model_id: config_key.to_string(),
|
||||
total_tokens,
|
||||
token_usage_by_category: HashMap::from([(category, total_tokens)]),
|
||||
},
|
||||
)]),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latest_user_query_returns_latest_non_empty_user_query() {
|
||||
let conversation =
|
||||
@@ -107,6 +173,49 @@ fn latest_user_query_trims_and_skips_empty_queries() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_uses_root_task_description() {
|
||||
let conversation = restored_conversation_with_root_description("Root task title");
|
||||
|
||||
assert_eq!(conversation.title().as_deref(), Some("Root task title"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_falls_back_to_initial_query_when_root_description_is_empty() {
|
||||
let conversation = restored_conversation_with_queries(&["Initial query"]);
|
||||
|
||||
assert_eq!(conversation.title().as_deref(), Some("Initial query"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reassign_exchange_ids_keeps_exchange_lookup_consistent() {
|
||||
let mut conversation = restored_conversation_with_queries(&["one", "two"]);
|
||||
|
||||
let old_ids: Vec<_> = conversation.all_exchanges().iter().map(|e| e.id).collect();
|
||||
assert!(!old_ids.is_empty());
|
||||
|
||||
// Pre-condition: every original id resolves via the exchange-id index.
|
||||
for id in &old_ids {
|
||||
assert!(conversation.exchange_with_id(*id).is_some());
|
||||
}
|
||||
|
||||
conversation.reassign_exchange_ids();
|
||||
|
||||
// Reassigning regenerates ids without changing the exchange count, so
|
||||
// `modify_task` does not rebuild the index; correctness relies on the
|
||||
// explicit `rebuild_exchange_id_index()` call. The stale ids must be gone.
|
||||
for id in &old_ids {
|
||||
assert!(conversation.exchange_with_id(*id).is_none());
|
||||
}
|
||||
|
||||
// Every current id resolves via the rebuilt index.
|
||||
let new_ids: Vec<_> = conversation.all_exchanges().iter().map(|e| e.id).collect();
|
||||
assert_eq!(new_ids.len(), old_ids.len());
|
||||
for id in &new_ids {
|
||||
assert!(conversation.exchange_with_id(*id).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_conversation_defaults_autoexecute_override_when_not_persisted() {
|
||||
let _flag = FeatureFlag::RememberFastForwardState.override_enabled(true);
|
||||
@@ -121,6 +230,416 @@ fn restored_conversation_defaults_autoexecute_override_when_not_persisted() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_conversation_uses_persisted_last_event_sequence() {
|
||||
let conversation_data: AgentConversationData =
|
||||
serde_json::from_str(r#"{"server_conversation_token":null,"last_event_sequence":42}"#)
|
||||
.unwrap();
|
||||
|
||||
let conversation = restored_conversation(Some(conversation_data));
|
||||
|
||||
assert_eq!(conversation.last_event_sequence(), Some(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_conversation_uses_persisted_remote_child_marker() {
|
||||
let conversation_data: AgentConversationData =
|
||||
serde_json::from_str(r#"{"server_conversation_token":null,"is_remote_child":true}"#)
|
||||
.unwrap();
|
||||
|
||||
let conversation = restored_conversation(Some(conversation_data));
|
||||
|
||||
assert!(conversation.is_remote_child());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_conversation_detection_uses_parent_agent_id() {
|
||||
let conversation_data: AgentConversationData = serde_json::from_str(
|
||||
r#"{"server_conversation_token":null,"parent_agent_id":"parent-run-id"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let conversation = restored_conversation(Some(conversation_data));
|
||||
|
||||
assert!(conversation.is_child_agent_conversation());
|
||||
assert_eq!(conversation.parent_conversation_id(), None);
|
||||
}
|
||||
|
||||
/// When the persisted task list is empty (e.g. a child conversation persisted
|
||||
/// before any server response), restoring via `new_restored_synthesizing_on_empty`
|
||||
/// must produce a fresh in-progress optimistic root, mirroring
|
||||
/// `AIConversation::new()`.
|
||||
#[test]
|
||||
fn restored_conversation_with_empty_task_list_creates_in_progress_optimistic_root() {
|
||||
let conversation =
|
||||
AIConversation::new_restored_synthesizing_on_empty(AIConversationId::new(), vec![], None)
|
||||
.expect("empty task list must synthesize an optimistic root");
|
||||
|
||||
let root_task = conversation
|
||||
.get_root_task()
|
||||
.expect("synthesized root task should exist");
|
||||
assert!(root_task.is_root_task());
|
||||
assert!(
|
||||
root_task.source().is_none(),
|
||||
"synthesized root is optimistic and has no api::Task source"
|
||||
);
|
||||
assert!(
|
||||
!root_task.id().to_string().is_empty(),
|
||||
"synthesized optimistic root must have a non-empty UUID id"
|
||||
);
|
||||
assert_eq!(conversation.status(), &ConversationStatus::InProgress);
|
||||
assert!(conversation.status_error_message().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_cost_and_usage_resolves_custom_endpoint_alias_for_footer_usage() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_custom_endpoint_usage_test_app(&mut app);
|
||||
ApiKeyManager::handle(&app).update(&mut app, |manager, ctx| {
|
||||
manager.add_custom_endpoint(
|
||||
"Endpoint".to_string(),
|
||||
"https://custom.example".to_string(),
|
||||
"key".to_string(),
|
||||
vec![(
|
||||
"raw-model".to_string(),
|
||||
Some("Friendly alias".to_string()),
|
||||
Some("config-key".to_string()),
|
||||
)],
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
app.add_singleton_model(LLMPreferences::new);
|
||||
|
||||
let mut conversation = AIConversation::new(false, false);
|
||||
app.read(|ctx| {
|
||||
conversation
|
||||
.update_cost_and_usage_for_request(
|
||||
None,
|
||||
vec![],
|
||||
Some(custom_endpoint_usage_metadata("config-key", 6)),
|
||||
false,
|
||||
ctx,
|
||||
)
|
||||
.expect("custom endpoint usage should update");
|
||||
});
|
||||
|
||||
let usage = conversation
|
||||
.token_usage()
|
||||
.iter()
|
||||
.find(|usage| usage.model_id == "Friendly alias")
|
||||
.expect("custom endpoint alias should resolve into footer usage");
|
||||
assert_eq!(usage.custom_endpoint_tokens, 6);
|
||||
assert_eq!(usage.byok_tokens, 0);
|
||||
assert_eq!(
|
||||
usage
|
||||
.custom_endpoint_token_usage_by_category
|
||||
.get("primary_agent"),
|
||||
Some(&6)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_cost_and_usage_uses_fallback_label_for_unknown_custom_endpoint() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_custom_endpoint_usage_test_app(&mut app);
|
||||
app.add_singleton_model(LLMPreferences::new);
|
||||
|
||||
let mut conversation = AIConversation::new(false, false);
|
||||
app.read(|ctx| {
|
||||
conversation
|
||||
.update_cost_and_usage_for_request(
|
||||
None,
|
||||
vec![],
|
||||
Some(custom_endpoint_usage_metadata("missing-config-key", 9)),
|
||||
false,
|
||||
ctx,
|
||||
)
|
||||
.expect("fallback custom endpoint usage should update");
|
||||
});
|
||||
|
||||
let usage = conversation
|
||||
.token_usage()
|
||||
.iter()
|
||||
.find(|usage| usage.model_id == "Custom endpoint")
|
||||
.expect("unknown custom endpoint usage should use the fallback label");
|
||||
assert_eq!(usage.custom_endpoint_tokens, 9);
|
||||
assert_eq!(usage.byok_tokens, 0);
|
||||
assert_eq!(
|
||||
usage
|
||||
.custom_endpoint_token_usage_by_category
|
||||
.get("primary_agent"),
|
||||
Some(&9)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[test]
|
||||
fn footer_model_token_usage_keeps_custom_endpoint_usage_distinct_from_same_labeled_models() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_custom_endpoint_usage_test_app(&mut app);
|
||||
ApiKeyManager::handle(&app).update(&mut app, |manager, ctx| {
|
||||
manager.add_custom_endpoint(
|
||||
"Endpoint".to_string(),
|
||||
"https://custom.example".to_string(),
|
||||
"key".to_string(),
|
||||
vec![(
|
||||
"raw-model".to_string(),
|
||||
Some("Resolved custom".to_string()),
|
||||
Some("config-key".to_string()),
|
||||
)],
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
app.add_singleton_model(LLMPreferences::new);
|
||||
|
||||
let category = "primary_agent".to_string();
|
||||
let usage_metadata = api::response_event::stream_finished::ConversationUsageMetadata {
|
||||
context_window_usage: 0.0,
|
||||
credits_spent: 0.0,
|
||||
platform_credits_spent: 0.0,
|
||||
summarized: false,
|
||||
#[allow(deprecated)]
|
||||
token_usage: vec![],
|
||||
tool_usage_metadata: None,
|
||||
total_input_tokens: 0,
|
||||
warp_token_usage: HashMap::new(),
|
||||
byok_token_usage: HashMap::from([(
|
||||
"Resolved custom".to_string(),
|
||||
api::response_event::stream_finished::ModelTokenUsage {
|
||||
model_id: "Resolved custom".to_string(),
|
||||
total_tokens: 4,
|
||||
token_usage_by_category: HashMap::from([(category.clone(), 4)]),
|
||||
},
|
||||
)]),
|
||||
custom_endpoint_token_usage: HashMap::from([(
|
||||
"config-key".to_string(),
|
||||
api::response_event::stream_finished::ModelTokenUsage {
|
||||
model_id: "config-key".to_string(),
|
||||
total_tokens: 6,
|
||||
token_usage_by_category: HashMap::from([(category.clone(), 6)]),
|
||||
},
|
||||
)]),
|
||||
context_window_segments: Vec::new(),
|
||||
};
|
||||
|
||||
let model_usage =
|
||||
app.read(|ctx| footer_model_token_usage(&usage_metadata, LLMPreferences::as_ref(ctx)));
|
||||
let byok_usage = model_usage
|
||||
.iter()
|
||||
.find(|usage| usage.model_id == "Resolved custom" && usage.byok_tokens == 4)
|
||||
.expect("existing model usage should be present");
|
||||
let custom_usage = model_usage
|
||||
.iter()
|
||||
.find(|usage| usage.model_id == "Resolved custom" && usage.custom_endpoint_tokens == 6)
|
||||
.expect("custom endpoint usage should remain distinct");
|
||||
|
||||
assert_eq!(model_usage.len(), 2);
|
||||
assert_eq!(
|
||||
byok_usage.byok_token_usage_by_category.get(&category),
|
||||
Some(&4)
|
||||
);
|
||||
assert_eq!(
|
||||
custom_usage
|
||||
.custom_endpoint_token_usage_by_category
|
||||
.get(&category),
|
||||
Some(&6)
|
||||
);
|
||||
assert_eq!(byok_usage.warp_tokens, 0);
|
||||
assert_eq!(custom_usage.warp_tokens, 0);
|
||||
assert_eq!(custom_usage.byok_tokens, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[test]
|
||||
fn footer_model_token_usage_preserves_unresolved_custom_endpoint_usage_with_fallback_label() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_custom_endpoint_usage_test_app(&mut app);
|
||||
app.add_singleton_model(LLMPreferences::new);
|
||||
|
||||
let category = "primary_agent".to_string();
|
||||
let usage_metadata = api::response_event::stream_finished::ConversationUsageMetadata {
|
||||
context_window_usage: 0.0,
|
||||
credits_spent: 0.0,
|
||||
platform_credits_spent: 0.0,
|
||||
summarized: false,
|
||||
#[allow(deprecated)]
|
||||
token_usage: vec![],
|
||||
tool_usage_metadata: None,
|
||||
total_input_tokens: 0,
|
||||
warp_token_usage: HashMap::new(),
|
||||
byok_token_usage: HashMap::new(),
|
||||
custom_endpoint_token_usage: HashMap::from([(
|
||||
"missing-config-key".to_string(),
|
||||
api::response_event::stream_finished::ModelTokenUsage {
|
||||
model_id: "missing-config-key".to_string(),
|
||||
total_tokens: 9,
|
||||
token_usage_by_category: HashMap::from([(category.clone(), 9)]),
|
||||
},
|
||||
)]),
|
||||
context_window_segments: Vec::new(),
|
||||
};
|
||||
|
||||
let model_usage =
|
||||
app.read(|ctx| footer_model_token_usage(&usage_metadata, LLMPreferences::as_ref(ctx)));
|
||||
let custom_usage = model_usage
|
||||
.iter()
|
||||
.find(|usage| usage.model_id == "Custom endpoint")
|
||||
.expect("fallback custom endpoint usage should be present");
|
||||
|
||||
assert_eq!(model_usage.len(), 1);
|
||||
assert_eq!(custom_usage.custom_endpoint_tokens, 9);
|
||||
assert_eq!(custom_usage.byok_tokens, 0);
|
||||
assert_eq!(
|
||||
custom_usage
|
||||
.custom_endpoint_token_usage_by_category
|
||||
.get(&category),
|
||||
Some(&9)
|
||||
);
|
||||
assert_eq!(custom_usage.warp_tokens, 0);
|
||||
});
|
||||
}
|
||||
|
||||
/// The legacy `AgentConversationData.root_task_is_optimistic` flag must be
|
||||
/// ignored on restore. A non-empty task list always produces a real
|
||||
/// server-backed root regardless of whether the flag is set.
|
||||
#[test]
|
||||
fn restored_conversation_ignores_legacy_root_task_is_optimistic_flag_with_non_empty_tasks() {
|
||||
let conversation_data: AgentConversationData = serde_json::from_str(
|
||||
r#"{"server_conversation_token":null,"root_task_is_optimistic":true}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let conversation = restored_conversation(Some(conversation_data));
|
||||
let root_task = conversation
|
||||
.get_root_task()
|
||||
.expect("root task should exist");
|
||||
|
||||
assert_eq!(root_task.id().to_string(), "root-task");
|
||||
assert!(root_task.is_root_task());
|
||||
assert!(
|
||||
root_task.source().is_some(),
|
||||
"with a real task list, the legacy optimistic flag must be ignored",
|
||||
);
|
||||
}
|
||||
|
||||
/// The legacy `root_task_is_optimistic` flag is ignored when restoring an
|
||||
/// empty task list via `new_restored_synthesizing_on_empty`.
|
||||
#[test]
|
||||
fn restored_conversation_ignores_legacy_root_task_is_optimistic_flag_with_empty_tasks() {
|
||||
let conversation_data: AgentConversationData = serde_json::from_str(
|
||||
r#"{"server_conversation_token":null,"root_task_is_optimistic":true}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let conversation = AIConversation::new_restored_synthesizing_on_empty(
|
||||
AIConversationId::new(),
|
||||
vec![],
|
||||
Some(conversation_data),
|
||||
)
|
||||
.expect("empty task list must synthesize an optimistic root regardless of legacy flag");
|
||||
|
||||
let root_task = conversation
|
||||
.get_root_task()
|
||||
.expect("synthesized root task should exist");
|
||||
assert!(root_task.is_root_task());
|
||||
assert!(root_task.source().is_none());
|
||||
assert_eq!(conversation.status(), &ConversationStatus::InProgress);
|
||||
}
|
||||
|
||||
/// Strict `new_restored` returns `NoRootTask` for an empty task list.
|
||||
#[test]
|
||||
fn new_restored_with_empty_task_list_returns_no_root_task_error() {
|
||||
let result = AIConversation::new_restored(AIConversationId::new(), vec![], None);
|
||||
assert!(
|
||||
matches!(result, Err(RestoreConversationError::NoRootTask)),
|
||||
"empty task list via strict new_restored must return NoRootTask; got {result:?}",
|
||||
);
|
||||
}
|
||||
|
||||
/// When multiple parentless tasks exist (e.g. a legacy orphan optimistic
|
||||
/// stub alongside the real server root), `new_restored` must prefer the
|
||||
/// candidate whose `messages` is non-empty. Each ordering runs in a loop to
|
||||
/// surface any nondeterminism in candidate selection.
|
||||
#[test]
|
||||
fn test_new_restored_prefers_parentless_task_with_messages_over_empty_stub() {
|
||||
let stub = api::Task {
|
||||
id: "optimistic-stub-uuid".to_string(),
|
||||
messages: vec![],
|
||||
dependencies: None,
|
||||
description: String::new(),
|
||||
summary: String::new(),
|
||||
server_data: String::new(),
|
||||
};
|
||||
let real = api::Task {
|
||||
id: "server-root-id".to_string(),
|
||||
messages: vec![user_query_message("user-msg", "request-1", "real query")],
|
||||
dependencies: None,
|
||||
description: String::new(),
|
||||
summary: String::new(),
|
||||
server_data: String::new(),
|
||||
};
|
||||
|
||||
// Stub appears first in the vec.
|
||||
for _ in 0..50 {
|
||||
let conversation = AIConversation::new_restored(
|
||||
AIConversationId::new(),
|
||||
vec![stub.clone(), real.clone()],
|
||||
None,
|
||||
)
|
||||
.expect("restore with stub + real parentless tasks must succeed");
|
||||
let root_task = conversation
|
||||
.get_root_task()
|
||||
.expect("restored conversation must have a root task");
|
||||
assert_eq!(
|
||||
root_task.id().to_string(),
|
||||
"server-root-id",
|
||||
"expected the real (non-empty) parentless task to win when stub is first",
|
||||
);
|
||||
let source = root_task
|
||||
.source()
|
||||
.expect("chosen root must have api::Task source");
|
||||
assert!(
|
||||
!source.messages.is_empty(),
|
||||
"chosen root must have non-empty messages",
|
||||
);
|
||||
}
|
||||
|
||||
// Real appears first in the vec.
|
||||
for _ in 0..50 {
|
||||
let conversation = AIConversation::new_restored(
|
||||
AIConversationId::new(),
|
||||
vec![real.clone(), stub.clone()],
|
||||
None,
|
||||
)
|
||||
.expect("restore with real + stub parentless tasks must succeed");
|
||||
let root_task = conversation
|
||||
.get_root_task()
|
||||
.expect("restored conversation must have a root task");
|
||||
assert_eq!(
|
||||
root_task.id().to_string(),
|
||||
"server-root-id",
|
||||
"expected the real (non-empty) parentless task to win when real is first",
|
||||
);
|
||||
let source = root_task
|
||||
.source()
|
||||
.expect("chosen root must have api::Task source");
|
||||
assert!(
|
||||
!source.messages.is_empty(),
|
||||
"chosen root must have non-empty messages",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_agent_transcript_vehicle_is_excluded_from_navigation() {
|
||||
let conversation = AIConversation::new(false, true);
|
||||
|
||||
assert!(conversation.should_exclude_from_navigation());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_conversation_defaults_unknown_persisted_autoexecute_override() {
|
||||
let _flag = FeatureFlag::RememberFastForwardState.override_enabled(true);
|
||||
@@ -197,3 +716,171 @@ fn fork_artifacts_adds_file_artifacts_to_conversation() {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn waiting_for_events_display_label_is_waiting() {
|
||||
assert_eq!(
|
||||
format!("{}", ConversationStatus::WaitingForEvents),
|
||||
"Waiting"
|
||||
);
|
||||
}
|
||||
|
||||
/// `is_done` returns true only for `Success | Error | Cancelled`;
|
||||
/// `WaitingForEvents` and `Blocked` are not done because the run can still
|
||||
/// resume on its own.
|
||||
#[test]
|
||||
fn is_done_only_includes_success_error_cancelled() {
|
||||
assert!(ConversationStatus::Success.is_done());
|
||||
assert!(ConversationStatus::Error.is_done());
|
||||
assert!(ConversationStatus::Cancelled.is_done());
|
||||
|
||||
assert!(!ConversationStatus::InProgress.is_done());
|
||||
assert!(!ConversationStatus::Blocked {
|
||||
blocked_action: "approve".to_string()
|
||||
}
|
||||
.is_done());
|
||||
assert!(!ConversationStatus::WaitingForEvents.is_done());
|
||||
}
|
||||
|
||||
/// `is_waiting_for_events` is true only for the new variant.
|
||||
#[test]
|
||||
fn is_waiting_for_events_returns_true_only_for_waiting_for_events_variant() {
|
||||
assert!(ConversationStatus::WaitingForEvents.is_waiting_for_events());
|
||||
|
||||
assert!(!ConversationStatus::InProgress.is_waiting_for_events());
|
||||
assert!(!ConversationStatus::Success.is_waiting_for_events());
|
||||
assert!(!ConversationStatus::Error.is_waiting_for_events());
|
||||
assert!(!ConversationStatus::Cancelled.is_waiting_for_events());
|
||||
assert!(!ConversationStatus::Blocked {
|
||||
blocked_action: "approve".to_string()
|
||||
}
|
||||
.is_waiting_for_events());
|
||||
}
|
||||
|
||||
/// A conversation that was yielded via `wait_for_events` at shutdown
|
||||
/// restores as whatever `derive_status_from_root_task` returns (Success
|
||||
/// for a cleanly-streamed last exchange). The unresolved tool call stays
|
||||
/// in the transcript as an orphan; the next outbound request triggers
|
||||
/// the server's existing supersede mechanism to synthesize the matching
|
||||
/// `Cancel`. The waiting state itself is not durable across restart.
|
||||
#[test]
|
||||
fn restored_conversation_does_not_re_enter_waiting_for_events() {
|
||||
let conversation_data: AgentConversationData =
|
||||
serde_json::from_str(r#"{"server_conversation_token":null}"#).unwrap();
|
||||
|
||||
let conversation = restored_conversation(Some(conversation_data));
|
||||
|
||||
assert_eq!(conversation.status(), &ConversationStatus::Success);
|
||||
}
|
||||
|
||||
fn fetched_memory(
|
||||
memory_id: &str,
|
||||
content: &str,
|
||||
memory_store_id: &str,
|
||||
source: Option<api::message::fetched_memory::Source>,
|
||||
) -> api::message::FetchedMemory {
|
||||
api::message::FetchedMemory {
|
||||
memory_id: memory_id.to_string(),
|
||||
content: content.to_string(),
|
||||
memory_store_id: memory_store_id.to_string(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
fn conversation_source(conversation_id: &str) -> Option<api::message::fetched_memory::Source> {
|
||||
Some(api::message::fetched_memory::Source::Conversation(
|
||||
api::message::fetched_memory::Conversation {
|
||||
conversation_id: conversation_id.to_string(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn restored_conversation_with_memories_per_query(
|
||||
memories_per_query: Vec<Vec<api::message::FetchedMemory>>,
|
||||
) -> AIConversation {
|
||||
let messages = memories_per_query
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, memories)| {
|
||||
let request_id = format!("request-{index}");
|
||||
let query = api::Message {
|
||||
fetched_memories: memories,
|
||||
..user_query_message(&format!("user-{index}"), &request_id, "query")
|
||||
};
|
||||
[
|
||||
query,
|
||||
agent_output_message(&format!("agent-{index}"), &request_id),
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
|
||||
AIConversation::new_restored(
|
||||
AIConversationId::new(),
|
||||
vec![api::Task {
|
||||
id: "root-task".to_string(),
|
||||
messages,
|
||||
..Default::default()
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetched_memories_is_empty_when_no_message_has_memories() {
|
||||
let conversation = restored_conversation_with_memories_per_query(vec![vec![]]);
|
||||
|
||||
assert_eq!(conversation.fetched_memories(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetched_memories_preserves_order_across_and_within_messages() {
|
||||
let conversation = restored_conversation_with_memories_per_query(vec![
|
||||
vec![
|
||||
fetched_memory("m1", "first", "store-1", None),
|
||||
fetched_memory("m2", "second", "store-1", None),
|
||||
],
|
||||
vec![fetched_memory("m3", "third", "store-2", None)],
|
||||
]);
|
||||
|
||||
let ids: Vec<String> = conversation
|
||||
.fetched_memories()
|
||||
.into_iter()
|
||||
.map(|memory| memory.memory_id)
|
||||
.collect();
|
||||
assert_eq!(ids, vec!["m1", "m2", "m3"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetched_memories_dedupes_keeping_first_position_and_latest_data() {
|
||||
let conversation = restored_conversation_with_memories_per_query(vec![
|
||||
vec![
|
||||
fetched_memory("m1", "old content", "store-1", None),
|
||||
fetched_memory("m2", "other", "store-1", None),
|
||||
],
|
||||
vec![
|
||||
fetched_memory(
|
||||
"m1",
|
||||
"new content",
|
||||
"store-1",
|
||||
conversation_source("conversation-1"),
|
||||
),
|
||||
fetched_memory("m1", "same memory id different store", "store-2", None),
|
||||
],
|
||||
]);
|
||||
|
||||
let memories = conversation.fetched_memories();
|
||||
assert_eq!(
|
||||
memories,
|
||||
vec![
|
||||
fetched_memory(
|
||||
"m1",
|
||||
"new content",
|
||||
"store-1",
|
||||
conversation_source("conversation-1"),
|
||||
),
|
||||
fetched_memory("m2", "other", "store-1", None),
|
||||
fetched_memory("m1", "same memory id different store", "store-2", None),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,10 @@ use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use api::message::tool_call::Tool;
|
||||
use api::message::tool_call_result::Result as ToolCallResultType;
|
||||
use api::message::Message;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::task::helper::{SubagentExt, ToolExt};
|
||||
|
||||
@@ -243,7 +242,8 @@ fn write_task_messages(
|
||||
| Message::SystemQuery(_)
|
||||
| Message::CodeReview(_)
|
||||
| Message::ServerEvent(_)
|
||||
| Message::InvokeSkill(_) => {}
|
||||
| Message::InvokeSkill(_)
|
||||
| Message::OrchestrationConfigSnapshot(_) => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -507,6 +507,19 @@ fn write_tool_call_args(out: &mut String, tool: &Tool) {
|
||||
out.push_str(&format!(" - deleted: {}\n", df.file_path));
|
||||
}
|
||||
}
|
||||
Tool::RunAgents(o) => {
|
||||
out.push_str(&format!(
|
||||
"summary: \"{}\"\n",
|
||||
escape_yaml_string(&o.summary)
|
||||
));
|
||||
out.push_str("agents:\n");
|
||||
for cfg in &o.agent_run_configs {
|
||||
out.push_str(&format!(
|
||||
" - name: \"{}\"\n",
|
||||
escape_yaml_string(&cfg.name)
|
||||
));
|
||||
}
|
||||
}
|
||||
// No additional args worth serializing.
|
||||
Tool::ReadShellCommandOutput(_)
|
||||
| Tool::UseComputer(_)
|
||||
@@ -519,13 +532,65 @@ fn write_tool_call_args(out: &mut String, tool: &Tool) {
|
||||
| Tool::InitProject(_)
|
||||
| Tool::Server(_)
|
||||
| Tool::Subagent(_)
|
||||
| Tool::TransferShellCommandControlToUser(_) => {}
|
||||
| Tool::TransferShellCommandControlToUser(_)
|
||||
| Tool::WaitForEvents(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes content from structured tool call results.
|
||||
fn write_tool_call_result_content(out: &mut String, result: &ToolCallResultType) {
|
||||
match result {
|
||||
ToolCallResultType::RunAgentsResult(r) => match &r.outcome {
|
||||
Some(api::run_agents_result::Outcome::Launched(launched)) => {
|
||||
out.push_str("status: launched\n");
|
||||
out.push_str(&format!("agent_count: {}\n", launched.agents.len()));
|
||||
if !launched.agents.is_empty() {
|
||||
out.push_str("agents:\n");
|
||||
for agent in &launched.agents {
|
||||
out.push_str(&format!(
|
||||
" - name: \"{}\"\n",
|
||||
escape_yaml_string(&agent.name)
|
||||
));
|
||||
match &agent.result {
|
||||
Some(api::run_agents_result::agent_outcome::Result::Launched(
|
||||
launched,
|
||||
)) => {
|
||||
out.push_str(" status: launched\n");
|
||||
out.push_str(&format!(" agent_id: {}\n", launched.agent_id));
|
||||
}
|
||||
Some(api::run_agents_result::agent_outcome::Result::Failed(failed)) => {
|
||||
out.push_str(" status: failed\n");
|
||||
out.push_str(&format!(
|
||||
" error: \"{}\"\n",
|
||||
escape_yaml_string(&failed.error)
|
||||
));
|
||||
}
|
||||
None => {
|
||||
out.push_str(" status: unknown\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
out.push_str(
|
||||
"next_step: \"Use send_message_to_agent with the existing agent_id instead of running agents again.\"\n",
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(api::run_agents_result::Outcome::Denied(denied)) => {
|
||||
out.push_str("status: launch_denied\n");
|
||||
out.push_str(&format!(
|
||||
"reason: \"{}\"\n",
|
||||
escape_yaml_string(&denied.reason)
|
||||
));
|
||||
}
|
||||
Some(api::run_agents_result::Outcome::Failure(failure)) => {
|
||||
out.push_str("status: failure\n");
|
||||
out.push_str(&format!(
|
||||
"error: \"{}\"\n",
|
||||
escape_yaml_string(&failure.error)
|
||||
));
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
ToolCallResultType::StartAgentV2(r) => match &r.result {
|
||||
Some(api::start_agent_v2_result::Result::Success(s)) => {
|
||||
out.push_str(&format!("agent_id: {}\n", s.agent_id));
|
||||
@@ -535,6 +600,7 @@ fn write_tool_call_result_content(out: &mut String, result: &ToolCallResultType)
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
ToolCallResultType::WaitForEvents(_) => {}
|
||||
ToolCallResultType::RunShellCommand(r) => {
|
||||
if let Some(res) = &r.result {
|
||||
use api::run_shell_command_result::Result;
|
||||
|
||||
@@ -3,12 +3,11 @@ use std::path::Path;
|
||||
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::{base_dir, materialize_tasks_to_yaml};
|
||||
use crate::test_util::ai_agent_tasks::{
|
||||
create_api_subtask, create_api_task, create_message, create_subagent_tool_call_message,
|
||||
};
|
||||
|
||||
use super::{base_dir, materialize_tasks_to_yaml};
|
||||
|
||||
/// Lists filenames (not full paths) in a directory, sorted.
|
||||
fn list_dir_sorted(dir: &Path) -> Vec<String> {
|
||||
let mut entries: Vec<String> = fs::read_dir(dir)
|
||||
@@ -22,6 +21,7 @@ fn list_dir_sorted(dir: &Path) -> Vec<String> {
|
||||
|
||||
fn make_user_query_message(id: &str, task_id: &str, query: &str) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: task_id.to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -45,6 +45,7 @@ fn make_tool_call_message(
|
||||
tool: api::message::tool_call::Tool,
|
||||
) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: task_id.to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -65,6 +66,7 @@ fn make_tool_call_result_message(
|
||||
result: api::message::tool_call_result::Result,
|
||||
) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: task_id.to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -133,6 +135,70 @@ fn mixed_message_types_produce_sequentially_indexed_files() {
|
||||
cleanup_dir(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_agents_result_serializes_agent_ids() {
|
||||
let task_id = "root";
|
||||
let tasks = vec![create_api_task(
|
||||
task_id,
|
||||
vec![make_tool_call_result_message(
|
||||
"m1",
|
||||
task_id,
|
||||
"tc_run_agents",
|
||||
api::message::tool_call_result::Result::RunAgentsResult(api::RunAgentsResult {
|
||||
outcome: Some(api::run_agents_result::Outcome::Launched(
|
||||
api::run_agents_result::Launched {
|
||||
resolved_model_id: "auto".to_string(),
|
||||
resolved_harness: Some(api::Harness {
|
||||
variant: Some(api::harness::Variant::Oz(api::harness::Oz {})),
|
||||
}),
|
||||
resolved_execution_mode: Some(
|
||||
api::run_agents_result::launched::ResolvedExecutionMode::Local(
|
||||
api::run_agents::Local {},
|
||||
),
|
||||
),
|
||||
agents: vec![
|
||||
api::run_agents_result::AgentOutcome {
|
||||
name: "child".to_string(),
|
||||
result: Some(
|
||||
api::run_agents_result::agent_outcome::Result::Launched(
|
||||
api::run_agents_result::LaunchedAgent {
|
||||
agent_id: "agent-123".to_string(),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
api::run_agents_result::AgentOutcome {
|
||||
name: "other".to_string(),
|
||||
result: Some(
|
||||
api::run_agents_result::agent_outcome::Result::Failed(
|
||||
api::run_agents_result::FailedAgent {
|
||||
error: "failed to start".to_string(),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
)),
|
||||
}),
|
||||
)],
|
||||
)];
|
||||
|
||||
let dir = materialize_tasks_to_yaml(&tasks).unwrap();
|
||||
let files = list_dir_sorted(Path::new(&dir));
|
||||
let content = fs::read_to_string(Path::new(&dir).join(&files[0])).unwrap();
|
||||
|
||||
assert!(content.contains("status: launched"));
|
||||
assert!(content.contains("agent_count: 2"));
|
||||
assert!(content.contains("name: \"child\""));
|
||||
assert!(content.contains("agent_id: agent-123"));
|
||||
assert!(content.contains("name: \"other\""));
|
||||
assert!(content.contains("error: \"failed to start\""));
|
||||
assert!(content.contains("Use send_message_to_agent with the existing agent_id"));
|
||||
|
||||
cleanup_dir(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagent_file_and_subdirectory_share_same_index() {
|
||||
let root_id = "root";
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use galaxy_core::ui::{appearance::Appearance, theme::AnsiColorIdentifier};
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::theme::AnsiColorIdentifier;
|
||||
|
||||
use crate::ui_components::{blended_colors, icons::Icon};
|
||||
use crate::ui_components::blended_colors;
|
||||
use crate::ui_components::icons::Icon;
|
||||
|
||||
pub fn todo_list_icon(appearance: &Appearance) -> galaxyui::elements::Icon {
|
||||
galaxyui::elements::Icon::new(
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::*;
|
||||
|
||||
// Helper function to create a basic message
|
||||
fn create_message(id: &str, task_id: &str) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: task_id.to_string(),
|
||||
server_message_data: "server_data".to_string(),
|
||||
@@ -22,6 +24,7 @@ fn create_message(id: &str, task_id: &str) -> api::Message {
|
||||
|
||||
fn create_subagent_tool_call_message(id: &str, task_id: &str, subtask_id: &str) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: task_id.to_string(),
|
||||
server_message_data: "server_data".to_string(),
|
||||
@@ -44,6 +47,7 @@ fn create_subagent_tool_call_message(id: &str, task_id: &str, subtask_id: &str)
|
||||
// Helper function to create a tool call result message.
|
||||
fn create_tool_call_result_message(id: &str, task_id: &str, tool_call_id: &str) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: task_id.to_string(),
|
||||
server_message_data: "server_data".to_string(),
|
||||
|
||||
@@ -12,50 +12,48 @@ mod task_store;
|
||||
pub(super) mod telemetry;
|
||||
pub(super) mod util;
|
||||
|
||||
// Re-export types that were moved to the ai crate.
|
||||
pub use ai::agent::{action::*, action_result::*, AIAgentCitation, FileLocations};
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
|
||||
#[cfg(test)]
|
||||
mod suggestion_test;
|
||||
use crate::ai::block_context::BlockContext;
|
||||
use crate::ai::blocklist::block::view_impl::output::are_all_text_sections_empty;
|
||||
use crate::ai::skills::SkillDescriptor;
|
||||
use crate::code::editor_management::CodeSource;
|
||||
use crate::code_review::comments::{
|
||||
AttachedReviewComment as CodeReviewComment, ReviewCommentBatch,
|
||||
};
|
||||
use crate::search::slash_command_menu::static_commands::commands;
|
||||
use crate::server::server_api::AIApiError;
|
||||
use ai::skills::ParsedSkill;
|
||||
use chrono::{DateTime, Local, TimeDelta};
|
||||
use comment::ReviewComment;
|
||||
use task::TaskId;
|
||||
pub use telemetry::AIIdentifiers;
|
||||
|
||||
use galaxy_editor::render::model::LineCount;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Display;
|
||||
use std::ops::{AddAssign, Deref, DerefMut, Range};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
// Re-export types that were moved to the ai crate.
|
||||
pub use ai::agent::action::*;
|
||||
pub use ai::agent::action_result::*;
|
||||
use ai::agent::orchestration_config::{OrchestrationConfig, OrchestrationConfigStatus};
|
||||
pub use ai::agent::{AIAgentCitation, FileLocations};
|
||||
use ai::skills::ParsedSkill;
|
||||
use chrono::{DateTime, Local, TimeDelta};
|
||||
use comment::ReviewComment;
|
||||
use derivative::Derivative;
|
||||
use markdown_parser::{parse_markdown, FormattedTable, FormattedText, FormattedTextInline};
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use session_sharing_protocol::common::ParticipantId;
|
||||
use task::TaskId;
|
||||
pub use telemetry::AIIdentifiers;
|
||||
use uuid::Uuid;
|
||||
use galaxy_core::channel::ChannelState;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use warp_editor::render::model::LineCount;
|
||||
use warp_multi_agent_api::{diff_hunk as diff_hunk_api, AgentEvent, AgentType};
|
||||
|
||||
pub use self::api::{MaybeAIAgentOutputMessage, MessageToAIAgentOutputMessageError};
|
||||
use super::llms::LLMId;
|
||||
use crate::ai::block_context::BlockContext;
|
||||
use crate::ai::blocklist::block::view_impl::output::are_all_text_sections_empty;
|
||||
use crate::ai::skills::SkillDescriptor;
|
||||
use crate::ai_assistant::execution_context::WarpAiExecutionContext;
|
||||
use crate::code::editor_management::CodeSource;
|
||||
use crate::code_review::comments::{
|
||||
AttachedReviewComment as CodeReviewComment, ReviewCommentBatch,
|
||||
};
|
||||
use crate::search::slash_command_menu::static_commands::commands;
|
||||
use crate::server::server_api::{AIApiError, DeserializationError};
|
||||
use crate::terminal::model::block::BlockId;
|
||||
use crate::terminal::shell::ShellType;
|
||||
use crate::terminal::view::block_onboarding::onboarding_agentic_suggestions_block::OnboardingChipType;
|
||||
use crate::TelemetryEvent;
|
||||
use derivative::Derivative;
|
||||
use markdown_parser::{parse_markdown, FormattedTable, FormattedText, FormattedTextInline};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use session_sharing_protocol::common::ParticipantId;
|
||||
|
||||
use super::llms::LLMId;
|
||||
|
||||
/// A server supplied ID for a specific AI generated output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -84,6 +82,8 @@ impl ServerOutputId {
|
||||
pub enum CancellationReason {
|
||||
/// The user explicitly cancelled without providing a follow-up.
|
||||
ManuallyCancelled,
|
||||
/// Warp automatically cancelled the local run so it could continue in Cloud Mode.
|
||||
AutomaticCloudHandoff,
|
||||
|
||||
/// The user submitted a follow-up query during streaming which implicitly cancelled the current one.
|
||||
FollowUpSubmitted {
|
||||
@@ -99,22 +99,65 @@ pub enum CancellationReason {
|
||||
// The user deleted the conversation while it was in progress.
|
||||
Deleted,
|
||||
|
||||
/// The long-running command completed while the agent was still streaming.
|
||||
/// The long-running command completed while the agent was still streaming a response started via inline agent view.
|
||||
/// This should be treated as a successful completion, not a cancellation.
|
||||
OptimisticCLISubagentCompletion,
|
||||
/// Note this is only used for inline agent view (user starting an agent to monitor an already running command),
|
||||
/// not when CLI subagent monitors a requested command.
|
||||
CommandFinishedDuringInlineAgentView,
|
||||
|
||||
/// The user manually took control of a long-running command away from the agent.
|
||||
/// The agent conversation is still in progress — it will resume after the command
|
||||
/// finishes or once the user hands control back. The stream is cancelled only to
|
||||
/// stop the CLI subagent monitoring loop, not to end the conversation.
|
||||
CLISubagentUserTakeover,
|
||||
|
||||
/// An agent-issued command caused the shell process to exit (e.g. it ran
|
||||
/// `exit`, or ran a failing command after enabling `set -e`). The in-flight
|
||||
/// stream/actions are cancelled to stop work, but the conversation is
|
||||
/// finalized as a terminal `Error` (with a shell-exit message) by the
|
||||
/// controller rather than reported as a user cancellation.
|
||||
AgentExitedShell,
|
||||
}
|
||||
|
||||
/// How a [`CancellationReason`] maps to the conversation's resulting status.
|
||||
/// This is the single source of truth consumed by the stream- and
|
||||
/// action-cancellation machinery; see [`CancellationReason::conversation_outcome`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum CancellationOutcome {
|
||||
/// Leave the conversation `InProgress`; it will continue on its own (a
|
||||
/// follow-up request or a resumed long-running command) without further user
|
||||
/// input.
|
||||
KeepInProgress,
|
||||
/// Finalize the conversation as a successful completion (`Success`).
|
||||
Succeeded,
|
||||
/// Finalize the conversation as a user cancellation (`Cancelled`).
|
||||
Cancelled,
|
||||
/// Terminal, but a dedicated path (not the cancellation machinery) writes the
|
||||
/// status — the cancellation is only a stop signal and must not stamp a status.
|
||||
/// Currently used for shell exit, which is finalized as `Error` by
|
||||
/// `fail_conversation_due_to_shell_exit`. Unlike `KeepInProgress`, the
|
||||
/// conversation is ending; only the status write is suppressed.
|
||||
FinalizedExternally,
|
||||
}
|
||||
|
||||
impl Display for CancellationReason {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
CancellationReason::ManuallyCancelled => write!(f, "manual cancellation"),
|
||||
CancellationReason::AutomaticCloudHandoff => write!(f, "automatic cloud handoff"),
|
||||
CancellationReason::FollowUpSubmitted { .. } => write!(f, "follow-up submission"),
|
||||
CancellationReason::UserCommandExecuted => write!(f, "user command execution"),
|
||||
CancellationReason::Reverted => write!(f, "revert"),
|
||||
CancellationReason::Deleted => write!(f, "deleted"),
|
||||
CancellationReason::OptimisticCLISubagentCompletion => {
|
||||
CancellationReason::CommandFinishedDuringInlineAgentView => {
|
||||
write!(f, "LRC command completed")
|
||||
}
|
||||
CancellationReason::CLISubagentUserTakeover => {
|
||||
write!(f, "CLI subagent user takeover")
|
||||
}
|
||||
CancellationReason::AgentExitedShell => {
|
||||
write!(f, "agent command exited the shell")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,8 +182,36 @@ impl CancellationReason {
|
||||
matches!(self, CancellationReason::Reverted)
|
||||
}
|
||||
|
||||
pub fn is_lrc_command_completed(&self) -> bool {
|
||||
matches!(self, CancellationReason::OptimisticCLISubagentCompletion)
|
||||
/// How a cancellation reason maps to the
|
||||
/// conversation's resulting status. Every site that finalizes a cancelled
|
||||
/// stream or action consults this instead of re-deriving the disposition,
|
||||
/// so the reason -> status mapping lives in one exhaustive place.
|
||||
/// Note that sometimes the action result is treated as authoritative for determining
|
||||
/// conversation status even when there is a cancellation reason (taking priority over this)
|
||||
pub fn conversation_outcome(&self) -> CancellationOutcome {
|
||||
match self {
|
||||
// The conversation continues without further user input (a follow-up
|
||||
// request or a resumed long-running command drives it forward), so
|
||||
// its status must stay InProgress.
|
||||
CancellationReason::FollowUpSubmitted {
|
||||
is_for_same_conversation: true,
|
||||
}
|
||||
| CancellationReason::CLISubagentUserTakeover => CancellationOutcome::KeepInProgress,
|
||||
// A long-running command finishing (optimistically) or a revert are
|
||||
// successful completions rather than cancellations.
|
||||
CancellationReason::CommandFinishedDuringInlineAgentView
|
||||
| CancellationReason::Reverted => CancellationOutcome::Succeeded,
|
||||
// The shell died under the agent; a dedicated path finalizes this as a
|
||||
// terminal `Error`, so the cancellation machinery must not stamp a status.
|
||||
CancellationReason::AgentExitedShell => CancellationOutcome::FinalizedExternally,
|
||||
CancellationReason::ManuallyCancelled
|
||||
| CancellationReason::AutomaticCloudHandoff
|
||||
| CancellationReason::UserCommandExecuted
|
||||
| CancellationReason::Deleted
|
||||
| CancellationReason::FollowUpSubmitted {
|
||||
is_for_same_conversation: false,
|
||||
} => CancellationOutcome::Cancelled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,6 +472,9 @@ pub struct OutputModelInfo {
|
||||
pub model_id: LLMId,
|
||||
pub display_name: String,
|
||||
pub is_fallback: bool,
|
||||
/// When the provider-side prompt cache for this request is expected to
|
||||
/// expire. `None` means unknown / no cache-expiry info.
|
||||
pub prompt_cache_expires_at: Option<DateTime<Local>>,
|
||||
}
|
||||
|
||||
impl Display for AIAgentOutput {
|
||||
@@ -615,9 +689,11 @@ impl AIAgentOutput {
|
||||
}
|
||||
|
||||
/// Represents user visible errors.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RenderableAIError {
|
||||
QuotaLimit,
|
||||
QuotaLimit {
|
||||
user_display_message: Option<String>,
|
||||
},
|
||||
ServerOverloaded,
|
||||
InternalWarpError,
|
||||
ContextWindowExceeded(String),
|
||||
@@ -628,16 +704,59 @@ pub enum RenderableAIError {
|
||||
AwsBedrockCredentialsExpiredOrInvalid {
|
||||
model_name: String,
|
||||
},
|
||||
/// A transient network failure (lost connection or truncated response stream). Carries its
|
||||
/// own complete user-facing copy; `kind` preserves the structured cause (including the raw
|
||||
/// API error) so user reports can disambiguate the different causes behind the shared message.
|
||||
TransientNetworkError {
|
||||
kind: TransientNetworkErrorKind,
|
||||
will_attempt_resume: bool,
|
||||
/// When `will_attempt_resume` is true, this indicates whether we're waiting for network
|
||||
/// connectivity before attempting the resume.
|
||||
waiting_for_network: bool,
|
||||
},
|
||||
Other {
|
||||
error_message: String,
|
||||
will_attempt_resume: bool,
|
||||
/// When `will_attempt_resume` is true, this indicates whether we're waiting for network
|
||||
/// connectivity before attempting the resume.
|
||||
waiting_for_network: bool,
|
||||
/// True when the error originates from a user-side issue (e.g., model not allowed,
|
||||
/// blocked due to fraud, plan restriction). Maps the task to FAILED state instead of ERROR.
|
||||
is_user_error: bool,
|
||||
},
|
||||
/// An agent-issued command caused the shell process to exit, so the run
|
||||
/// cannot continue. Surfaced as a terminal failure (FAILED) rather than a
|
||||
/// user cancellation.
|
||||
AgentExitedShell,
|
||||
}
|
||||
|
||||
impl RenderableAIError {
|
||||
const TRANSIENT_NETWORK_ERROR_MESSAGE: &'static str =
|
||||
"Warp lost connection while receiving the agent response. This is usually temporary.";
|
||||
/// User-facing message shown when an agent-issued command exits the shell.
|
||||
pub const AGENT_EXITED_SHELL_MESSAGE: &'static str =
|
||||
"The shell exited while the agent was running a command, so the run could not continue. Ensure the agent is not asked to run commands or source scripts that can exit the shell.";
|
||||
/// Creates a transient network error. `kind` is the structured cause (including the raw API
|
||||
/// error where one exists), preserved so user reports can disambiguate the different causes
|
||||
/// behind the shared user-facing copy.
|
||||
pub fn transient_network_error(
|
||||
will_attempt_resume: bool,
|
||||
waiting_for_network: bool,
|
||||
kind: TransientNetworkErrorKind,
|
||||
) -> Self {
|
||||
Self::TransientNetworkError {
|
||||
kind,
|
||||
will_attempt_resume,
|
||||
waiting_for_network,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_transient_network_transport_error(error: &reqwest::Error) -> bool {
|
||||
// If reqwest has an HTTP status, the server responded. Preserve the existing generic
|
||||
// rendering for those failures rather than calling them lost connections.
|
||||
error.status().is_none()
|
||||
}
|
||||
|
||||
pub fn is_invalid_api_key(&self) -> bool {
|
||||
matches!(self, Self::InvalidApiKey { .. })
|
||||
}
|
||||
@@ -653,20 +772,99 @@ impl RenderableAIError {
|
||||
Self::Other {
|
||||
will_attempt_resume: true,
|
||||
..
|
||||
} | Self::TransientNetworkError {
|
||||
will_attempt_resume: true,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether the failed-output UI should be suppressed while an automatic resume is in
|
||||
/// flight. Release builds stay quiet so transient blips that recover on their own
|
||||
/// don't surface an alarming error; dogfood builds (Local/Dev) keep the old, more
|
||||
/// aggressive behavior so developers still see every transport failure.
|
||||
pub fn should_suppress_during_recovery(&self) -> bool {
|
||||
self.will_attempt_resume() && !ChannelState::channel().is_dogfood()
|
||||
}
|
||||
|
||||
/// Constructs a generic [`RenderableAIError::Other`] from a message.
|
||||
/// `is_user_error` selects the task classification (true → FAILED, false →
|
||||
/// ERROR). The resume/network flags are false: this is for terminal,
|
||||
/// out-of-band errors that are not auto-resumed.
|
||||
pub fn other(error_message: impl Into<String>, is_user_error: bool) -> Self {
|
||||
Self::Other {
|
||||
error_message: error_message.into(),
|
||||
will_attempt_resume: false,
|
||||
waiting_for_network: false,
|
||||
is_user_error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AIApiError> for RenderableAIError {
|
||||
fn from(value: &AIApiError) -> Self {
|
||||
match value {
|
||||
AIApiError::QuotaLimit => Self::QuotaLimit,
|
||||
/// The cause behind a [`RenderableAIError::TransientNetworkError`]. Kept structured (rather than
|
||||
/// collapsed to a free-form string) so user reports preserve the raw error; rendered to text only
|
||||
/// at display time.
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub enum TransientNetworkErrorKind {
|
||||
/// A lost connection or truncated response stream — the raw underlying API error. Rendered via
|
||||
/// `Debug` so reports preserve the full structured error rather than its terse `Display`.
|
||||
#[error("{0:?}")]
|
||||
Api(Arc<AIApiError>),
|
||||
/// The response stream completed with an unfinished exchange and no error event.
|
||||
#[error("stream completed with an unfinished exchange and no error event")]
|
||||
UnfinishedExchange,
|
||||
/// The conversation was left in a transient-error state but the last exchange carried no
|
||||
/// structured error to surface.
|
||||
#[error("no structured error on the last exchange")]
|
||||
MissingExchangeError,
|
||||
}
|
||||
|
||||
impl From<&Arc<AIApiError>> for RenderableAIError {
|
||||
fn from(value: &Arc<AIApiError>) -> Self {
|
||||
// Non-retryable 4xx errors (403 fraud block, 400 model/plan restriction, etc.)
|
||||
// are user-originating — map them to a user error so the task reaches FAILED
|
||||
// state rather than ERROR state.
|
||||
let is_user_error = !value.is_recoverable();
|
||||
match value.as_ref() {
|
||||
AIApiError::QuotaLimit {
|
||||
user_display_message,
|
||||
} => Self::QuotaLimit {
|
||||
user_display_message: user_display_message.clone(),
|
||||
},
|
||||
AIApiError::ServerOverloaded => Self::ServerOverloaded,
|
||||
_ => Self::Other {
|
||||
AIApiError::Transport(error)
|
||||
| AIApiError::Deserialization(DeserializationError::Transport(error)) => {
|
||||
// A transport error with no HTTP status is a lost-connection failure; one that
|
||||
// carries a status means the server responded, so it gets generic rendering.
|
||||
if Self::is_transient_network_transport_error(error) {
|
||||
Self::transient_network_error(
|
||||
false,
|
||||
false,
|
||||
TransientNetworkErrorKind::Api(value.clone()),
|
||||
)
|
||||
} else {
|
||||
Self::Other {
|
||||
error_message: format!("Request failed with error: {value:?}"),
|
||||
will_attempt_resume: false,
|
||||
waiting_for_network: false,
|
||||
is_user_error,
|
||||
}
|
||||
}
|
||||
}
|
||||
AIApiError::UnexpectedEof => Self::transient_network_error(
|
||||
false,
|
||||
false,
|
||||
TransientNetworkErrorKind::Api(value.clone()),
|
||||
),
|
||||
AIApiError::Deserialization(DeserializationError::Json(_))
|
||||
| AIApiError::NoContextFound
|
||||
| AIApiError::ErrorStatus(_, _)
|
||||
| AIApiError::Other(_)
|
||||
| AIApiError::Stream { .. } => Self::Other {
|
||||
error_message: format!("Request failed with error: {value:?}"),
|
||||
will_attempt_resume: false,
|
||||
waiting_for_network: false,
|
||||
is_user_error,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -675,7 +873,15 @@ impl From<&AIApiError> for RenderableAIError {
|
||||
impl Display for RenderableAIError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::QuotaLimit => write!(f, "Quota limit reached."),
|
||||
Self::QuotaLimit {
|
||||
user_display_message,
|
||||
} => {
|
||||
if let Some(message) = user_display_message {
|
||||
write!(f, "{message}")
|
||||
} else {
|
||||
write!(f, "Quota limit reached.")
|
||||
}
|
||||
}
|
||||
Self::ServerOverloaded => {
|
||||
write!(f, "Warp is currently overloaded. Please try again later.")
|
||||
}
|
||||
@@ -692,7 +898,15 @@ impl Display for RenderableAIError {
|
||||
"AWS Bedrock credentials expired or invalid for {model_name}"
|
||||
)
|
||||
}
|
||||
Self::TransientNetworkError { kind, .. } => {
|
||||
write!(
|
||||
f,
|
||||
"{}\n\nDebug info: {kind}",
|
||||
Self::TRANSIENT_NETWORK_ERROR_MESSAGE
|
||||
)
|
||||
}
|
||||
Self::Other { error_message, .. } => write!(f, "{error_message}"),
|
||||
Self::AgentExitedShell => write!(f, "{}", Self::AGENT_EXITED_SHELL_MESSAGE),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -717,13 +931,19 @@ impl ProgrammingLanguage {
|
||||
#[cfg_attr(target_family = "wasm", allow(unused))]
|
||||
pub fn to_extension(&self) -> Option<&str> {
|
||||
match self {
|
||||
// The arms below cover both canonical language names emitted by the agent (e.g.
|
||||
// "rust", "kotlin") and common markdown code-fence aliases (e.g. "rs", "kt") to keep
|
||||
// syntax highlighting working when the model uses either. The set of recognized
|
||||
// languages here is kept in sync with `SUPPORTED_LANGUAGES` in the `languages` crate.
|
||||
Self::Other(language) => match language.to_lowercase().as_str() {
|
||||
"rust" => Some("rs"),
|
||||
"go" => Some("go"),
|
||||
"python" => Some("py"),
|
||||
"javascript" => Some("js"),
|
||||
"typescript" => Some("ts"),
|
||||
"yaml" => Some("yaml"),
|
||||
"rust" | "rs" => Some("rs"),
|
||||
"go" | "golang" => Some("go"),
|
||||
"python" | "py" => Some("py"),
|
||||
"javascript" | "js" => Some("js"),
|
||||
"typescript" | "ts" => Some("ts"),
|
||||
"jsx" => Some("jsx"),
|
||||
"tsx" => Some("tsx"),
|
||||
"yaml" | "yml" => Some("yaml"),
|
||||
"cpp" | "c++" => Some("cpp"),
|
||||
"java" => Some("java"),
|
||||
"groovy" => Some("java"),
|
||||
@@ -733,17 +953,23 @@ impl ProgrammingLanguage {
|
||||
"css" => Some("css"),
|
||||
"c" => Some("c"),
|
||||
"json" => Some("json"),
|
||||
"hcl" => Some("hcl"),
|
||||
"jq" => Some("jq"),
|
||||
"hcl" | "terraform" | "tf" => Some("hcl"),
|
||||
"lua" => Some("lua"),
|
||||
"ruby" => Some("rb"),
|
||||
"ruby" | "rb" => Some("rb"),
|
||||
"php" => Some("php"),
|
||||
"toml" => Some("toml"),
|
||||
"swift" => Some("swift"),
|
||||
"kotlin" => Some("kt"),
|
||||
"kotlin" | "kt" => Some("kt"),
|
||||
"powershell" => Some("ps1"),
|
||||
"elixir" => Some("exs"),
|
||||
"scala" => Some("scala"),
|
||||
"sql" => Some("sql"),
|
||||
"objective-c" | "objc" => Some("m"),
|
||||
"starlark" => Some("bzl"),
|
||||
"xml" => Some("xml"),
|
||||
"vue" => Some("vue"),
|
||||
"dockerfile" | "docker" | "containerfile" => Some("dockerfile"),
|
||||
_ => None,
|
||||
},
|
||||
Self::Shell(ShellType::PowerShell) => Some("ps1"),
|
||||
@@ -1513,9 +1739,12 @@ pub enum SubagentType {
|
||||
Summarization,
|
||||
ConversationSearch {
|
||||
query: Option<String>,
|
||||
/// The ID of the conversation being searched. None when searching the
|
||||
/// current conversation.
|
||||
/// Search targets are mutually exclusive; at most one of `conversation_id` or
|
||||
/// `agent_run_id` should be populated for a single conversation search subagent.
|
||||
/// The ID of the conversation being searched.
|
||||
conversation_id: Option<String>,
|
||||
/// The ID of the agent run being searched.
|
||||
agent_run_id: Option<String>,
|
||||
},
|
||||
WarpDocumentationSearch,
|
||||
Unknown,
|
||||
@@ -2010,6 +2239,30 @@ pub enum AIAgentContext {
|
||||
branch: Option<String>,
|
||||
},
|
||||
|
||||
/// Information about the git repository in the current working directory.
|
||||
Repository {
|
||||
/// The repository name (e.g. "warp-internal").
|
||||
name: String,
|
||||
/// The repository owner/organization (e.g. "warpdotdev"), if determinable from the remote URL.
|
||||
owner: Option<String>,
|
||||
},
|
||||
|
||||
/// Information about the GitHub pull request associated with the current branch.
|
||||
PullRequest {
|
||||
/// The pull request number.
|
||||
#[serde(default, deserialize_with = "deserialize_pull_request_number")]
|
||||
number: i32,
|
||||
/// The pull request state (for example, `OPEN`, `MERGED`, or `CLOSED`).
|
||||
#[serde(default)]
|
||||
state: String,
|
||||
/// Whether the pull request is marked as draft.
|
||||
#[serde(default)]
|
||||
draft: bool,
|
||||
/// The pull request's base branch.
|
||||
#[serde(default)]
|
||||
base_branch: String,
|
||||
},
|
||||
|
||||
/// List of available skills is provided to the agent during initialization
|
||||
/// or when updated.
|
||||
Skills {
|
||||
@@ -2020,6 +2273,37 @@ pub enum AIAgentContext {
|
||||
Block(Box<BlockContext>),
|
||||
}
|
||||
|
||||
fn deserialize_pull_request_number<'de, D>(deserializer: D) -> Result<i32, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = serde_json::Value::deserialize(deserializer)?;
|
||||
match value {
|
||||
serde_json::Value::Null => Ok(0),
|
||||
serde_json::Value::String(s) => {
|
||||
if !s.chars().all(|c| c.is_ascii_digit()) {
|
||||
return Ok(0);
|
||||
}
|
||||
Ok(s.parse()
|
||||
.ok()
|
||||
.filter(|number| *number > 0)
|
||||
.unwrap_or_default())
|
||||
}
|
||||
serde_json::Value::Number(n) => {
|
||||
let Some(number) = n.as_i64() else {
|
||||
return Ok(0);
|
||||
};
|
||||
Ok(i32::try_from(number)
|
||||
.ok()
|
||||
.filter(|number| *number > 0)
|
||||
.unwrap_or_default())
|
||||
}
|
||||
value => Err(serde::de::Error::custom(format!(
|
||||
"expected string or number for pull request number, got {value}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct ImageContext {
|
||||
/// Base64-encoded image data.
|
||||
@@ -2229,16 +2513,12 @@ pub enum StaticQueryType {
|
||||
Code,
|
||||
Deploy,
|
||||
SomethingElse,
|
||||
CustomOnboardingRequest,
|
||||
EvaluationSuite,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum EntrypointType {
|
||||
Onboarding {
|
||||
chip_type: OnboardingChipType,
|
||||
},
|
||||
PromptSuggestion {
|
||||
is_static: bool,
|
||||
is_coding: bool,
|
||||
@@ -2327,6 +2607,31 @@ pub enum UserQueryMode {
|
||||
Orchestrate,
|
||||
}
|
||||
|
||||
pub fn extract_user_query_mode(query: String) -> (String, UserQueryMode) {
|
||||
if let Some(query) = commands::strip_command_prefix(&query, commands::PLAN_NAME) {
|
||||
(query, UserQueryMode::Plan)
|
||||
} else if let Some(query) = commands::strip_command_prefix(&query, commands::ORCHESTRATE_NAME) {
|
||||
(query, UserQueryMode::Orchestrate)
|
||||
} else {
|
||||
(query, UserQueryMode::Normal)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconstructs the display form of a user query that has been stripped via
|
||||
/// [`extract_user_query_mode`], by re-prepending the slash-command prefix
|
||||
/// associated with [`UserQueryMode`].
|
||||
///
|
||||
/// This is the inverse of [`extract_user_query_mode`] and the canonical way
|
||||
/// for UI to render a stored `(mode, query)` pair so the displayed prompt
|
||||
/// always matches what the user originally submitted.
|
||||
pub fn display_user_query_with_mode(mode: UserQueryMode, query: &str) -> String {
|
||||
match mode {
|
||||
UserQueryMode::Normal => query.to_owned(),
|
||||
UserQueryMode::Plan => format!("{} {query}", commands::PLAN.name),
|
||||
UserQueryMode::Orchestrate => format!("{} {query}", commands::ORCHESTRATE.name),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(zachbai): Refactor this to consolidate with `LongRunningCommandSnapshot` and `Snapshot`
|
||||
// variants of `ReadShellCommandOutputResult` and `WriteToLongRunningShellCommandResult`.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -2422,6 +2727,7 @@ pub enum AIAgentInput {
|
||||
|
||||
SummarizeConversation {
|
||||
prompt: Option<String>,
|
||||
context: Arc<[AIAgentContext]>,
|
||||
},
|
||||
|
||||
/// Invoke a skill. The skill content is passed as instructions to the agent.
|
||||
@@ -2468,6 +2774,15 @@ pub enum AIAgentInput {
|
||||
suggestion: PassiveSuggestionResultType,
|
||||
context: Arc<[AIAgentContext]>,
|
||||
},
|
||||
|
||||
/// Piggybacked orchestration config update from the plan card.
|
||||
/// Sent on the next outbound request after the user edits the
|
||||
/// config block or toggles approval.
|
||||
OrchestrationConfigUpdate {
|
||||
plan_id: String,
|
||||
config: OrchestrationConfig,
|
||||
status: OrchestrationConfigStatus,
|
||||
},
|
||||
}
|
||||
|
||||
/// Data for a single message received by an agent from another agent.
|
||||
@@ -2525,7 +2840,7 @@ impl Display for AIAgentInput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UserQuery { .. } => {
|
||||
write!(f, "UserQuery: {}", self.user_query().unwrap_or_default())
|
||||
write!(f, "UserQuery: {}", self.display_query().unwrap_or_default())
|
||||
}
|
||||
Self::AutoCodeDiffQuery { query, .. } => {
|
||||
write!(f, "AutoCodeDiffQuery: {query}")
|
||||
@@ -2561,24 +2876,23 @@ impl Display for AIAgentInput {
|
||||
write!(f, "EventsFromAgents({} events)", events.len())
|
||||
}
|
||||
Self::PassiveSuggestionResult { .. } => write!(f, "PassiveSuggestionResult"),
|
||||
Self::OrchestrationConfigUpdate { .. } => write!(f, "OrchestrationConfigUpdate"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AIAgentInput {
|
||||
pub fn user_query(&self) -> Option<String> {
|
||||
/// Display text for any input that surfaces a prompt-like query in the UI
|
||||
/// (typed queries, slash commands, skill invocations, etc.). Unlike
|
||||
/// [`Self::is_user_query`], which strictly matches the `UserQuery` variant,
|
||||
/// this returns `Some` for several input variants.
|
||||
pub fn display_query(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::UserQuery {
|
||||
query,
|
||||
user_query_mode,
|
||||
..
|
||||
} => match user_query_mode {
|
||||
UserQueryMode::Plan => Some(format!("{} {query}", commands::PLAN.name)),
|
||||
UserQueryMode::Orchestrate => {
|
||||
Some(format!("{} {query}", commands::ORCHESTRATE.name))
|
||||
}
|
||||
UserQueryMode::Normal => Some(query.clone()),
|
||||
},
|
||||
} => Some(display_user_query_with_mode(*user_query_mode, query)),
|
||||
Self::CreateNewProject { query, .. } => Some(query.clone()),
|
||||
Self::CloneRepository {
|
||||
clone_repo_url: url,
|
||||
@@ -2624,7 +2938,8 @@ impl AIAgentInput {
|
||||
| Self::StartFromAmbientRunPrompt { .. }
|
||||
| Self::MessagesReceivedFromAgents { .. }
|
||||
| Self::EventsFromAgents { .. }
|
||||
| Self::PassiveSuggestionResult { .. } => None,
|
||||
| Self::PassiveSuggestionResult { .. }
|
||||
| Self::OrchestrationConfigUpdate { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2634,7 +2949,7 @@ impl AIAgentInput {
|
||||
&self,
|
||||
initial_conversation_query: Option<&String>,
|
||||
) -> Option<String> {
|
||||
let mut query = self.user_query()?;
|
||||
let mut query = self.display_query()?;
|
||||
if self
|
||||
.user_query_mode()
|
||||
.is_none_or(|mode| matches!(mode, UserQueryMode::Normal))
|
||||
@@ -2719,9 +3034,10 @@ impl AIAgentInput {
|
||||
| Self::InvokeSkill { context, .. }
|
||||
| Self::StartFromAmbientRunPrompt { context, .. }
|
||||
| Self::PassiveSuggestionResult { context, .. } => Some(context),
|
||||
Self::SummarizeConversation { .. }
|
||||
| Self::MessagesReceivedFromAgents { .. }
|
||||
| Self::EventsFromAgents { .. } => None,
|
||||
Self::SummarizeConversation { context, .. } => Some(context),
|
||||
Self::MessagesReceivedFromAgents { .. }
|
||||
| Self::EventsFromAgents { .. }
|
||||
| Self::OrchestrationConfigUpdate { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2752,7 +3068,8 @@ impl AIAgentInput {
|
||||
| Self::StartFromAmbientRunPrompt { .. }
|
||||
| Self::MessagesReceivedFromAgents { .. }
|
||||
| Self::EventsFromAgents { .. }
|
||||
| Self::PassiveSuggestionResult { .. } => None,
|
||||
| Self::PassiveSuggestionResult { .. }
|
||||
| Self::OrchestrationConfigUpdate { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2859,7 +3176,7 @@ impl AIAgentExchange {
|
||||
let user_queries: Vec<String> = self
|
||||
.input
|
||||
.iter()
|
||||
.filter_map(|input| input.user_query())
|
||||
.filter_map(|input| input.display_query())
|
||||
.collect();
|
||||
user_queries.join("\n")
|
||||
}
|
||||
@@ -2909,7 +3226,9 @@ impl AIAgentExchange {
|
||||
}
|
||||
|
||||
pub fn has_user_query(&self) -> bool {
|
||||
self.input.iter().any(|input| input.user_query().is_some())
|
||||
self.input
|
||||
.iter()
|
||||
.any(|input| input.display_query().is_some())
|
||||
}
|
||||
|
||||
pub fn has_accepted_file_edit(&self) -> bool {
|
||||
@@ -2970,25 +3289,7 @@ pub struct RequestMetadata {
|
||||
pub is_auto_resume_after_error: bool,
|
||||
}
|
||||
|
||||
/// A globally unique ID for a suggested objects.
|
||||
///
|
||||
/// This is used for telemetry purposes to track and connect both:
|
||||
/// - Suggested objects generated by the AI agent
|
||||
/// - The corresponding objects stored in the cloud (if the suggestion was accepted)
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SuggestedLoggingId(String);
|
||||
|
||||
impl Display for SuggestedLoggingId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SuggestedLoggingId {
|
||||
fn from(value: String) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
pub use cloud_object_models::SuggestedLoggingId;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct SuggestedRule {
|
||||
@@ -3034,5 +3335,5 @@ impl Suggestions {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mod_test.rs"]
|
||||
#[path = "mod_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
|
||||
use warp_multi_agent_api::{FileContent, FileContentLineRange};
|
||||
|
||||
use crate::ai::agent::{
|
||||
AIAgentOutput, AIAgentOutputMessage, AIAgentOutputMessageType, AIAgentText, AIAgentTextSection,
|
||||
AgentOutputImage, AgentOutputImageLayout, AgentOutputMermaidDiagram, AnyFileContent,
|
||||
FileContext, FormattedTextWrapper, MessageId, ProgrammingLanguage,
|
||||
AIAgentContext, AIAgentOutput, AIAgentOutputMessage, AIAgentOutputMessageType, AIAgentText,
|
||||
AIAgentTextSection, AgentOutputImage, AgentOutputImageLayout, AgentOutputMermaidDiagram,
|
||||
AnyFileContent, FileContext, FormattedTextWrapper, MessageId, ProgrammingLanguage,
|
||||
RenderableAIError, TransientNetworkErrorKind,
|
||||
};
|
||||
use crate::server::server_api::AIApiError;
|
||||
use crate::terminal::shell::ShellType;
|
||||
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
|
||||
|
||||
fn to_range(range: Range<u32>) -> Option<FileContentLineRange> {
|
||||
Some(FileContentLineRange {
|
||||
@@ -46,6 +49,80 @@ fn formatted_text_wrapper_preserves_content() {
|
||||
assert_eq!(ft.lines.len(), 2);
|
||||
}
|
||||
|
||||
fn deserialize_pull_request_number_from_json(number_json: &str) -> serde_json::Result<i32> {
|
||||
let context = serde_json::from_str::<AIAgentContext>(&format!(
|
||||
r#"{{"PullRequest":{{"number":{number_json}}}}}"#
|
||||
))?;
|
||||
match context {
|
||||
AIAgentContext::PullRequest { number, .. } => Ok(number),
|
||||
other => panic!("expected pull request context, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pull_request_number_deserializer_accepts_positive_number_and_string() {
|
||||
assert_eq!(deserialize_pull_request_number_from_json("42").unwrap(), 42);
|
||||
assert_eq!(
|
||||
deserialize_pull_request_number_from_json(r#""42""#).unwrap(),
|
||||
42
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pull_request_number_deserializer_defaults_invalid_numbers() {
|
||||
for number_json in ["null", "0", "-1", "1.5", "2147483648", r#""""#, r#""abc""#] {
|
||||
assert_eq!(
|
||||
deserialize_pull_request_number_from_json(number_json).unwrap(),
|
||||
0,
|
||||
"expected {number_json} to deserialize to default pull request number",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pull_request_number_deserializer_rejects_unsupported_json_types() {
|
||||
for number_json in ["true", "[]", "{}"] {
|
||||
assert!(
|
||||
deserialize_pull_request_number_from_json(number_json).is_err(),
|
||||
"expected {number_json} to fail deserialization",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transient_network_error_includes_user_facing_message_and_debug_details() {
|
||||
let error = RenderableAIError::transient_network_error(
|
||||
false,
|
||||
false,
|
||||
TransientNetworkErrorKind::Api(Arc::new(AIApiError::Other(anyhow!("connection reset")))),
|
||||
);
|
||||
|
||||
let rendered = error.to_string();
|
||||
assert!(
|
||||
rendered.starts_with(
|
||||
"Warp lost connection while receiving the agent response. This is usually temporary.\n\nDebug info: "
|
||||
),
|
||||
"unexpected rendering: {rendered}"
|
||||
);
|
||||
// The raw underlying API error must survive into the debug section.
|
||||
assert!(
|
||||
rendered.contains("connection reset"),
|
||||
"raw error detail should surface in debug info: {rendered}"
|
||||
);
|
||||
assert!(!error.will_attempt_resume());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transient_network_error_reports_pending_resume() {
|
||||
let error = RenderableAIError::transient_network_error(
|
||||
true,
|
||||
false,
|
||||
TransientNetworkErrorKind::Api(Arc::new(AIApiError::Other(anyhow!("connection reset")))),
|
||||
);
|
||||
|
||||
assert!(error.will_attempt_resume());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_files() {
|
||||
let a = FileContext::new(
|
||||
@@ -152,6 +229,86 @@ fn test_programming_language_from_string() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_programming_language_to_extension() {
|
||||
// Each entry is (markdown language token, expected extension). The expected extension
|
||||
// must resolve back to a recognized language via `languages::language_by_filename` so that
|
||||
// syntax highlighting is applied to the AI block.
|
||||
let cases: &[(&str, &str)] = &[
|
||||
// Canonical names.
|
||||
("rust", "rs"),
|
||||
("go", "go"),
|
||||
("python", "py"),
|
||||
("javascript", "js"),
|
||||
("typescript", "ts"),
|
||||
("yaml", "yaml"),
|
||||
("cpp", "cpp"),
|
||||
("java", "java"),
|
||||
("c#", "cs"),
|
||||
("csharp", "cs"),
|
||||
("html", "html"),
|
||||
("css", "css"),
|
||||
("c", "c"),
|
||||
("json", "json"),
|
||||
("hcl", "hcl"),
|
||||
("lua", "lua"),
|
||||
("ruby", "rb"),
|
||||
("php", "php"),
|
||||
("toml", "toml"),
|
||||
("swift", "swift"),
|
||||
("kotlin", "kt"),
|
||||
("powershell", "ps1"),
|
||||
("elixir", "exs"),
|
||||
("scala", "scala"),
|
||||
("sql", "sql"),
|
||||
// Languages newly covered by this fix — previously fell through to None and rendered
|
||||
// without syntax highlighting in AI blocks even though the `languages` crate supports them.
|
||||
("jsx", "jsx"),
|
||||
("tsx", "tsx"),
|
||||
("xml", "xml"),
|
||||
("vue", "vue"),
|
||||
("dockerfile", "dockerfile"),
|
||||
("starlark", "bzl"),
|
||||
("objective-c", "m"),
|
||||
("objc", "m"),
|
||||
// Common markdown code-fence aliases.
|
||||
("rs", "rs"),
|
||||
("golang", "go"),
|
||||
("py", "py"),
|
||||
("js", "js"),
|
||||
("ts", "ts"),
|
||||
("yml", "yaml"),
|
||||
("c++", "cpp"),
|
||||
("rb", "rb"),
|
||||
("kt", "kt"),
|
||||
("terraform", "hcl"),
|
||||
("tf", "hcl"),
|
||||
("docker", "dockerfile"),
|
||||
("containerfile", "dockerfile"),
|
||||
];
|
||||
for (token, expected_extension) in cases {
|
||||
let language = ProgrammingLanguage::from((*token).to_string());
|
||||
assert_eq!(
|
||||
language.to_extension(),
|
||||
Some(*expected_extension),
|
||||
"expected to_extension({token:?}) to be Some({expected_extension:?})",
|
||||
);
|
||||
}
|
||||
|
||||
// PowerShell remains the only Shell variant whose extension is exposed; this preserves
|
||||
// existing behavior for the other Shell variants which are intentionally not extended here.
|
||||
assert_eq!(
|
||||
ProgrammingLanguage::Shell(ShellType::PowerShell).to_extension(),
|
||||
Some("ps1"),
|
||||
);
|
||||
|
||||
// Unrecognized tokens still return None.
|
||||
assert_eq!(
|
||||
ProgrammingLanguage::Other("definitely-not-a-language".to_string()).to_extension(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_for_copy_preserves_visual_markdown_sections() {
|
||||
let output = AIAgentOutput {
|
||||
@@ -189,3 +346,6 @@ fn format_for_copy_preserves_visual_markdown_sections() {
|
||||
"Intro\n\n```mermaid\ngraph TD\nA --> B\n```"
|
||||
);
|
||||
}
|
||||
|
||||
#[path = "suggestions_tests.rs"]
|
||||
mod suggestions;
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::super::blocklist::block::secret_redaction::{
|
||||
find_secrets_in_text, SECRET_REDACTION_REPLACEMENT_CHARACTER,
|
||||
};
|
||||
use crate::ai::agent::{
|
||||
AIAgentActionResultType, AIAgentAttachment, AIAgentContext, AIAgentInput, AnyFileContent,
|
||||
AskUserQuestionAnswerItem, AskUserQuestionResult, BlockContext, PassiveSuggestionResultType,
|
||||
PassiveSuggestionTrigger, RequestCommandOutputResult, TransferShellCommandControlToUserResult,
|
||||
};
|
||||
|
||||
use super::super::blocklist::block::secret_redaction::{
|
||||
find_secrets_in_text, SECRET_REDACTION_REPLACEMENT_CHARACTER,
|
||||
};
|
||||
|
||||
/// Redact all detected secrets in-place within the given string.
|
||||
pub(crate) fn redact_secrets(input: &mut String) {
|
||||
let mut secrets: Vec<_> = find_secrets_in_text(input)
|
||||
@@ -53,10 +52,11 @@ pub(crate) fn redact_inputs(inputs: &mut [AIAgentInput]) {
|
||||
| AIAgentInput::StartFromAmbientRunPrompt { context, .. } => {
|
||||
redact_context(Arc::make_mut(context));
|
||||
}
|
||||
AIAgentInput::SummarizeConversation { prompt } => {
|
||||
AIAgentInput::SummarizeConversation { prompt, context } => {
|
||||
if let Some(p) = prompt {
|
||||
redact_secrets(p);
|
||||
}
|
||||
redact_context(Arc::make_mut(context));
|
||||
}
|
||||
AIAgentInput::CreateEnvironment { context, .. } => {
|
||||
redact_context(Arc::make_mut(context));
|
||||
@@ -105,7 +105,8 @@ pub(crate) fn redact_inputs(inputs: &mut [AIAgentInput]) {
|
||||
}
|
||||
// No user-provided text to redact in inter-agent relay inputs.
|
||||
AIAgentInput::MessagesReceivedFromAgents { .. }
|
||||
| AIAgentInput::EventsFromAgents { .. } => {}
|
||||
| AIAgentInput::EventsFromAgents { .. }
|
||||
| AIAgentInput::OrchestrationConfigUpdate { .. } => {}
|
||||
AIAgentInput::ActionResult { result, context } => {
|
||||
redact_context(Arc::make_mut(context));
|
||||
match &mut result.result {
|
||||
@@ -260,6 +261,10 @@ pub(crate) fn redact_inputs(inputs: &mut [AIAgentInput]) {
|
||||
AIAgentActionResultType::AskUserQuestion(result) => {
|
||||
redact_ask_user_question_result(result);
|
||||
}
|
||||
// Orchestrate results contain agent IDs / canonical error
|
||||
// strings only; no user-provided text to redact.
|
||||
AIAgentActionResultType::RunAgents(_)
|
||||
| AIAgentActionResultType::WaitForEvents(_) => {}
|
||||
}
|
||||
}
|
||||
AIAgentInput::FetchReviewComments { repo_path, context } => {
|
||||
@@ -344,6 +349,8 @@ fn redact_context(context: &mut [AIAgentContext]) {
|
||||
| AIAgentContext::Codebase { .. }
|
||||
| AIAgentContext::ProjectRules { .. }
|
||||
| AIAgentContext::Git { .. }
|
||||
| AIAgentContext::Repository { .. }
|
||||
| AIAgentContext::PullRequest { .. }
|
||||
| AIAgentContext::File(_)
|
||||
| AIAgentContext::Skills { .. } => {}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,35 @@
|
||||
pub mod helper;
|
||||
pub mod transaction;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Display,
|
||||
ops::Deref,
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Display;
|
||||
use std::ops::Deref;
|
||||
|
||||
use chrono::DateTime;
|
||||
use ai::skills::SkillPathOrigin;
|
||||
use field_mask::{FieldMaskError, FieldMaskOperation};
|
||||
use helper::{MessageExt, SubagentExt, ToolCallExt};
|
||||
use itertools::Itertools;
|
||||
use prost_types::FieldMask;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use warp_multi_agent_api::{
|
||||
self as api,
|
||||
message::{tool_call::subagent::Metadata, Message},
|
||||
};
|
||||
use warp_multi_agent_api::message::tool_call::subagent::Metadata;
|
||||
use warp_multi_agent_api::message::Message;
|
||||
use warp_multi_agent_api::{self as api};
|
||||
|
||||
use crate::{
|
||||
ai::{
|
||||
agent::comment::CodeReview,
|
||||
document::ai_document_model::{AIDocumentId, AIDocumentVersion},
|
||||
},
|
||||
server::datetime_ext::DateTimeExt,
|
||||
terminal::model::block::BlockId,
|
||||
AIAgentTodoList,
|
||||
use super::api::convert_conversation::convert_tool_call_result_to_input;
|
||||
use super::api::{
|
||||
user_inputs_from_messages, ConversionParams, ConvertAPIMessageToClientOutputMessage,
|
||||
};
|
||||
|
||||
use super::comment::CodeReview;
|
||||
use super::conversation::{context_in_exchanges, update_todo_list_from_todo_op};
|
||||
use super::{
|
||||
api::{
|
||||
convert_conversation::convert_tool_call_result_to_input, user_inputs_from_messages,
|
||||
ConversionParams, ConvertAPIMessageToClientOutputMessage,
|
||||
},
|
||||
conversation::{context_in_exchanges, update_todo_list_from_todo_op},
|
||||
AIAgentContext, AIAgentExchange, AIAgentExchangeId, AIAgentOutput, AIAgentOutputMessage,
|
||||
AIAgentOutputStatus, MaybeAIAgentOutputMessage, MessageId, MessageToAIAgentOutputMessageError,
|
||||
Shared,
|
||||
};
|
||||
use crate::ai::document::ai_document_model::{AIDocumentId, AIDocumentVersion};
|
||||
use crate::terminal::model::block::BlockId;
|
||||
use crate::AIAgentTodoList;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct TaskId(String);
|
||||
@@ -128,7 +119,6 @@ struct ServerTask {
|
||||
}
|
||||
|
||||
mod optimistic {
|
||||
use crate::terminal::model::block::BlockId;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct CLIAgentSubtask {
|
||||
@@ -176,6 +166,12 @@ pub struct Task {
|
||||
/// List of `AIAgentExchange`s corresponding to messages contained in this task.
|
||||
exchanges: Vec<AIAgentExchange>,
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct TaskMessageContext<'a> {
|
||||
pub(super) current_todo_list: Option<&'a AIAgentTodoList>,
|
||||
pub(super) active_code_review: Option<&'a CodeReview>,
|
||||
pub(super) skill_path_origin: &'a SkillPathOrigin,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub(super) fn new_optimistic_root() -> Self {
|
||||
@@ -236,6 +232,7 @@ impl Task {
|
||||
parent_task: Option<&api::Task>,
|
||||
current_todo_list: Option<&AIAgentTodoList>,
|
||||
active_code_review: Option<&CodeReview>,
|
||||
skill_path_origin: &SkillPathOrigin,
|
||||
) -> Result<Self, UpgradeOptimisticTaskError> {
|
||||
match self.data {
|
||||
TaskImpl::Optimistic(optimistic::Task::Root) => {
|
||||
@@ -281,8 +278,11 @@ impl Task {
|
||||
if let Err(e) = self.update_exchange_from_messages(
|
||||
messages,
|
||||
exchange_id,
|
||||
current_todo_list,
|
||||
active_code_review,
|
||||
TaskMessageContext {
|
||||
current_todo_list,
|
||||
active_code_review,
|
||||
skill_path_origin,
|
||||
},
|
||||
false,
|
||||
) {
|
||||
log::error!(
|
||||
@@ -315,7 +315,8 @@ impl Task {
|
||||
parent_task: &api::Task,
|
||||
existing_exchange: &AIAgentExchange,
|
||||
current_todo_list: Option<&AIAgentTodoList>,
|
||||
current_comment_state: Option<&CodeReview>,
|
||||
active_code_review: Option<&CodeReview>,
|
||||
skill_path_origin: &SkillPathOrigin,
|
||||
should_convert_input_messages: bool,
|
||||
) -> Self {
|
||||
let subagent_call_and_id = parent_task.messages.iter().find_map(|message| {
|
||||
@@ -330,7 +331,7 @@ impl Task {
|
||||
input: vec![],
|
||||
output_status: AIAgentOutputStatus::Streaming { output: None },
|
||||
added_message_ids: Default::default(),
|
||||
start_time: DateTime::now().into(),
|
||||
start_time: chrono::Local::now(),
|
||||
finish_time: None,
|
||||
time_to_first_token_ms: None,
|
||||
working_directory: existing_exchange.working_directory.clone(),
|
||||
@@ -368,8 +369,11 @@ impl Task {
|
||||
me.update_exchange_from_messages(
|
||||
messages_clone,
|
||||
new_exchange_id,
|
||||
current_todo_list,
|
||||
current_comment_state,
|
||||
TaskMessageContext {
|
||||
current_todo_list,
|
||||
active_code_review,
|
||||
skill_path_origin,
|
||||
},
|
||||
should_convert_input_messages,
|
||||
)
|
||||
.expect("Exchange exists and output is in 'streaming' state.");
|
||||
@@ -462,7 +466,7 @@ impl Task {
|
||||
input: vec![],
|
||||
output_status: AIAgentOutputStatus::Streaming { output: None },
|
||||
added_message_ids: Default::default(),
|
||||
start_time: DateTime::now().into(),
|
||||
start_time: chrono::Local::now(),
|
||||
finish_time: None,
|
||||
time_to_first_token_ms: None,
|
||||
working_directory: existing_exchange.working_directory.clone(),
|
||||
@@ -603,6 +607,19 @@ impl Task {
|
||||
self.try_get_source().ok()
|
||||
}
|
||||
|
||||
pub(super) fn source_for_persistence(&self) -> Option<api::Task> {
|
||||
match &self.data {
|
||||
TaskImpl::Server(server_data) => Some(server_data.source.clone()),
|
||||
// Optimistic root tasks have a client-generated UUID and no
|
||||
// server-side identity yet. Persisting a stub `api::Task` for them
|
||||
// produces an orphan row in `agent_tasks` that survives the later
|
||||
// server-side upgrade and breaks restore by competing with the
|
||||
// real server root for parentless-task selection. See QUALITY-774.
|
||||
TaskImpl::Optimistic(optimistic::Task::Root) => None,
|
||||
TaskImpl::Optimistic(optimistic::Task::CLIAgent(_)) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn messages(&self) -> impl Iterator<Item = &api::Message> {
|
||||
self.source()
|
||||
.into_iter()
|
||||
@@ -677,8 +694,7 @@ impl Task {
|
||||
&mut self,
|
||||
messages: Vec<api::Message>,
|
||||
exchange_id: AIAgentExchangeId,
|
||||
current_todo_list: Option<&AIAgentTodoList>,
|
||||
current_comments: Option<&CodeReview>,
|
||||
message_context: TaskMessageContext<'_>,
|
||||
should_convert_input_messages: bool,
|
||||
) -> Result<(), UpdateTaskError> {
|
||||
if self.source().is_none() {
|
||||
@@ -687,8 +703,7 @@ impl Task {
|
||||
self.update_exchange_from_messages(
|
||||
messages.clone(),
|
||||
exchange_id,
|
||||
current_todo_list,
|
||||
current_comments,
|
||||
message_context,
|
||||
should_convert_input_messages,
|
||||
)?;
|
||||
self.try_get_source_mut()?.messages.extend(messages);
|
||||
@@ -699,8 +714,7 @@ impl Task {
|
||||
&mut self,
|
||||
message: api::Message,
|
||||
exchange_id: AIAgentExchangeId,
|
||||
current_todo_list: Option<&AIAgentTodoList>,
|
||||
current_comments: Option<&CodeReview>,
|
||||
message_context: TaskMessageContext<'_>,
|
||||
mask: FieldMask,
|
||||
should_convert_input_messages: bool,
|
||||
) -> Result<&api::Message, UpdateTaskError> {
|
||||
@@ -714,8 +728,7 @@ impl Task {
|
||||
self.add_messages(
|
||||
vec![message.clone()],
|
||||
exchange_id,
|
||||
current_todo_list,
|
||||
current_comments,
|
||||
message_context,
|
||||
should_convert_input_messages,
|
||||
)?;
|
||||
return self
|
||||
@@ -734,10 +747,13 @@ impl Task {
|
||||
.exchange_mut(exchange_id)
|
||||
.ok_or(UpdateTaskError::ExchangeNotFound)?;
|
||||
exchange_to_update.upsert_output_for_message(
|
||||
&id,
|
||||
&updated_message,
|
||||
current_todo_list,
|
||||
current_comments,
|
||||
ConversionParams {
|
||||
task_id: &id,
|
||||
current_todo_list: message_context.current_todo_list,
|
||||
active_code_review: message_context.active_code_review,
|
||||
skill_path_origin: message_context.skill_path_origin,
|
||||
},
|
||||
)?;
|
||||
|
||||
// Task message updates can carry tool call result updates with them,
|
||||
@@ -782,8 +798,7 @@ impl Task {
|
||||
&mut self,
|
||||
message: api::Message,
|
||||
exchange_id: AIAgentExchangeId,
|
||||
current_todo_list: Option<&AIAgentTodoList>,
|
||||
current_comments: Option<&CodeReview>,
|
||||
message_context: TaskMessageContext<'_>,
|
||||
mask: FieldMask,
|
||||
) -> Result<&api::Message, UpdateTaskError> {
|
||||
let Some((idx, existing_message)) = self
|
||||
@@ -836,10 +851,13 @@ impl Task {
|
||||
.exchange_mut(exchange_id)
|
||||
.ok_or(UpdateTaskError::ExchangeNotFound)?;
|
||||
exchange_to_update.upsert_output_for_message(
|
||||
&id,
|
||||
&updated_message,
|
||||
current_todo_list,
|
||||
current_comments,
|
||||
ConversionParams {
|
||||
task_id: &id,
|
||||
current_todo_list: message_context.current_todo_list,
|
||||
active_code_review: message_context.active_code_review,
|
||||
skill_path_origin: message_context.skill_path_origin,
|
||||
},
|
||||
)?;
|
||||
|
||||
let source = self.try_get_source_mut()?;
|
||||
@@ -964,8 +982,7 @@ impl Task {
|
||||
&mut self,
|
||||
messages: Vec<api::Message>,
|
||||
exchange_id: AIAgentExchangeId,
|
||||
current_todo_list: Option<&AIAgentTodoList>,
|
||||
active_code_review: Option<&CodeReview>,
|
||||
message_context: TaskMessageContext<'_>,
|
||||
should_convert_input_messages: bool,
|
||||
) -> Result<(), UpdateTaskError> {
|
||||
let exchange = self
|
||||
@@ -1007,8 +1024,9 @@ impl Task {
|
||||
.filter_map(|m| {
|
||||
match m.to_client_output_message(ConversionParams {
|
||||
task_id: &self.id,
|
||||
current_todo_list,
|
||||
active_code_review,
|
||||
current_todo_list: message_context.current_todo_list,
|
||||
active_code_review: message_context.active_code_review,
|
||||
skill_path_origin: message_context.skill_path_origin,
|
||||
}) {
|
||||
Ok(MaybeAIAgentOutputMessage::Message(m)) => Some(Ok(m)),
|
||||
Ok(MaybeAIAgentOutputMessage::NoClientRepresentation) => None,
|
||||
@@ -1043,10 +1061,8 @@ impl AIAgentExchange {
|
||||
/// Note: this means updates will insert a new entry after previously added entries.
|
||||
fn upsert_output_for_message(
|
||||
&self,
|
||||
task_id: &TaskId,
|
||||
task_message: &api::Message,
|
||||
todo_list: Option<&AIAgentTodoList>,
|
||||
comments: Option<&CodeReview>,
|
||||
conversion_params: super::api::ConversionParams<'_>,
|
||||
) -> Result<(), UpdateTaskError> {
|
||||
if let AIAgentOutputStatus::Streaming {
|
||||
output: Some(output),
|
||||
@@ -1079,11 +1095,8 @@ impl AIAgentExchange {
|
||||
|
||||
match task_message
|
||||
.clone()
|
||||
.to_client_output_message(ConversionParams {
|
||||
current_todo_list: todo_list,
|
||||
active_code_review: comments,
|
||||
task_id,
|
||||
})? {
|
||||
.to_client_output_message(conversion_params)?
|
||||
{
|
||||
MaybeAIAgentOutputMessage::Message(m) => {
|
||||
log::info!(
|
||||
"[bedrock-debug] upsert_output_for_message: client_message_type={:?}",
|
||||
|
||||
@@ -139,6 +139,10 @@ impl ToolExt for api::message::tool_call::Tool {
|
||||
Tool::AskUserQuestion(_) => "ask_user_question",
|
||||
Tool::SendMessageToAgent(_) => "send_message_to_agent",
|
||||
Tool::TransferShellCommandControlToUser(_) => "transfer_shell_command_control",
|
||||
Tool::RunAgents(_) => "orchestrate",
|
||||
// Matches the legacy server-handled name so analytics don't
|
||||
// double-count the rollout.
|
||||
Tool::WaitForEvents(_) => "wait_for_events",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::ai::agent::task::TaskId;
|
||||
|
||||
use super::Task;
|
||||
use crate::ai::agent::task::TaskId;
|
||||
|
||||
/// Keeps track of the state of tasks before they are modified.
|
||||
/// Messages are assumed to be only updated during the same transaction
|
||||
|
||||
@@ -2,18 +2,11 @@ use std::collections::HashMap;
|
||||
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::{
|
||||
agent::{AIAgentContext, AIAgentInput},
|
||||
skills::SkillDescriptor,
|
||||
};
|
||||
|
||||
use super::{
|
||||
task::{
|
||||
helper::{MessageExt, ToolCallExt},
|
||||
Task, TaskId,
|
||||
},
|
||||
AIAgentExchange, AIAgentExchangeId, AIAgentOutputMessageType,
|
||||
};
|
||||
use super::task::helper::{MessageExt, ToolCallExt};
|
||||
use super::task::{Task, TaskId};
|
||||
use super::{AIAgentExchange, AIAgentExchangeId, AIAgentOutputMessageType};
|
||||
use crate::ai::agent::{AIAgentContext, AIAgentInput};
|
||||
use crate::ai::skills::SkillDescriptor;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExchangeRef {
|
||||
@@ -27,6 +20,12 @@ pub struct TaskStore {
|
||||
root_task_id: TaskId,
|
||||
tasks: HashMap<TaskId, Task>,
|
||||
linearized_refs: Vec<ExchangeRef>,
|
||||
exchange_id_index: HashMap<AIAgentExchangeId, ExchangeRef>,
|
||||
/// If the root task was upgraded from an optimistic (client-generated) ID
|
||||
/// to a server-assigned ID, stores the original optimistic ID so that
|
||||
/// deferred event handlers referencing the stale ID can still resolve
|
||||
/// the task via `root_task_id`.
|
||||
optimistic_root_task_id: Option<TaskId>,
|
||||
}
|
||||
|
||||
impl TaskStore {
|
||||
@@ -35,7 +34,9 @@ impl TaskStore {
|
||||
let mut store = Self {
|
||||
tasks: HashMap::new(),
|
||||
linearized_refs: Vec::new(),
|
||||
exchange_id_index: HashMap::new(),
|
||||
root_task_id: root_task_id.clone(),
|
||||
optimistic_root_task_id: None,
|
||||
};
|
||||
store.tasks.insert(root_task_id, root_task);
|
||||
store.rebuild_linearized_refs_index();
|
||||
@@ -48,7 +49,9 @@ impl TaskStore {
|
||||
let mut store = Self {
|
||||
tasks,
|
||||
linearized_refs: Vec::new(),
|
||||
exchange_id_index: HashMap::new(),
|
||||
root_task_id,
|
||||
optimistic_root_task_id: None,
|
||||
};
|
||||
store.rebuild_linearized_refs_index();
|
||||
store
|
||||
@@ -59,7 +62,10 @@ impl TaskStore {
|
||||
}
|
||||
|
||||
pub fn get(&self, task_id: &TaskId) -> Option<&Task> {
|
||||
self.tasks.get(task_id)
|
||||
self.tasks.get(task_id).or_else(|| {
|
||||
let old_id = self.optimistic_root_task_id.as_ref()?;
|
||||
(old_id == task_id).then(|| self.tasks.get(&self.root_task_id))?
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tasks(&self) -> impl Iterator<Item = &Task> {
|
||||
@@ -104,15 +110,15 @@ impl TaskStore {
|
||||
None
|
||||
}
|
||||
|
||||
/// Modifies a task via the provided closure and rebuilds the exchange index
|
||||
/// if exchanges changed.
|
||||
/// Modifies a task via the provided closure and rebuilds the exchange index if the exchange
|
||||
/// count changes.
|
||||
pub fn modify_task<R>(
|
||||
&mut self,
|
||||
task_id: &TaskId,
|
||||
f: impl FnOnce(&mut Task) -> R,
|
||||
) -> Option<R> {
|
||||
let exchange_count_before = self.tasks.get(task_id)?.exchanges_len();
|
||||
let task = self.tasks.get_mut(task_id)?;
|
||||
let exchange_count_before = task.exchanges_len();
|
||||
let result = f(task);
|
||||
let exchange_count_after = self
|
||||
.tasks
|
||||
@@ -142,10 +148,39 @@ impl TaskStore {
|
||||
self.remove(&old_root_id);
|
||||
|
||||
let new_root_id = root_task.id().clone();
|
||||
if old_root_id != new_root_id {
|
||||
self.optimistic_root_task_id = Some(old_root_id);
|
||||
}
|
||||
self.root_task_id = new_root_id;
|
||||
self.insert(root_task);
|
||||
}
|
||||
|
||||
pub fn exchange_by_id(&self, exchange_id: AIAgentExchangeId) -> Option<&AIAgentExchange> {
|
||||
let exchange_ref = self.exchange_id_index.get(&exchange_id)?;
|
||||
self.lookup_exchange(exchange_ref)
|
||||
}
|
||||
|
||||
pub(super) fn rebuild_exchange_id_index(&mut self) {
|
||||
self.exchange_id_index = self
|
||||
.tasks
|
||||
.values()
|
||||
.flat_map(|task| {
|
||||
let task_id = task.id().clone();
|
||||
task.exchanges()
|
||||
.enumerate()
|
||||
.map(move |(exchange_index, exchange)| {
|
||||
(
|
||||
exchange.id,
|
||||
ExchangeRef {
|
||||
task_id: task_id.clone(),
|
||||
exchange_index,
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn first_exchange(&self) -> Option<&AIAgentExchange> {
|
||||
self.linearized_refs
|
||||
.first()
|
||||
@@ -266,7 +301,7 @@ impl TaskStore {
|
||||
|
||||
pub fn remove(&mut self, task_id: &TaskId) -> Option<Task> {
|
||||
let task = self.tasks.remove(task_id)?;
|
||||
self.linearized_refs.retain(|r| &r.task_id != task_id);
|
||||
self.rebuild_linearized_refs_index();
|
||||
Some(task)
|
||||
}
|
||||
|
||||
@@ -280,6 +315,7 @@ impl TaskStore {
|
||||
/// Rebuilds the linearized index from scratch using DFS traversal.
|
||||
fn rebuild_linearized_refs_index(&mut self) {
|
||||
self.linearized_refs = Self::build_linearized_refs(&self.tasks, &self.root_task_id);
|
||||
self.rebuild_exchange_id_index();
|
||||
}
|
||||
|
||||
/// Builds linearized exchange refs via DFS traversal without mutating self.
|
||||
@@ -330,9 +366,8 @@ impl TaskStore {
|
||||
|
||||
#[cfg(test)]
|
||||
mod testing {
|
||||
use crate::ai::agent::task::TaskId;
|
||||
|
||||
use super::TaskStore;
|
||||
use crate::ai::agent::task::TaskId;
|
||||
|
||||
impl TaskStore {
|
||||
pub fn contains(&self, task_id: &TaskId) -> bool {
|
||||
|
||||
@@ -3,17 +3,14 @@ use std::collections::HashSet;
|
||||
use chrono::Local;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ai::{
|
||||
agent::{
|
||||
task::{Task, TaskId},
|
||||
AIAgentExchange, AIAgentExchangeId, AIAgentOutput, AIAgentOutputMessage,
|
||||
AIAgentOutputMessageType, AIAgentOutputStatus, FinishedAIAgentOutput, MessageId, Shared,
|
||||
SubagentCall,
|
||||
},
|
||||
llms::LLMId,
|
||||
};
|
||||
|
||||
use super::TaskStore;
|
||||
use crate::ai::agent::task::{Task, TaskId};
|
||||
use crate::ai::agent::{
|
||||
AIAgentExchange, AIAgentExchangeId, AIAgentOutput, AIAgentOutputMessage,
|
||||
AIAgentOutputMessageType, AIAgentOutputStatus, FinishedAIAgentOutput, MessageId, Shared,
|
||||
SubagentCall,
|
||||
};
|
||||
use crate::ai::llms::LLMId;
|
||||
|
||||
fn create_test_exchange() -> AIAgentExchange {
|
||||
AIAgentExchange {
|
||||
@@ -216,7 +213,8 @@ fn test_set_root_task_replaces_old() {
|
||||
|
||||
assert_eq!(store.task_count(), 1);
|
||||
assert_eq!(store.exchange_count(), 3);
|
||||
assert!(store.get(&task1_id).is_none());
|
||||
// task1_id is now aliased to the new root task via optimistic_root_task_id
|
||||
assert!(store.get(&task1_id).is_some());
|
||||
assert!(store.get(&task2_id).is_some());
|
||||
assert_eq!(store.root_task_id(), &task2_id);
|
||||
|
||||
@@ -485,7 +483,6 @@ fn test_linearization_nested_subtasks() {
|
||||
let grandchild_exchange_id = grandchild_subtask.exchanges().next().unwrap().id;
|
||||
|
||||
// Create child subtask with a call to grandchild
|
||||
use crate::terminal::model::block::BlockId;
|
||||
let mut child_subtask = Task::new_optimistic_cli_agent_subtask(BlockId::new(), None);
|
||||
let child_id = child_subtask.id().clone();
|
||||
|
||||
@@ -623,3 +620,119 @@ fn test_all_exchanges_by_task_with_subtasks() {
|
||||
assert_eq!(by_task[2].1.len(), 1);
|
||||
assert_eq!(by_task[2].1[0].id, root_exchange3_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_by_id_resolves_subtask_exchange() {
|
||||
// The index spans all tasks, not just the root, so exchanges that live in a
|
||||
// subtask must be resolvable by id.
|
||||
let root_task = create_test_task_with_exchanges(1);
|
||||
let root_task_id = root_task.id().clone();
|
||||
let mut store = TaskStore::with_root_task(root_task);
|
||||
|
||||
let subtask = create_test_subtask_with_exchanges(2);
|
||||
let subtask_id = subtask.id().clone();
|
||||
let subtask_exchange_ids: Vec<_> = subtask.exchanges().map(|e| e.id).collect();
|
||||
|
||||
let subagent_exchange = create_exchange_with_subagent_call(&subtask_id);
|
||||
store.append_exchange(&root_task_id, subagent_exchange);
|
||||
store.insert(subtask);
|
||||
|
||||
for id in &subtask_exchange_ids {
|
||||
assert_eq!(store.exchange_by_id(*id).map(|e| e.id), Some(*id));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_by_id_after_remove_task() {
|
||||
let root_task = create_test_task_with_exchanges(1);
|
||||
let root_task_id = root_task.id().clone();
|
||||
let root_exchange_id = root_task.exchanges().next().unwrap().id;
|
||||
let mut store = TaskStore::with_root_task(root_task);
|
||||
|
||||
let subtask = create_test_subtask_with_exchanges(2);
|
||||
let subtask_id = subtask.id().clone();
|
||||
let subtask_exchange_ids: Vec<_> = subtask.exchanges().map(|e| e.id).collect();
|
||||
|
||||
let subagent_exchange = create_exchange_with_subagent_call(&subtask_id);
|
||||
store.append_exchange(&root_task_id, subagent_exchange);
|
||||
store.insert(subtask);
|
||||
|
||||
// Sanity: the subtask's exchanges resolve before removal.
|
||||
assert!(store.exchange_by_id(subtask_exchange_ids[0]).is_some());
|
||||
|
||||
store.remove(&subtask_id);
|
||||
|
||||
// The removed task's exchanges are no longer resolvable.
|
||||
for id in &subtask_exchange_ids {
|
||||
assert!(store.exchange_by_id(*id).is_none());
|
||||
}
|
||||
// The surviving root exchange still resolves.
|
||||
assert_eq!(
|
||||
store.exchange_by_id(root_exchange_id).map(|e| e.id),
|
||||
Some(root_exchange_id)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_by_id_after_remove_task_exchange_index_shift() {
|
||||
let task = create_test_task_with_exchanges(3);
|
||||
let task_id = task.id().clone();
|
||||
let exchange_ids: Vec<_> = task.exchanges().map(|e| e.id).collect();
|
||||
let mut store = TaskStore::with_root_task(task);
|
||||
|
||||
// Remove the middle exchange, which shifts the index of everything after it.
|
||||
store.remove_task_exchange(&task_id, exchange_ids[1]);
|
||||
|
||||
// The removed id no longer resolves.
|
||||
assert!(store.exchange_by_id(exchange_ids[1]).is_none());
|
||||
|
||||
// The exchange that followed it (now at a different index) still resolves to itself.
|
||||
assert_eq!(
|
||||
store.exchange_by_id(exchange_ids[2]).map(|e| e.id),
|
||||
Some(exchange_ids[2])
|
||||
);
|
||||
// The exchange before it is unaffected.
|
||||
assert_eq!(
|
||||
store.exchange_by_id(exchange_ids[0]).map(|e| e.id),
|
||||
Some(exchange_ids[0])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_by_id_after_modify_task_append() {
|
||||
let task = create_test_task_with_exchanges(2);
|
||||
let task_id = task.id().clone();
|
||||
let mut store = TaskStore::with_root_task(task);
|
||||
|
||||
let new_exchange = create_test_exchange();
|
||||
let new_exchange_id = new_exchange.id;
|
||||
store.modify_task(&task_id, |task| {
|
||||
task.append_exchange(new_exchange);
|
||||
});
|
||||
|
||||
// The newly appended exchange is found via the rebuilt index.
|
||||
assert_eq!(
|
||||
store.exchange_by_id(new_exchange_id).map(|e| e.id),
|
||||
Some(new_exchange_id)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_by_id_after_set_root_task() {
|
||||
let task1 = create_test_task_with_exchanges(2);
|
||||
let task1_exchange_ids: Vec<_> = task1.exchanges().map(|e| e.id).collect();
|
||||
let mut store = TaskStore::with_root_task(task1);
|
||||
|
||||
let task2 = create_test_task_with_exchanges(3);
|
||||
let task2_exchange_ids: Vec<_> = task2.exchanges().map(|e| e.id).collect();
|
||||
store.set_root_task(task2);
|
||||
|
||||
// The old root's exchanges no longer resolve.
|
||||
for id in &task1_exchange_ids {
|
||||
assert!(store.exchange_by_id(*id).is_none());
|
||||
}
|
||||
// The new root's exchanges resolve.
|
||||
for id in &task2_exchange_ids {
|
||||
assert_eq!(store.exchange_by_id(*id).map(|e| e.id), Some(*id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ai::skills::SkillPathOrigin;
|
||||
use chrono::Local;
|
||||
use prost_types::FieldMask;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::{ExtractMessagesError, Task, TaskMessageContext};
|
||||
use crate::ai::agent::{
|
||||
AIAgentActionType, AIAgentExchange, AIAgentOutput, AIAgentOutputMessageType,
|
||||
AIAgentOutputStatus, MessageId, Shared,
|
||||
@@ -8,11 +14,6 @@ use crate::ai::llms::LLMId;
|
||||
use crate::test_util::ai_agent_tasks::{
|
||||
create_api_subtask, create_api_task, create_message, create_subagent_tool_call_message,
|
||||
};
|
||||
use chrono::Local;
|
||||
use prost_types::FieldMask;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::{ExtractMessagesError, Task};
|
||||
|
||||
/// Creates a Task backed by server data from the given api::Task.
|
||||
fn create_server_task(api_task: api::Task) -> Task {
|
||||
@@ -47,6 +48,7 @@ fn create_start_agent_tool_call_message(
|
||||
prompt: &str,
|
||||
) -> api::Message {
|
||||
api::Message {
|
||||
fetched_memories: vec![],
|
||||
id: id.to_string(),
|
||||
task_id: task_id.to_string(),
|
||||
server_message_data: String::new(),
|
||||
@@ -113,8 +115,11 @@ fn test_upsert_message_adds_start_agent_prompt_to_output() {
|
||||
"run tests",
|
||||
),
|
||||
exchange_id,
|
||||
None,
|
||||
None,
|
||||
TaskMessageContext {
|
||||
current_todo_list: None,
|
||||
active_code_review: None,
|
||||
skill_path_origin: &SkillPathOrigin::Local,
|
||||
},
|
||||
FieldMask {
|
||||
paths: vec!["message.tool_call".to_string()],
|
||||
},
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
use galaxyui::{AppContext, SingletonEntity};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ai::llms::LLMId;
|
||||
use crate::CloudModel;
|
||||
use crate::{
|
||||
server::telemetry::AgentModeCitation as CitationForTelemetry,
|
||||
terminal::view::block_onboarding::onboarding_agentic_suggestions_block::OnboardingChipType,
|
||||
};
|
||||
|
||||
use super::conversation::AIConversationId;
|
||||
use super::{
|
||||
AIAgentCitation, AIAgentExchangeId, EntrypointType, PassiveSuggestionTriggerType,
|
||||
ServerOutputId,
|
||||
};
|
||||
use crate::ai::llms::LLMId;
|
||||
use crate::server::telemetry::AgentModeCitation as CitationForTelemetry;
|
||||
use crate::CloudModel;
|
||||
|
||||
pub trait ForTelemetry {
|
||||
type Output;
|
||||
@@ -37,6 +33,14 @@ impl ForTelemetry for AIAgentCitation {
|
||||
Some(CitationForTelemetry::WarpDocs { page: path.clone() })
|
||||
}
|
||||
Self::WebPage { url } => Some(CitationForTelemetry::WebPage { url: url.clone() }),
|
||||
Self::AgentMemory {
|
||||
memory_store_id,
|
||||
memory_id,
|
||||
..
|
||||
} => Some(CitationForTelemetry::AgentMemory {
|
||||
memory_store_id: memory_store_id.clone(),
|
||||
memory_id: memory_id.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,20 +48,6 @@ impl ForTelemetry for AIAgentCitation {
|
||||
impl EntrypointType {
|
||||
pub fn entrypoint(&self) -> String {
|
||||
match self {
|
||||
Self::Onboarding { chip_type } => {
|
||||
format!(
|
||||
"ONBOARDING.{}",
|
||||
match chip_type {
|
||||
OnboardingChipType::FixAnIssue => "FIX_AN_ISSUE",
|
||||
OnboardingChipType::PullCloudLogs => "PULL_CLOUD_LOGS",
|
||||
OnboardingChipType::StartAFeature => "START_A_FEATURE",
|
||||
OnboardingChipType::PythonSnakeGame => "PYTHON_SNAKE_GAME",
|
||||
OnboardingChipType::ExploreGitHistory => "EXPLORE_GIT_HISTORY",
|
||||
OnboardingChipType::MatrixThemePicker => "MATRIX_THEME_PICKER",
|
||||
OnboardingChipType::Other => "OTHER",
|
||||
}
|
||||
)
|
||||
}
|
||||
Self::PromptSuggestion {
|
||||
is_static,
|
||||
is_coding,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::ai::agent::AIAgentTodo;
|
||||
|
||||
use super::AIAgentTodoId;
|
||||
use crate::ai::agent::AIAgentTodo;
|
||||
pub(crate) mod popup;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
use crate::ai::blocklist::{BlocklistAIContextEvent, BlocklistAIContextModel};
|
||||
use pathfinder_color::ColorU;
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxyui::elements::{
|
||||
ClippedScrollStateHandle, ClippedScrollable, Dismiss, Empty, Expanded, ParentElement,
|
||||
SavePosition, ScrollTarget, ScrollToPositionMode, ScrollbarWidth, Shrinkable,
|
||||
Border, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container, CornerRadius,
|
||||
CrossAxisAlignment, Dismiss, DropShadow, Empty, Expanded, Flex, MainAxisSize, ParentElement,
|
||||
Radius, SavePosition, ScrollTarget, ScrollToPositionMode, ScrollbarWidth, Shrinkable, Text,
|
||||
};
|
||||
use galaxyui::fonts::FamilyId;
|
||||
use galaxyui::ModelHandle;
|
||||
use galaxyui::SingletonEntity;
|
||||
use galaxyui::fonts::{FamilyId, Properties, Weight};
|
||||
use galaxyui::keymap::FixedBinding;
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DropShadow, Flex,
|
||||
MainAxisSize, Radius, Text,
|
||||
},
|
||||
fonts::{Properties, Weight},
|
||||
keymap::FixedBinding,
|
||||
AppContext, Element, Entity, EntityId, TypedActionView, View, ViewContext,
|
||||
AppContext, Element, Entity, EntityId, ModelHandle, SingletonEntity, TypedActionView, View,
|
||||
ViewContext,
|
||||
};
|
||||
use pathfinder_color::ColorU;
|
||||
|
||||
use crate::ai::agent::icons::{in_progress_icon, pending_icon, succeeded_icon};
|
||||
use crate::ai::agent::todos::AIAgentTodoList;
|
||||
use crate::ai::blocklist::{BlocklistAIHistoryEvent, BlocklistAIHistoryModel};
|
||||
use crate::ai::blocklist::{
|
||||
BlocklistAIContextEvent, BlocklistAIContextModel, BlocklistAIHistoryEvent,
|
||||
BlocklistAIHistoryModel,
|
||||
};
|
||||
use crate::ui_components::blended_colors;
|
||||
|
||||
pub struct AgentTodosPopupView {
|
||||
@@ -86,8 +83,11 @@ impl AgentTodosPopupView {
|
||||
event: &BlocklistAIHistoryEvent,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let BlocklistAIHistoryEvent::UpdatedTodoList { terminal_view_id } = event {
|
||||
if *terminal_view_id == self.terminal_view_id {
|
||||
if let BlocklistAIHistoryEvent::UpdatedTodoList {
|
||||
terminal_surface_id,
|
||||
} = event
|
||||
{
|
||||
if *terminal_surface_id == self.terminal_view_id {
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use super::{
|
||||
AIAgentTextSection, AgentOutputImage, AgentOutputImageLayout, AgentOutputMermaidDiagram,
|
||||
AgentOutputTable, ProgrammingLanguage,
|
||||
};
|
||||
use crate::code::editor_management::CodeSource;
|
||||
use crate::features::FeatureFlag;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use ai::gfm_table::{format_gfm_table, maybe_collect_gfm_table_lines};
|
||||
use galaxy_util::path::LineAndColumnArg;
|
||||
use itertools::Itertools;
|
||||
@@ -13,7 +10,13 @@ use markdown_parser::{
|
||||
};
|
||||
use mermaid_to_svg::is_mermaid_diagram;
|
||||
use regex::Regex;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use super::{
|
||||
AIAgentTextSection, AgentOutputImage, AgentOutputImageLayout, AgentOutputMermaidDiagram,
|
||||
AgentOutputTable, ProgrammingLanguage,
|
||||
};
|
||||
use crate::code::editor_management::CodeSource;
|
||||
use crate::features::FeatureFlag;
|
||||
|
||||
lazy_static! {
|
||||
/// Markdown prefix for code blocks. Matches on triple backticks followed by a language.
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use session_sharing_protocol::common::SessionId;
|
||||
use warp_cli::agent::Harness;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use warpui::{AppContext, SingletonEntity};
|
||||
|
||||
use super::{
|
||||
artifacts_match_filter, AgentManagementFilters, AgentRunDisplayStatus, ArtifactFilter,
|
||||
ConversationMetadata, CreatedOnFilter, CreatorFilter, EnvironmentFilter, HarnessFilter,
|
||||
OwnerFilter, SessionStatus, SourceFilter, StatusFilter,
|
||||
};
|
||||
use crate::ai::active_agent_views_model::{ActiveAgentViewsModel, ConversationOrTaskId};
|
||||
use crate::ai::agent::api::ServerConversationToken;
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::ambient_agents::{AgentSource, AmbientAgentTask, AmbientAgentTaskId};
|
||||
use crate::ai::artifacts::Artifact;
|
||||
use crate::ai::blocklist::history_model::{AIConversationMetadata, BlocklistAIHistoryModel};
|
||||
use crate::ai::conversation_navigation::ConversationNavigationData;
|
||||
use crate::auth::{AuthStateProvider, UserUid};
|
||||
use crate::util::time_format::human_readable_precise_duration;
|
||||
use crate::workspace::RestoreConversationLayout;
|
||||
use crate::workspaces::user_profiles::{UserProfileWithUID, UserProfiles};
|
||||
|
||||
const SESSION_EXPIRATION_TIME: chrono::Duration = chrono::Duration::weeks(1);
|
||||
|
||||
/// Stable projection identity used by list and navigation surfaces.
|
||||
///
|
||||
/// Task-backed rows use the ambient run ID even when they are attached to a local
|
||||
/// conversation, so task-specific affordances do not disappear when local data is present.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum AgentConversationEntryId {
|
||||
AmbientRun(AmbientAgentTaskId),
|
||||
Conversation(AIConversationId),
|
||||
}
|
||||
|
||||
impl AgentConversationEntryId {
|
||||
pub fn as_key(&self) -> String {
|
||||
match self {
|
||||
AgentConversationEntryId::AmbientRun(id) => format!("task_{id}"),
|
||||
AgentConversationEntryId::Conversation(id) => format!("conv_{id}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConversationOrTaskId> for AgentConversationEntryId {
|
||||
fn from(id: ConversationOrTaskId) -> Self {
|
||||
match id {
|
||||
ConversationOrTaskId::ConversationId(conversation_id) => {
|
||||
AgentConversationEntryId::Conversation(conversation_id)
|
||||
}
|
||||
ConversationOrTaskId::TaskId(task_id) => AgentConversationEntryId::AmbientRun(task_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigation request input for resolving an entry or server-token handle at action time.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AgentConversationNavigationSubject {
|
||||
Entry(AgentConversationEntryId),
|
||||
#[allow(dead_code)]
|
||||
ServerToken(ServerConversationToken),
|
||||
}
|
||||
|
||||
/// Normalized row data for agent conversation list, management, and navigation surfaces.
|
||||
///
|
||||
/// The entry keeps local conversation identity, ambient run identity, cloud token identity,
|
||||
/// display fields, and available actions together so callers do not recompute navigation
|
||||
/// policy from stale partial sources.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AgentConversationEntry {
|
||||
pub id: AgentConversationEntryId,
|
||||
pub identity: AgentConversationIdentity,
|
||||
pub provenance: AgentConversationProvenance,
|
||||
pub display: AgentConversationDisplayData,
|
||||
pub backing: AgentConversationBackingData,
|
||||
pub capabilities: AgentConversationCapabilities,
|
||||
}
|
||||
|
||||
/// Cross-system identifiers that may refer to the same underlying conversation/run.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AgentConversationIdentity {
|
||||
pub local_conversation_id: Option<AIConversationId>,
|
||||
pub ambient_agent_task_id: Option<AmbientAgentTaskId>,
|
||||
pub server_conversation_token: Option<ServerConversationToken>,
|
||||
pub session_id: Option<SessionId>,
|
||||
}
|
||||
|
||||
/// Display-only fields for rendering a conversation entry without consulting source models.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AgentConversationDisplayData {
|
||||
pub title: String,
|
||||
pub initial_query: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub last_updated: DateTime<Utc>,
|
||||
pub status: AgentRunDisplayStatus,
|
||||
pub creator: AgentConversationPrincipal,
|
||||
pub executor: Option<AgentConversationPrincipal>,
|
||||
pub request_usage: Option<f32>,
|
||||
pub run_time: Option<String>,
|
||||
pub session_status: Option<SessionStatus>,
|
||||
pub source: Option<AgentSource>,
|
||||
pub working_directory: Option<String>,
|
||||
pub environment_id: Option<String>,
|
||||
pub harness: Option<Harness>,
|
||||
pub artifacts: Vec<Artifact>,
|
||||
}
|
||||
|
||||
/// Type of principal that created or executed a run.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PrincipalType {
|
||||
User,
|
||||
ServiceAccount,
|
||||
}
|
||||
|
||||
impl PrincipalType {
|
||||
/// Parse from the wire-format string sent by the server.
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
if s.eq_ignore_ascii_case("user") {
|
||||
Some(PrincipalType::User)
|
||||
} else if s.eq_ignore_ascii_case("service_account") || s.eq_ignore_ascii_case("agent") {
|
||||
Some(PrincipalType::ServiceAccount)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_service_account(self) -> bool {
|
||||
self == PrincipalType::ServiceAccount
|
||||
}
|
||||
}
|
||||
|
||||
/// Principal information normalized across local conversations and ambient runs.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct AgentConversationPrincipal {
|
||||
pub name: Option<String>,
|
||||
pub uid: Option<String>,
|
||||
pub principal_type: Option<PrincipalType>,
|
||||
}
|
||||
|
||||
/// Source category that explains why an entry exists and which backing systems can refresh it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AgentConversationProvenance {
|
||||
LocalInteractive,
|
||||
AmbientRun,
|
||||
CloudSyncedConversation,
|
||||
}
|
||||
|
||||
/// Availability flags for the source data that contributed to an entry.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AgentConversationBackingData {
|
||||
pub has_loaded_conversation: bool,
|
||||
pub has_local_persisted_data: bool,
|
||||
pub has_cloud_data: bool,
|
||||
pub has_ambient_run: bool,
|
||||
}
|
||||
|
||||
/// Actions that should be exposed for an entry after applying current navigation policy.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AgentConversationCapabilities {
|
||||
pub can_open: bool,
|
||||
pub can_copy_link: bool,
|
||||
pub can_share: bool,
|
||||
pub can_delete: bool,
|
||||
pub can_fork_locally: bool,
|
||||
pub can_cancel: bool,
|
||||
}
|
||||
|
||||
impl AgentConversationEntry {
|
||||
pub(super) fn matches_filters(
|
||||
&self,
|
||||
filters: &AgentManagementFilters,
|
||||
app: &AppContext,
|
||||
) -> bool {
|
||||
self.matches_owner_and_creator(&filters.owners, &filters.creator, app)
|
||||
&& self.matches_status(&filters.status)
|
||||
&& self.matches_source(&filters.source)
|
||||
&& self.matches_created_on(&filters.created_on)
|
||||
&& self.matches_artifact(&filters.artifact)
|
||||
&& self.matches_environment(&filters.environment)
|
||||
&& self.matches_harness(&filters.harness)
|
||||
}
|
||||
|
||||
fn matches_owner_and_creator(
|
||||
&self,
|
||||
owner_filter: &OwnerFilter,
|
||||
creator_filter: &CreatorFilter,
|
||||
app: &AppContext,
|
||||
) -> bool {
|
||||
let current_user_id = AuthStateProvider::as_ref(app)
|
||||
.get()
|
||||
.user_id()
|
||||
.map(|uid| uid.as_string());
|
||||
|
||||
let passes_owner = match owner_filter {
|
||||
OwnerFilter::All => true,
|
||||
OwnerFilter::PersonalOnly => {
|
||||
if self.backing.has_ambient_run {
|
||||
self.display.creator.uid == current_user_id
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if !passes_owner || matches!(owner_filter, OwnerFilter::PersonalOnly) {
|
||||
return passes_owner;
|
||||
}
|
||||
|
||||
match creator_filter {
|
||||
CreatorFilter::All => true,
|
||||
CreatorFilter::Specific { name, .. } => {
|
||||
self.display.creator.name.as_ref() == Some(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_status(&self, status_filter: &StatusFilter) -> bool {
|
||||
match status_filter {
|
||||
StatusFilter::All => true,
|
||||
StatusFilter::Working | StatusFilter::Done | StatusFilter::Failed => {
|
||||
self.display.status.status_filter() == *status_filter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_source(&self, source_filter: &SourceFilter) -> bool {
|
||||
match source_filter {
|
||||
SourceFilter::All => true,
|
||||
SourceFilter::Specific(source) => self.display.source.as_ref() == Some(source),
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_created_on(&self, created_on_filter: &CreatedOnFilter) -> bool {
|
||||
let now = Utc::now();
|
||||
let created_cutoff = match created_on_filter {
|
||||
CreatedOnFilter::All => None,
|
||||
CreatedOnFilter::Last24Hours => Some(now - chrono::Duration::hours(24)),
|
||||
CreatedOnFilter::Past3Days => Some(now - chrono::Duration::days(3)),
|
||||
CreatedOnFilter::LastWeek => Some(now - chrono::Duration::days(7)),
|
||||
};
|
||||
match created_cutoff {
|
||||
Some(cutoff) => self.display.created_at >= cutoff,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_artifact(&self, artifact_filter: &ArtifactFilter) -> bool {
|
||||
artifacts_match_filter(&self.display.artifacts, artifact_filter)
|
||||
}
|
||||
|
||||
fn matches_environment(&self, environment_filter: &EnvironmentFilter) -> bool {
|
||||
match environment_filter {
|
||||
EnvironmentFilter::All => true,
|
||||
EnvironmentFilter::NoEnvironment => self.display.environment_id.is_none(),
|
||||
EnvironmentFilter::Specific(id) => self.display.environment_id.as_ref() == Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_harness(&self, harness_filter: &HarnessFilter) -> bool {
|
||||
match harness_filter {
|
||||
HarnessFilter::All => true,
|
||||
HarnessFilter::Specific(harness) => self.display.harness == Some(*harness),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_open_action(
|
||||
&self,
|
||||
restore_layout: Option<RestoreConversationLayout>,
|
||||
app: &AppContext,
|
||||
) -> bool {
|
||||
super::AgentConversationsModel::resolve_open_action(
|
||||
AgentConversationNavigationSubject::Entry(self.id),
|
||||
restore_layout,
|
||||
app,
|
||||
)
|
||||
.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the local conversation ID represented by the given task, if this task and a
|
||||
/// conversation entry both point at the same underlying local run.
|
||||
///
|
||||
/// We first match using the orchestration agent ID (task ID / run ID under v2), and fall back
|
||||
/// to the server conversation token for cases where the task only carries conversation identity
|
||||
/// through `conversation_id`.
|
||||
pub(super) fn conversation_id_shadowed_by_task(
|
||||
task: &AmbientAgentTask,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
) -> Option<AIConversationId> {
|
||||
history_model
|
||||
.conversation_id_for_agent_id(&task.run_id().to_string())
|
||||
.or_else(|| {
|
||||
task.conversation_id().and_then(|conversation_id| {
|
||||
history_model.find_conversation_id_by_server_token(&ServerConversationToken::new(
|
||||
conversation_id.to_string(),
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn task_creator_name(task: &AmbientAgentTask, app: &AppContext) -> Option<String> {
|
||||
task.creator_display_name().or_else(|| {
|
||||
let uid = task.creator.as_ref().map(|creator| &creator.uid)?;
|
||||
UserProfiles::as_ref(app).displayable_identifier_for_uid(UserUid::new(uid))
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn task_creator_uid(task: &AmbientAgentTask) -> Option<String> {
|
||||
task.creator.as_ref().map(|creator| creator.uid.clone())
|
||||
}
|
||||
|
||||
fn current_user_name(app: &AppContext) -> Option<String> {
|
||||
AuthStateProvider::as_ref(app).get().username_for_display()
|
||||
}
|
||||
|
||||
fn current_user_uid(app: &AppContext) -> Option<String> {
|
||||
AuthStateProvider::as_ref(app)
|
||||
.get()
|
||||
.user_id()
|
||||
.map(|uid| uid.to_string())
|
||||
}
|
||||
|
||||
fn task_session_id(task: &AmbientAgentTask) -> Option<SessionId> {
|
||||
task.session_id.as_deref().and_then(parse_session_id)
|
||||
}
|
||||
|
||||
fn task_session_status(task: &AmbientAgentTask) -> SessionStatus {
|
||||
if FeatureFlag::CloudConversations.is_enabled() {
|
||||
return if task.active_run_execution().session_link.is_some() {
|
||||
SessionStatus::Available
|
||||
} else {
|
||||
SessionStatus::Unavailable
|
||||
};
|
||||
}
|
||||
|
||||
if task.active_run_execution().session_id.is_some() {
|
||||
SessionStatus::Available
|
||||
} else if (Utc::now() - task.created_at) > SESSION_EXPIRATION_TIME {
|
||||
SessionStatus::Expired
|
||||
} else {
|
||||
SessionStatus::Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
fn task_run_time(task: &AmbientAgentTask) -> Option<String> {
|
||||
task.run_time().map(human_readable_precise_duration)
|
||||
}
|
||||
|
||||
fn task_harness(task: &AmbientAgentTask) -> Option<Harness> {
|
||||
task.agent_config_snapshot.as_ref().and_then(|config| {
|
||||
config
|
||||
.harness
|
||||
.as_ref()
|
||||
.map(|harness| harness.harness_type)
|
||||
.or(Some(Harness::Oz))
|
||||
})
|
||||
}
|
||||
|
||||
fn conversation_title(
|
||||
metadata: &ConversationMetadata,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
) -> String {
|
||||
history_model
|
||||
.conversation(&metadata.nav_data.id)
|
||||
.and_then(|conversation| conversation.title().clone())
|
||||
.unwrap_or(metadata.nav_data.title.clone())
|
||||
}
|
||||
|
||||
fn conversation_display_status(
|
||||
metadata: &ConversationMetadata,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
) -> AgentRunDisplayStatus {
|
||||
history_model
|
||||
.conversation(&metadata.nav_data.id)
|
||||
.map(|conversation| AgentRunDisplayStatus::from_conversation_status(conversation.status()))
|
||||
.unwrap_or(AgentRunDisplayStatus::ConversationSucceeded)
|
||||
}
|
||||
|
||||
fn conversation_request_usage(
|
||||
metadata: &ConversationMetadata,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
) -> Option<f32> {
|
||||
history_model
|
||||
.conversation(&metadata.nav_data.id)
|
||||
.map(|conversation| conversation.credits_spent())
|
||||
.or_else(|| {
|
||||
history_model
|
||||
.get_conversation_metadata(&metadata.nav_data.id)
|
||||
.and_then(|metadata| metadata.credits_spent)
|
||||
})
|
||||
}
|
||||
|
||||
fn conversation_artifacts(
|
||||
metadata: &ConversationMetadata,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
) -> Vec<Artifact> {
|
||||
history_model
|
||||
.conversation(&metadata.nav_data.id)
|
||||
.map(|conversation| conversation.artifacts().to_vec())
|
||||
.or_else(|| {
|
||||
history_model
|
||||
.get_conversation_metadata(&metadata.nav_data.id)
|
||||
.map(|metadata| metadata.artifacts.clone())
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn principal_from_user_profile(profile: &UserProfileWithUID) -> AgentConversationPrincipal {
|
||||
let name = profile
|
||||
.display_name
|
||||
.as_ref()
|
||||
.filter(|name| !name.is_empty())
|
||||
.or_else(|| (!profile.email.is_empty()).then_some(&profile.email))
|
||||
.cloned()
|
||||
.or_else(|| Some(profile.firebase_uid.to_string()));
|
||||
|
||||
AgentConversationPrincipal {
|
||||
name,
|
||||
uid: Some(profile.firebase_uid.to_string()),
|
||||
principal_type: Some(PrincipalType::User),
|
||||
}
|
||||
}
|
||||
|
||||
fn conversation_creator(
|
||||
metadata: &ConversationMetadata,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
app: &AppContext,
|
||||
) -> AgentConversationPrincipal {
|
||||
let server_metadata = history_model.get_server_conversation_metadata(&metadata.nav_data.id);
|
||||
if let Some(profile) = server_metadata.and_then(|metadata| metadata.creator.as_ref()) {
|
||||
return principal_from_user_profile(profile);
|
||||
}
|
||||
|
||||
if let Some(uid) = server_metadata.and_then(|metadata| metadata.metadata.creator_uid.as_ref()) {
|
||||
return AgentConversationPrincipal {
|
||||
name: UserProfiles::as_ref(app).displayable_identifier_for_uid(UserUid::new(uid)),
|
||||
uid: Some(uid.clone()),
|
||||
principal_type: Some(PrincipalType::User),
|
||||
};
|
||||
}
|
||||
|
||||
AgentConversationPrincipal {
|
||||
name: current_user_name(app),
|
||||
uid: current_user_uid(app),
|
||||
principal_type: Some(PrincipalType::User),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn entry_for_task(
|
||||
task: &AmbientAgentTask,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
app: &AppContext,
|
||||
) -> AgentConversationEntry {
|
||||
let local_conversation_id = conversation_id_shadowed_by_task(task, history_model);
|
||||
let conversation_metadata =
|
||||
local_conversation_id.and_then(|id| history_model.get_conversation_metadata(&id));
|
||||
let server_conversation_token = task
|
||||
.conversation_id()
|
||||
.map(|id| ServerConversationToken::new(id.to_string()))
|
||||
.or_else(|| {
|
||||
local_conversation_id.and_then(|conversation_id| {
|
||||
server_conversation_token_for_conversation(conversation_id, None, history_model)
|
||||
})
|
||||
});
|
||||
let status = AgentRunDisplayStatus::from_task(task, app);
|
||||
let has_active_session_id = task
|
||||
.active_execution_session_id()
|
||||
.and_then(parse_session_id)
|
||||
.is_some();
|
||||
let has_open_ambient_session = ActiveAgentViewsModel::as_ref(app)
|
||||
.get_terminal_view_id_for_ambient_task(task.task_id)
|
||||
.is_some();
|
||||
let can_open = has_open_ambient_session
|
||||
|| has_active_session_id
|
||||
|| local_conversation_id.is_some()
|
||||
|| server_conversation_token.is_some();
|
||||
let can_copy_link = task.has_active_execution()
|
||||
&& task.active_run_execution().session_link.is_some()
|
||||
|| server_conversation_token.is_some();
|
||||
|
||||
AgentConversationEntry {
|
||||
id: AgentConversationEntryId::AmbientRun(task.task_id),
|
||||
identity: AgentConversationIdentity {
|
||||
local_conversation_id,
|
||||
ambient_agent_task_id: Some(task.task_id),
|
||||
server_conversation_token,
|
||||
session_id: task_session_id(task),
|
||||
},
|
||||
provenance: AgentConversationProvenance::AmbientRun,
|
||||
display: AgentConversationDisplayData {
|
||||
title: task.title.clone(),
|
||||
initial_query: Some(task.prompt.clone()),
|
||||
created_at: task.created_at,
|
||||
last_updated: task.updated_at,
|
||||
status: status.clone(),
|
||||
creator: AgentConversationPrincipal {
|
||||
name: task_creator_name(task, app),
|
||||
uid: task_creator_uid(task),
|
||||
principal_type: task
|
||||
.creator
|
||||
.as_ref()
|
||||
.and_then(|c| PrincipalType::parse(&c.creator_type)),
|
||||
},
|
||||
executor: task
|
||||
.executor
|
||||
.as_ref()
|
||||
.map(|executor| AgentConversationPrincipal {
|
||||
name: executor.display_name.clone(),
|
||||
uid: Some(executor.uid.clone()),
|
||||
principal_type: PrincipalType::parse(&executor.creator_type),
|
||||
}),
|
||||
request_usage: task.credits_used(),
|
||||
run_time: task_run_time(task),
|
||||
session_status: Some(task_session_status(task)),
|
||||
source: task.source.clone(),
|
||||
working_directory: conversation_metadata
|
||||
.and_then(|metadata| metadata.initial_working_directory.clone()),
|
||||
environment_id: task
|
||||
.agent_config_snapshot
|
||||
.as_ref()
|
||||
.and_then(|snapshot| snapshot.environment_id.clone()),
|
||||
harness: task_harness(task),
|
||||
artifacts: task.artifacts.clone(),
|
||||
},
|
||||
backing: AgentConversationBackingData {
|
||||
has_loaded_conversation: local_conversation_id
|
||||
.is_some_and(|id| history_model.conversation(&id).is_some()),
|
||||
has_local_persisted_data: conversation_metadata
|
||||
.is_some_and(|metadata| metadata.has_local_data),
|
||||
has_cloud_data: conversation_metadata.is_some_and(|metadata| metadata.has_cloud_data)
|
||||
|| task.conversation_id().is_some(),
|
||||
has_ambient_run: true,
|
||||
},
|
||||
capabilities: AgentConversationCapabilities {
|
||||
can_open,
|
||||
can_copy_link,
|
||||
can_share: task.conversation_id().is_some()
|
||||
|| local_conversation_id
|
||||
.is_some_and(|id| history_model.can_conversation_be_shared(&id)),
|
||||
can_delete: false,
|
||||
can_fork_locally: local_conversation_id.is_some(),
|
||||
can_cancel: status.is_cancellable(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn entry_for_conversation(
|
||||
metadata: &ConversationMetadata,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
app: &AppContext,
|
||||
) -> AgentConversationEntry {
|
||||
let conversation_metadata = history_model.get_conversation_metadata(&metadata.nav_data.id);
|
||||
entry_for_conversation_parts(
|
||||
metadata.nav_data.clone(),
|
||||
conversation_metadata,
|
||||
history_model,
|
||||
app,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn entry_for_historical_metadata(
|
||||
metadata: &AIConversationMetadata,
|
||||
nav_data: ConversationNavigationData,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
app: &AppContext,
|
||||
) -> AgentConversationEntry {
|
||||
entry_for_conversation_parts(nav_data, Some(metadata), history_model, app)
|
||||
}
|
||||
|
||||
fn entry_for_conversation_parts(
|
||||
nav_data: ConversationNavigationData,
|
||||
conversation_metadata: Option<&AIConversationMetadata>,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
app: &AppContext,
|
||||
) -> AgentConversationEntry {
|
||||
let metadata = ConversationMetadata { nav_data };
|
||||
let conversation_id = metadata.nav_data.id;
|
||||
let status = conversation_display_status(&metadata, history_model);
|
||||
let has_loaded_conversation = history_model.conversation(&conversation_id).is_some();
|
||||
let has_local_persisted_data = conversation_metadata
|
||||
.is_some_and(|metadata| metadata.has_local_data)
|
||||
|| has_loaded_conversation;
|
||||
let has_cloud_data = conversation_metadata.is_some_and(|metadata| metadata.has_cloud_data)
|
||||
|| server_conversation_token_for_conversation(
|
||||
conversation_id,
|
||||
Some(&metadata.nav_data),
|
||||
history_model,
|
||||
)
|
||||
.is_some();
|
||||
let provenance = if has_cloud_data {
|
||||
AgentConversationProvenance::CloudSyncedConversation
|
||||
} else {
|
||||
AgentConversationProvenance::LocalInteractive
|
||||
};
|
||||
|
||||
AgentConversationEntry {
|
||||
id: AgentConversationEntryId::Conversation(conversation_id),
|
||||
identity: AgentConversationIdentity {
|
||||
local_conversation_id: Some(conversation_id),
|
||||
ambient_agent_task_id: conversation_metadata
|
||||
.and_then(|metadata| metadata.server_conversation_metadata.as_ref())
|
||||
.and_then(|metadata| metadata.ambient_agent_task_id),
|
||||
server_conversation_token: server_conversation_token_for_conversation(
|
||||
conversation_id,
|
||||
Some(&metadata.nav_data),
|
||||
history_model,
|
||||
),
|
||||
session_id: None,
|
||||
},
|
||||
provenance,
|
||||
display: AgentConversationDisplayData {
|
||||
title: conversation_title(&metadata, history_model),
|
||||
initial_query: metadata.nav_data.initial_query.clone(),
|
||||
created_at: metadata.nav_data.last_updated.into(),
|
||||
last_updated: metadata.nav_data.last_updated.into(),
|
||||
status: status.clone(),
|
||||
creator: conversation_creator(&metadata, history_model, app),
|
||||
executor: None,
|
||||
request_usage: conversation_request_usage(&metadata, history_model),
|
||||
run_time: None,
|
||||
session_status: None,
|
||||
source: Some(AgentSource::Interactive),
|
||||
working_directory: metadata
|
||||
.nav_data
|
||||
.latest_working_directory
|
||||
.clone()
|
||||
.or_else(|| metadata.nav_data.initial_working_directory.clone()),
|
||||
environment_id: None,
|
||||
harness: conversation_metadata
|
||||
.and_then(|metadata| metadata.server_conversation_metadata.as_ref())
|
||||
.map(|metadata| Harness::from(metadata.harness))
|
||||
.or(Some(Harness::Oz)),
|
||||
artifacts: conversation_artifacts(&metadata, history_model),
|
||||
},
|
||||
backing: AgentConversationBackingData {
|
||||
has_loaded_conversation,
|
||||
has_local_persisted_data,
|
||||
has_cloud_data,
|
||||
has_ambient_run: conversation_metadata
|
||||
.is_some_and(AIConversationMetadata::is_ambient_agent_conversation),
|
||||
},
|
||||
capabilities: AgentConversationCapabilities {
|
||||
can_open: has_local_persisted_data || has_cloud_data,
|
||||
can_copy_link: server_conversation_token_for_conversation(
|
||||
conversation_id,
|
||||
Some(&metadata.nav_data),
|
||||
history_model,
|
||||
)
|
||||
.is_some(),
|
||||
can_share: history_model.can_conversation_be_shared(&conversation_id),
|
||||
can_delete: has_local_persisted_data,
|
||||
can_fork_locally: has_local_persisted_data,
|
||||
can_cancel: status.is_cancellable(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn server_conversation_token_for_conversation(
|
||||
conversation_id: AIConversationId,
|
||||
nav_data: Option<&ConversationNavigationData>,
|
||||
history_model: &BlocklistAIHistoryModel,
|
||||
) -> Option<ServerConversationToken> {
|
||||
history_model
|
||||
.conversation(&conversation_id)
|
||||
.and_then(|conversation| conversation.server_conversation_token())
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
history_model
|
||||
.get_conversation_metadata(&conversation_id)
|
||||
.and_then(|metadata| metadata.server_conversation_token.clone())
|
||||
})
|
||||
.or_else(|| nav_data.and_then(|nav_data| nav_data.server_conversation_token.clone()))
|
||||
}
|
||||
|
||||
pub(super) fn parse_session_id(session_id: &str) -> Option<SessionId> {
|
||||
match session_id.parse::<SessionId>() {
|
||||
Ok(session_id) => Some(session_id),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse shared session ID: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,46 +7,102 @@ use futures::future::Either;
|
||||
use futures::StreamExt;
|
||||
use galaxyui::r#async::Timer;
|
||||
use instant::Instant;
|
||||
use galaxy_core::errors::AnyhowErrorExt as _;
|
||||
|
||||
use crate::server::retry_strategies::is_transient_http_error;
|
||||
use crate::server::server_api::ai::AgentRunEvent;
|
||||
use crate::server::server_api::presigned_upload::HttpStatusError;
|
||||
use crate::server::server_api::ServerApi;
|
||||
|
||||
pub(crate) const DEFAULT_AGENT_EVENT_RECONNECT_BACKOFF_STEPS: &[u64] = &[1, 2, 5, 10];
|
||||
pub(crate) const DEFAULT_PERMANENT_ERROR_BACKOFF_STEPS: &[u64] = &[30];
|
||||
pub(crate) const DEFAULT_AGENT_EVENT_PROACTIVE_RECONNECT: Duration = Duration::from_secs(14 * 60);
|
||||
pub(crate) const DEFAULT_AGENT_EVENT_FAILURES_BEFORE_ERROR_LOG: usize = 5;
|
||||
|
||||
/// Selects which server-side filter shape an [`AgentEventSource`] should use
|
||||
/// when opening a stream.
|
||||
///
|
||||
/// `RunIds` maps to the `?run_ids[]=` query parameter on the SSE endpoint
|
||||
/// and is used by child-only per-conversation streams and the dormant
|
||||
/// Claude wake listener. `AncestorRunId` maps to the `?ancestor_run_id=`
|
||||
/// shape: with `include_self=false` it streams events for every direct
|
||||
/// child of the supplied parent run (the shared-session viewer's pill bar),
|
||||
/// and with `include_self=true` it additionally streams the parent run's
|
||||
/// own events so an owner-side orchestrator can receive child lifecycle
|
||||
/// events plus its own inbox on one ordered stream.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum AgentEventFilter {
|
||||
/// One stream per multiplexed set of run IDs. Matches today's
|
||||
/// `?run_ids[]=` endpoint.
|
||||
RunIds(Vec<String>),
|
||||
/// Stream events for every direct child of the supplied parent run, and
|
||||
/// (when `include_self` is true) the parent run itself. Matches the
|
||||
/// `?ancestor_run_id=` endpoint.
|
||||
AncestorRunId {
|
||||
ancestor_run_id: String,
|
||||
include_self: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl AgentEventFilter {
|
||||
/// Returns a short debug label used in driver log lines so we don't have
|
||||
/// to format the full `Vec<String>` payload on every retry.
|
||||
pub(crate) fn log_label(&self) -> String {
|
||||
match self {
|
||||
AgentEventFilter::RunIds(ids) => format!("run_ids={ids:?}"),
|
||||
AgentEventFilter::AncestorRunId {
|
||||
ancestor_run_id,
|
||||
include_self,
|
||||
} => format!("ancestor_run_id={ancestor_run_id} include_self={include_self}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the shared agent-event stream driver.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct AgentEventDriverConfig {
|
||||
/// Run IDs whose events should be multiplexed into a single stream.
|
||||
pub run_ids: Vec<String>,
|
||||
/// Wire-level filter selecting which run IDs the stream serves. Either a
|
||||
/// concrete multiplexed list of run IDs or an ancestor-scoped child set;
|
||||
/// see [`AgentEventFilter`].
|
||||
pub filter: AgentEventFilter,
|
||||
/// Last fully handled event sequence. Events at or below this cursor are
|
||||
/// ignored on reconnect so the consumer only sees new work.
|
||||
pub since_sequence: i64,
|
||||
/// Exponential-ish reconnect delays, in seconds, used after stream open
|
||||
/// failures, stream errors, and clean stream termination.
|
||||
/// Exponential-ish reconnect delays, in seconds, used after transient
|
||||
/// stream failures (5xx, timeouts, connection resets).
|
||||
pub reconnect_backoff_steps: &'static [u64],
|
||||
/// Reconnect delays for permanent HTTP errors (4xx other than 408/429).
|
||||
/// Typically much slower than transient backoff to reduce log spam while
|
||||
/// still allowing recovery if the error was spurious.
|
||||
pub permanent_error_backoff_steps: &'static [u64],
|
||||
/// Optional deadline for proactively recycling an otherwise healthy stream
|
||||
/// before upstream infrastructure times it out (for example, before Cloud
|
||||
/// Run's 20-minute streaming timeout).
|
||||
pub proactive_reconnect_after: Option<Duration>,
|
||||
/// Failure count at which reconnect logging is escalated from debug to warn.
|
||||
/// Failure count at which actionable reconnect failures are reported at Error level.
|
||||
/// This only affects log severity; retry behavior stays the same.
|
||||
pub failures_before_error_log: usize,
|
||||
}
|
||||
|
||||
impl AgentEventDriverConfig {
|
||||
/// Build the production reconnecting configuration used by long-lived
|
||||
/// orchestration and harness listeners.
|
||||
pub(crate) fn retry_forever(run_ids: Vec<String>, since_sequence: i64) -> Self {
|
||||
/// orchestration and harness listeners, parameterised on the wire filter.
|
||||
pub(crate) fn retry_forever(filter: AgentEventFilter, since_sequence: i64) -> Self {
|
||||
Self {
|
||||
run_ids,
|
||||
filter,
|
||||
since_sequence,
|
||||
reconnect_backoff_steps: DEFAULT_AGENT_EVENT_RECONNECT_BACKOFF_STEPS,
|
||||
permanent_error_backoff_steps: DEFAULT_PERMANENT_ERROR_BACKOFF_STEPS,
|
||||
proactive_reconnect_after: Some(DEFAULT_AGENT_EVENT_PROACTIVE_RECONNECT),
|
||||
failures_before_error_log: DEFAULT_AGENT_EVENT_FAILURES_BEFORE_ERROR_LOG,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience: build a `retry_forever` config from a concrete list of
|
||||
/// run IDs. Lets existing call sites keep their current ergonomics.
|
||||
pub(crate) fn retry_forever_run_ids(run_ids: Vec<String>, since_sequence: i64) -> Self {
|
||||
Self::retry_forever(AgentEventFilter::RunIds(run_ids), since_sequence)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tells the shared driver whether to continue or stop after a handled event.
|
||||
@@ -75,6 +131,27 @@ pub(crate) enum AgentEventDriverState {
|
||||
ProactiveReconnect,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct AgentMessageEventMetadata {
|
||||
pub sequence: i64,
|
||||
pub message_id: String,
|
||||
pub occurred_at: String,
|
||||
}
|
||||
|
||||
impl AgentMessageEventMetadata {
|
||||
pub(crate) fn from_event(event: &AgentRunEvent) -> Option<Self> {
|
||||
if event.event_type != "new_message" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
sequence: event.sequence,
|
||||
message_id: event.ref_id.clone()?,
|
||||
occurred_at: event.occurred_at.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsed items emitted by an [`AgentEventSource`].
|
||||
pub(crate) enum AgentEventSourceItem {
|
||||
Open,
|
||||
@@ -91,13 +168,13 @@ cfg_if::cfg_if! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens a stream of parsed agent events for one or more run IDs.
|
||||
/// Opens a stream of parsed agent events for the supplied filter.
|
||||
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_family = "wasm"), async_trait)]
|
||||
pub(crate) trait AgentEventSource: Send + Sync {
|
||||
async fn open_stream(
|
||||
&self,
|
||||
run_ids: &[String],
|
||||
filter: &AgentEventFilter,
|
||||
since_sequence: i64,
|
||||
) -> Result<AgentEventSourceStream>;
|
||||
}
|
||||
@@ -118,13 +195,28 @@ impl ServerApiAgentEventSource {
|
||||
impl AgentEventSource for ServerApiAgentEventSource {
|
||||
async fn open_stream(
|
||||
&self,
|
||||
run_ids: &[String],
|
||||
filter: &AgentEventFilter,
|
||||
since_sequence: i64,
|
||||
) -> Result<AgentEventSourceStream> {
|
||||
let stream = self
|
||||
.server_api
|
||||
.stream_agent_events(run_ids, since_sequence)
|
||||
.await?;
|
||||
let stream = match filter {
|
||||
AgentEventFilter::RunIds(run_ids) => {
|
||||
self.server_api
|
||||
.stream_agent_events(run_ids, since_sequence)
|
||||
.await?
|
||||
}
|
||||
AgentEventFilter::AncestorRunId {
|
||||
ancestor_run_id,
|
||||
include_self,
|
||||
} => {
|
||||
self.server_api
|
||||
.stream_agent_events_for_ancestor(
|
||||
ancestor_run_id,
|
||||
*include_self,
|
||||
since_sequence,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
let stream = stream.filter_map(|event_result| async move {
|
||||
match event_result {
|
||||
@@ -138,7 +230,29 @@ impl AgentEventSource for ServerApiAgentEventSource {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => Some(Err(anyhow!("SSE stream error: {err:?}"))),
|
||||
Err(err) => {
|
||||
let anyhow_err = match err {
|
||||
reqwest_eventsource::Error::InvalidStatusCode(status_code, response) => {
|
||||
let body = response
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|err| format!("(no response body: {err:#})"));
|
||||
let status_err = HttpStatusError {
|
||||
status: status_code.as_u16(),
|
||||
body: body.clone(),
|
||||
};
|
||||
anyhow::Error::new(status_err).context(format!(
|
||||
"SSE stream error: invalid status code {status_code}: {body}"
|
||||
))
|
||||
}
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
reqwest_eventsource::Error::Transport(err) => {
|
||||
anyhow::Error::new(err).context("SSE stream error")
|
||||
}
|
||||
err => anyhow!("SSE stream error: {err:?}"),
|
||||
};
|
||||
Some(Err(anyhow_err))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -188,18 +302,23 @@ where
|
||||
let mut has_connected_once = false;
|
||||
|
||||
loop {
|
||||
let mut stream = match source.open_stream(&config.run_ids, since_sequence).await {
|
||||
Ok(stream) => {
|
||||
failures = 0;
|
||||
has_connected_once = true;
|
||||
notify_driver_state(consumer, AgentEventDriverState::Connected).await;
|
||||
stream
|
||||
}
|
||||
// `open_stream` is lazy for the SSE-backed source: the TCP
|
||||
// connect happens when the stream is first polled, not when
|
||||
// this returns Ok. Wait for the `AgentEventSourceItem::Open`
|
||||
// event below before declaring connectivity, so a server
|
||||
// outage doesn't reset `failures` between every retry.
|
||||
let mut stream = match source.open_stream(&config.filter, since_sequence).await {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => {
|
||||
failures += 1;
|
||||
let backoff = agent_event_backoff(failures, config.reconnect_backoff_steps);
|
||||
let backoff_steps = if is_transient_http_error(&err) {
|
||||
config.reconnect_backoff_steps
|
||||
} else {
|
||||
config.permanent_error_backoff_steps
|
||||
};
|
||||
let backoff = agent_event_backoff(failures, backoff_steps);
|
||||
log_stream_failure(
|
||||
&config.run_ids,
|
||||
&config.filter,
|
||||
failures,
|
||||
backoff,
|
||||
&err,
|
||||
@@ -249,7 +368,12 @@ where
|
||||
}
|
||||
NextDriverItem::StreamItem(Some(Ok(AgentEventSourceItem::Open))) => {
|
||||
failures = 0;
|
||||
log::info!("Agent event stream opened for {:?}", config.run_ids);
|
||||
has_connected_once = true;
|
||||
notify_driver_state(consumer, AgentEventDriverState::Connected).await;
|
||||
log::info!(
|
||||
"Agent event stream opened for {}",
|
||||
config.filter.log_label()
|
||||
);
|
||||
}
|
||||
NextDriverItem::StreamItem(Some(Ok(AgentEventSourceItem::Event(event)))) => {
|
||||
failures = 0;
|
||||
@@ -273,9 +397,14 @@ where
|
||||
}
|
||||
NextDriverItem::StreamItem(Some(Err(err))) => {
|
||||
failures += 1;
|
||||
let backoff = agent_event_backoff(failures, config.reconnect_backoff_steps);
|
||||
let backoff_steps = if is_transient_http_error(&err) {
|
||||
config.reconnect_backoff_steps
|
||||
} else {
|
||||
config.permanent_error_backoff_steps
|
||||
};
|
||||
let backoff = agent_event_backoff(failures, backoff_steps);
|
||||
log_stream_failure(
|
||||
&config.run_ids,
|
||||
&config.filter,
|
||||
failures,
|
||||
backoff,
|
||||
&err,
|
||||
@@ -293,12 +422,15 @@ where
|
||||
Timer::after(backoff).await;
|
||||
break;
|
||||
}
|
||||
// Clean stream closure (server-side close, not an HTTP
|
||||
// error) — always use the transient backoff schedule since
|
||||
// there is no HTTP status to classify.
|
||||
NextDriverItem::StreamItem(None) => {
|
||||
failures += 1;
|
||||
let backoff = agent_event_backoff(failures, config.reconnect_backoff_steps);
|
||||
log::warn!(
|
||||
"Agent event stream closed for {:?}, reconnecting in {backoff:?}",
|
||||
config.run_ids
|
||||
"Agent event stream closed for {}, reconnecting in {backoff:?}",
|
||||
config.filter.log_label()
|
||||
);
|
||||
notify_driver_state(
|
||||
consumer,
|
||||
@@ -332,21 +464,20 @@ async fn notify_driver_state<C: AgentEventConsumer>(
|
||||
}
|
||||
|
||||
fn log_stream_failure(
|
||||
run_ids: &[String],
|
||||
filter: &AgentEventFilter,
|
||||
failures: usize,
|
||||
backoff: Duration,
|
||||
err: &anyhow::Error,
|
||||
failures_before_error_log: usize,
|
||||
) {
|
||||
if agent_event_failures_exceeded_threshold(failures, failures_before_error_log) {
|
||||
let label = filter.log_label();
|
||||
if agent_event_failure_should_log_error(err, failures, failures_before_error_log) {
|
||||
log::error!(
|
||||
"Agent event stream failed {failures} consecutive times for {:?}, retrying in {backoff:?}: {err:#}",
|
||||
run_ids
|
||||
"Agent event stream failed {failures} consecutive times for {label}, retrying in {backoff:?}: {err:#}"
|
||||
);
|
||||
} else {
|
||||
log::warn!(
|
||||
"Agent event stream failed for {:?}, retrying in {backoff:?}: {err:#}",
|
||||
run_ids
|
||||
"Agent event stream failed {failures} consecutive times for {label}, retrying in {backoff:?}: {err:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -361,6 +492,15 @@ pub(crate) fn agent_event_backoff(failures: usize, backoff_steps: &[u64]) -> Dur
|
||||
Duration::from_secs(safe_steps[index])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn agent_event_failures_exceeded_threshold(failures: usize, threshold: usize) -> bool {
|
||||
failures >= threshold
|
||||
}
|
||||
|
||||
pub(crate) fn agent_event_failure_should_log_error(
|
||||
err: &anyhow::Error,
|
||||
failures: usize,
|
||||
threshold: usize,
|
||||
) -> bool {
|
||||
threshold > 0 && failures == threshold && err.is_actionable()
|
||||
}
|
||||
|
||||