xqo 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xqo-3.0.0/LICENSE +21 -0
- xqo-3.0.0/PKG-INFO +130 -0
- xqo-3.0.0/README.md +95 -0
- xqo-3.0.0/pyproject.toml +52 -0
- xqo-3.0.0/setup.cfg +4 -0
- xqo-3.0.0/tests/test_alerts.py +129 -0
- xqo-3.0.0/tests/test_artifacts.py +260 -0
- xqo-3.0.0/tests/test_artifacts_groups.py +104 -0
- xqo-3.0.0/tests/test_attachments.py +128 -0
- xqo-3.0.0/tests/test_attention.py +180 -0
- xqo-3.0.0/tests/test_avatars.py +158 -0
- xqo-3.0.0/tests/test_chat.py +399 -0
- xqo-3.0.0/tests/test_chat_attachments.py +129 -0
- xqo-3.0.0/tests/test_compaction.py +76 -0
- xqo-3.0.0/tests/test_config_limits.py +75 -0
- xqo-3.0.0/tests/test_delete_path.py +115 -0
- xqo-3.0.0/tests/test_departments.py +283 -0
- xqo-3.0.0/tests/test_features.py +221 -0
- xqo-3.0.0/tests/test_folders.py +70 -0
- xqo-3.0.0/tests/test_git_replay.py +235 -0
- xqo-3.0.0/tests/test_grants_skills.py +283 -0
- xqo-3.0.0/tests/test_hardening.py +214 -0
- xqo-3.0.0/tests/test_layout.py +33 -0
- xqo-3.0.0/tests/test_lifecycle.py +257 -0
- xqo-3.0.0/tests/test_limits.py +119 -0
- xqo-3.0.0/tests/test_live_viewer.py +150 -0
- xqo-3.0.0/tests/test_markdown.py +124 -0
- xqo-3.0.0/tests/test_master_config.py +66 -0
- xqo-3.0.0/tests/test_max_tokens.py +76 -0
- xqo-3.0.0/tests/test_models.py +62 -0
- xqo-3.0.0/tests/test_move_file.py +78 -0
- xqo-3.0.0/tests/test_netgrants.py +140 -0
- xqo-3.0.0/tests/test_nickname.py +288 -0
- xqo-3.0.0/tests/test_nolimits_budget_time.py +150 -0
- xqo-3.0.0/tests/test_offer_judge.py +390 -0
- xqo-3.0.0/tests/test_office.py +371 -0
- xqo-3.0.0/tests/test_office_idle.py +305 -0
- xqo-3.0.0/tests/test_office_live.py +147 -0
- xqo-3.0.0/tests/test_office_paths.py +105 -0
- xqo-3.0.0/tests/test_office_recall.py +123 -0
- xqo-3.0.0/tests/test_office_resume_camera.py +89 -0
- xqo-3.0.0/tests/test_ops.py +191 -0
- xqo-3.0.0/tests/test_plugin_api.py +154 -0
- xqo-3.0.0/tests/test_plugins.py +250 -0
- xqo-3.0.0/tests/test_providers.py +184 -0
- xqo-3.0.0/tests/test_reports.py +101 -0
- xqo-3.0.0/tests/test_run_dir.py +54 -0
- xqo-3.0.0/tests/test_sdk_backend.py +647 -0
- xqo-3.0.0/tests/test_sdk_check.py +125 -0
- xqo-3.0.0/tests/test_select_new_task.py +78 -0
- xqo-3.0.0/tests/test_skill_match.py +33 -0
- xqo-3.0.0/tests/test_smoke.py +25 -0
- xqo-3.0.0/tests/test_store.py +73 -0
- xqo-3.0.0/tests/test_submit_feedback.py +117 -0
- xqo-3.0.0/tests/test_token_script.py +47 -0
- xqo-3.0.0/tests/test_venv.py +144 -0
- xqo-3.0.0/tests/test_write_paths.py +107 -0
- xqo-3.0.0/xqo/__init__.py +2 -0
- xqo-3.0.0/xqo/__main__.py +558 -0
- xqo-3.0.0/xqo/agent.py +950 -0
- xqo-3.0.0/xqo/api.py +46 -0
- xqo-3.0.0/xqo/artifacts.py +594 -0
- xqo-3.0.0/xqo/attachments.py +145 -0
- xqo-3.0.0/xqo/attention.py +241 -0
- xqo-3.0.0/xqo/avatars.py +91 -0
- xqo-3.0.0/xqo/backup.py +279 -0
- xqo-3.0.0/xqo/branding.py +37 -0
- xqo-3.0.0/xqo/bus.py +817 -0
- xqo-3.0.0/xqo/chat.py +420 -0
- xqo-3.0.0/xqo/client.py +35 -0
- xqo-3.0.0/xqo/compaction.py +114 -0
- xqo-3.0.0/xqo/config.py +291 -0
- xqo-3.0.0/xqo/default_agents.json +67 -0
- xqo-3.0.0/xqo/default_departments/engineering.json +54 -0
- xqo-3.0.0/xqo/default_departments/marketing.json +32 -0
- xqo-3.0.0/xqo/default_departments/research.json +30 -0
- xqo-3.0.0/xqo/departments.py +237 -0
- xqo-3.0.0/xqo/fences.py +238 -0
- xqo-3.0.0/xqo/folders.py +88 -0
- xqo-3.0.0/xqo/gitstore.py +165 -0
- xqo-3.0.0/xqo/grants.py +137 -0
- xqo-3.0.0/xqo/lifecycle.py +265 -0
- xqo-3.0.0/xqo/limits.py +133 -0
- xqo-3.0.0/xqo/llm.py +258 -0
- xqo-3.0.0/xqo/mcp_hub.py +218 -0
- xqo-3.0.0/xqo/memory.py +77 -0
- xqo-3.0.0/xqo/migrations.py +100 -0
- xqo-3.0.0/xqo/models.py +90 -0
- xqo-3.0.0/xqo/netgrants.py +82 -0
- xqo-3.0.0/xqo/offer_judge.py +316 -0
- xqo-3.0.0/xqo/patching.py +139 -0
- xqo-3.0.0/xqo/plugin.py +580 -0
- xqo-3.0.0/xqo/plugins/__init__.py +29 -0
- xqo-3.0.0/xqo/plugins/office/__init__.py +159 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/dexter_actions.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/dexter_idle.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/dexter_stand.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/dexter_walk.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/morty_actions.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/morty_idle.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/morty_stand.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/morty_walk.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/nova_actions.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/nova_idle.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/nova_stand.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/nova_walk.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/rio_actions.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/rio_idle.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/rio_stand.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/citizens/rio_walk.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/area_rug_lounge.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/bean_bag_chair_dark.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/bean_bag_chair_light.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/bookshelf_packed.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/coffee_bar_counter.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/ergonomic_chair.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/espresso_machine.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/large_window.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/low_coffee_table.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/mini_fridge.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/tall_potted_plant.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/wooden_desk_single.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/props/wooden_framed_whiteboard.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/tiles/concrete_accent.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/tiles/exposed_brick_wall.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office/tiles/main_floor.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office.html +149 -0
- xqo-3.0.0/xqo/plugins/office/static/office.js +508 -0
- xqo-3.0.0/xqo/plugins/office/static/office2d.js +305 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/README.txt +10 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/catalog.json +459 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/characters/LICENSE-kenney-mini-characters.txt +28 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/characters/Textures/colormap.png +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/characters/character-female-a.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/characters/character-female-d.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/characters/character-male-a.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/characters/character-male-c.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/furniture/LICENSE-kenney-furniture-kit.txt +23 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/furniture/chairDesk.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/furniture/computerKeyboard.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/furniture/computerMouse.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/furniture/computerScreen.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/furniture/desk.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d/furniture/pottedPlant.glb +0 -0
- xqo-3.0.0/xqo/plugins/office/static/office3d.js +889 -0
- xqo-3.0.0/xqo/plugins/office/static/officeedit.js +356 -0
- xqo-3.0.0/xqo/plugins/office/static/officeidle.js +89 -0
- xqo-3.0.0/xqo/plugins/office/static/officelayout.js +161 -0
- xqo-3.0.0/xqo/plugins/office/static/officepath.js +141 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/miniverse/LICENSE +21 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/miniverse/README.txt +3 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/miniverse/miniverse.js +2354 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/three/LICENSE +21 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/three/README.txt +3 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/three/addons/controls/OrbitControls.js +1972 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/three/addons/loaders/GLTFLoader.js +4925 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/three/addons/utils/BufferGeometryUtils.js +1501 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/three/addons/utils/SkeletonUtils.js +496 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/three/three.core.js +60586 -0
- xqo-3.0.0/xqo/plugins/office/static/vendor/three/three.module.js +19719 -0
- xqo-3.0.0/xqo/plugins/office/view.py +486 -0
- xqo-3.0.0/xqo/providers.py +293 -0
- xqo-3.0.0/xqo/sandbox.py +666 -0
- xqo-3.0.0/xqo/sdk_backend.py +646 -0
- xqo-3.0.0/xqo/sdk_check.py +158 -0
- xqo-3.0.0/xqo/skills.py +182 -0
- xqo-3.0.0/xqo/static/app.css +244 -0
- xqo-3.0.0/xqo/static/index.html +114 -0
- xqo-3.0.0/xqo/static/js/agents.js +83 -0
- xqo-3.0.0/xqo/static/js/artifacts.js +185 -0
- xqo-3.0.0/xqo/static/js/attention.js +101 -0
- xqo-3.0.0/xqo/static/js/compose.js +145 -0
- xqo-3.0.0/xqo/static/js/core.js +91 -0
- xqo-3.0.0/xqo/static/js/dialogs.js +86 -0
- xqo-3.0.0/xqo/static/js/graph.js +113 -0
- xqo-3.0.0/xqo/static/js/main.js +80 -0
- xqo-3.0.0/xqo/static/js/timeline.js +117 -0
- xqo-3.0.0/xqo/static/render.js +236 -0
- xqo-3.0.0/xqo/static/usage.js +32 -0
- xqo-3.0.0/xqo/static/vendor/VERSIONS.txt +9 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Main-Bold.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Main-Italic.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Main-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Math-Italic.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Script-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- xqo-3.0.0/xqo/static/vendor/highlight.min.js +1213 -0
- xqo-3.0.0/xqo/static/vendor/hljs-github-dark.min.css +10 -0
- xqo-3.0.0/xqo/static/vendor/katex.min.css +1 -0
- xqo-3.0.0/xqo/static/vendor/katex.min.js +1 -0
- xqo-3.0.0/xqo/static/vendor/markdown-it-footnote.min.js +2 -0
- xqo-3.0.0/xqo/static/vendor/markdown-it-task-lists.min.js +2 -0
- xqo-3.0.0/xqo/static/vendor/markdown-it.min.js +2 -0
- xqo-3.0.0/xqo/static/vendor/mermaid.min.js +2314 -0
- xqo-3.0.0/xqo/static/vendor/purify.min.js +3 -0
- xqo-3.0.0/xqo/store.py +228 -0
- xqo-3.0.0/xqo/testing.py +61 -0
- xqo-3.0.0/xqo/usage.py +115 -0
- xqo-3.0.0/xqo/web.py +711 -0
- xqo-3.0.0/xqo.egg-info/PKG-INFO +130 -0
- xqo-3.0.0/xqo.egg-info/SOURCES.txt +217 -0
- xqo-3.0.0/xqo.egg-info/dependency_links.txt +1 -0
- xqo-3.0.0/xqo.egg-info/entry_points.txt +2 -0
- xqo-3.0.0/xqo.egg-info/requires.txt +24 -0
- xqo-3.0.0/xqo.egg-info/top_level.txt +1 -0
xqo-3.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dudikkocak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
xqo-3.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xqo
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: xqo: a small, plugin-based HQ of Claude agents with a live dashboard and a 2D / 3D office
|
|
5
|
+
Author: dudikkocak
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dudikkocak/xqo
|
|
8
|
+
Project-URL: Issues, https://github.com/dudikkocak/xqo/issues
|
|
9
|
+
Keywords: claude,agents,llm,orchestration,dashboard
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: anthropic>=2.2.0
|
|
16
|
+
Requires-Dist: fastapi>=0.110
|
|
17
|
+
Requires-Dist: uvicorn>=0.29
|
|
18
|
+
Requires-Dist: httpx>=0.27
|
|
19
|
+
Requires-Dist: claude-agent-sdk>=0.2.170
|
|
20
|
+
Provides-Extra: sqlite
|
|
21
|
+
Requires-Dist: xqo-sqlite==3.0.0; extra == "sqlite"
|
|
22
|
+
Provides-Extra: postgres
|
|
23
|
+
Requires-Dist: xqo-postgres==3.0.0; extra == "postgres"
|
|
24
|
+
Provides-Extra: docker
|
|
25
|
+
Requires-Dist: xqo-docker==3.0.0; extra == "docker"
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: xqo-sqlite==3.0.0; extra == "all"
|
|
28
|
+
Requires-Dist: xqo-postgres==3.0.0; extra == "all"
|
|
29
|
+
Requires-Dist: xqo-docker==3.0.0; extra == "all"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "dev"
|
|
33
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# xqo
|
|
37
|
+
|
|
38
|
+
A small, plugin-based HQ of Claude agents: agents take tasks, hand work to each other by skill, run code in a
|
|
39
|
+
sandbox and ask you when they need a decision, all on a live dashboard with a 2D / 3D office view.
|
|
40
|
+
|
|
41
|
+
The `xqo` package is the **core** (agents, safety, dashboard), the **office** plugin and the **Claude Agent SDK**,
|
|
42
|
+
with events stored in **jsonl**. Everything else is a plugin package you install when you need it:
|
|
43
|
+
|
|
44
|
+
| Package | Adds |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `xqo-schedules` | cron / every / at tasks |
|
|
47
|
+
| `xqo-feeds` | system, logs, files, journald, docker and push feeds that wake agents (needs schedules) |
|
|
48
|
+
| `xqo-channels` | Telegram, WhatsApp, Instagram, X, Threads, Gmail, SMS |
|
|
49
|
+
| `xqo-peering` | other HQs through an exchange folder or HTTP |
|
|
50
|
+
| `xqo-toolchains` | Node / Go / Rust / .NET setup for `run_code` |
|
|
51
|
+
| `xqo-docker` | the Docker sandbox backend for `run_code` |
|
|
52
|
+
| `xqo-agency` | `xqo agency`: import agents from agency-agents |
|
|
53
|
+
| `xqo-sqlite`, `xqo-postgres` | other event stores |
|
|
54
|
+
|
|
55
|
+
`pip install "xqo[all]"` installs every plugin. The plugin packages are being published one by one; until a
|
|
56
|
+
package is out, its feature is not available.
|
|
57
|
+
|
|
58
|
+
A plugin that is not installed is a **warning**, never an error (also with `"strict": true`): its config keys
|
|
59
|
+
(`schedules`, `channels`, `feeds`, `feed_rules`, `peering`) are ignored, the warning names the `pip install`
|
|
60
|
+
command, and the HQ starts. A `"storage"` backend or a `"sandbox"` backend that is not installed still stops
|
|
61
|
+
startup with the reason: events in another store are never silently shown as empty, and code never runs without
|
|
62
|
+
the isolation you asked for.
|
|
63
|
+
|
|
64
|
+
## Quick start
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install xqo # core + office + Claude Agent SDK (mcp comes with it)
|
|
68
|
+
xqo init # writes a starter agents.json (+ departments/)
|
|
69
|
+
xqo agents.json # = xqo --config agents.json serve -> http://127.0.0.1:8000
|
|
70
|
+
xqo --mock --config agents.json serve # offline, scripted model: no API key needed
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
A basic HQ (one office, 2D office only, no Docker, Claude Agent SDK by default, Anthropic / OpenAI APIs where an
|
|
74
|
+
agent asks for them) is in [examples/basic/agents.json](examples/basic/agents.json):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
xqo examples/basic/agents.json check # loads it, lists the plugins and the missing environment variables
|
|
78
|
+
xqo examples/basic/agents.json # serve
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Docker (Linux / macOS / WSL2: `./docker-startup.sh`, Windows: `docker-startup.bat`): [docs/DOCKER.md](docs/DOCKER.md),
|
|
82
|
+
Claude Agent SDK sign-in in Docker: [docs/CLAUDE-SDK.md](docs/CLAUDE-SDK.md).
|
|
83
|
+
|
|
84
|
+
## Core and plugins
|
|
85
|
+
|
|
86
|
+
| Core (always there) | Plugins (`xqo/plugins/`) |
|
|
87
|
+
|---|---|
|
|
88
|
+
| message bus, event log (jsonl), replay & fork | **office**: `/office`, 2D and/or 3D, editor *(on by default)* |
|
|
89
|
+
| agent loops: Claude Agent SDK (default), Anthropic API, OpenAI-compatible, Ollama | |
|
|
90
|
+
| workspaces, versions, git, attachments, notes | |
|
|
91
|
+
| chat, attention (requests + alerts, one inbox), reports | |
|
|
92
|
+
| departments, grants, skills, network grants, limits, budgets | |
|
|
93
|
+
| sandbox with the `local` backend (Python venvs), MCP hub | |
|
|
94
|
+
| dashboard, task lifecycle (archive + trash), limits, backup, drain, check | |
|
|
95
|
+
|
|
96
|
+
Turning plugins off:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
"plugins": {"disable": ["office"], "strict": false}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`/api/plugins` (and `/api/health`, `xqo check`, the `serve` banner) lists every plugin as **active**,
|
|
103
|
+
**inactive** (not configured), **disabled**, **missing** (not installed, or its module can't be imported; a
|
|
104
|
+
warning) or **failed** with the reason. A failed plugin's routes, tools and
|
|
105
|
+
dashboard buttons disappear; startup goes on. `"strict": true`, `XQO_STRICT=1` and `xqo check` turn plugin
|
|
106
|
+
errors back into startup errors. Security settings (grants, network grants, limits, departments) are core and
|
|
107
|
+
always fail closed.
|
|
108
|
+
|
|
109
|
+
Writing a plugin (its own pip package, importing only `xqo.api`), a storage or sandbox backend or dashboard UI:
|
|
110
|
+
see [PLUGINS.md](PLUGINS.md).
|
|
111
|
+
|
|
112
|
+
## Judged fit (optional)
|
|
113
|
+
|
|
114
|
+
Skill match stays the free first filter. With `"offer_judge": {"enabled": true, "model": "claude-haiku-5-5"}` a small model is asked only when two or more offers **tie** or the task is **large** (`large_chars`, default 2000), and it only affects who gets the handover. Prompt caching (shared prefix) and an in-memory fit cache (`cache_ttl`) keep the cost down; any failure falls back to the free ranking. Details: [docs/REFERENCE.md](docs/REFERENCE.md) (Departments → Judged fit).
|
|
115
|
+
|
|
116
|
+
## Storage
|
|
117
|
+
|
|
118
|
+
Events go to `logs/events.jsonl`, one line per event (`"storage": {"backend": "jsonl", "path": "events.jsonl"}`;
|
|
119
|
+
`path` is optional and relative to `logs/`). `xqo-sqlite` and `xqo-postgres` add `"backend": "sqlite"` and
|
|
120
|
+
`"backend": "postgres"`.
|
|
121
|
+
|
|
122
|
+
- Every task is restored at startup, whatever its age; only the recent 5000 events are kept in memory for the
|
|
123
|
+
live views (older events load on demand, e.g. a task's full history or `/api/tasks?roots=true&limit=50&offset=50`).
|
|
124
|
+
- A backend that is not installed, or can't open, stops startup with the reason. `XQO_STORAGE` overrides
|
|
125
|
+
`"backend"` for one run.
|
|
126
|
+
|
|
127
|
+
## Docs
|
|
128
|
+
|
|
129
|
+
- [docs/REFERENCE.md](docs/REFERENCE.md): every feature in detail (commands are `xqo …`)
|
|
130
|
+
- [docs/SETUP.md](docs/SETUP.md), [docs/OPERATIONS.md](docs/OPERATIONS.md): install, upgrade, backup
|
xqo-3.0.0/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# xqo
|
|
2
|
+
|
|
3
|
+
A small, plugin-based HQ of Claude agents: agents take tasks, hand work to each other by skill, run code in a
|
|
4
|
+
sandbox and ask you when they need a decision, all on a live dashboard with a 2D / 3D office view.
|
|
5
|
+
|
|
6
|
+
The `xqo` package is the **core** (agents, safety, dashboard), the **office** plugin and the **Claude Agent SDK**,
|
|
7
|
+
with events stored in **jsonl**. Everything else is a plugin package you install when you need it:
|
|
8
|
+
|
|
9
|
+
| Package | Adds |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `xqo-schedules` | cron / every / at tasks |
|
|
12
|
+
| `xqo-feeds` | system, logs, files, journald, docker and push feeds that wake agents (needs schedules) |
|
|
13
|
+
| `xqo-channels` | Telegram, WhatsApp, Instagram, X, Threads, Gmail, SMS |
|
|
14
|
+
| `xqo-peering` | other HQs through an exchange folder or HTTP |
|
|
15
|
+
| `xqo-toolchains` | Node / Go / Rust / .NET setup for `run_code` |
|
|
16
|
+
| `xqo-docker` | the Docker sandbox backend for `run_code` |
|
|
17
|
+
| `xqo-agency` | `xqo agency`: import agents from agency-agents |
|
|
18
|
+
| `xqo-sqlite`, `xqo-postgres` | other event stores |
|
|
19
|
+
|
|
20
|
+
`pip install "xqo[all]"` installs every plugin. The plugin packages are being published one by one; until a
|
|
21
|
+
package is out, its feature is not available.
|
|
22
|
+
|
|
23
|
+
A plugin that is not installed is a **warning**, never an error (also with `"strict": true`): its config keys
|
|
24
|
+
(`schedules`, `channels`, `feeds`, `feed_rules`, `peering`) are ignored, the warning names the `pip install`
|
|
25
|
+
command, and the HQ starts. A `"storage"` backend or a `"sandbox"` backend that is not installed still stops
|
|
26
|
+
startup with the reason: events in another store are never silently shown as empty, and code never runs without
|
|
27
|
+
the isolation you asked for.
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install xqo # core + office + Claude Agent SDK (mcp comes with it)
|
|
33
|
+
xqo init # writes a starter agents.json (+ departments/)
|
|
34
|
+
xqo agents.json # = xqo --config agents.json serve -> http://127.0.0.1:8000
|
|
35
|
+
xqo --mock --config agents.json serve # offline, scripted model: no API key needed
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A basic HQ (one office, 2D office only, no Docker, Claude Agent SDK by default, Anthropic / OpenAI APIs where an
|
|
39
|
+
agent asks for them) is in [examples/basic/agents.json](examples/basic/agents.json):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
xqo examples/basic/agents.json check # loads it, lists the plugins and the missing environment variables
|
|
43
|
+
xqo examples/basic/agents.json # serve
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Docker (Linux / macOS / WSL2: `./docker-startup.sh`, Windows: `docker-startup.bat`): [docs/DOCKER.md](docs/DOCKER.md),
|
|
47
|
+
Claude Agent SDK sign-in in Docker: [docs/CLAUDE-SDK.md](docs/CLAUDE-SDK.md).
|
|
48
|
+
|
|
49
|
+
## Core and plugins
|
|
50
|
+
|
|
51
|
+
| Core (always there) | Plugins (`xqo/plugins/`) |
|
|
52
|
+
|---|---|
|
|
53
|
+
| message bus, event log (jsonl), replay & fork | **office**: `/office`, 2D and/or 3D, editor *(on by default)* |
|
|
54
|
+
| agent loops: Claude Agent SDK (default), Anthropic API, OpenAI-compatible, Ollama | |
|
|
55
|
+
| workspaces, versions, git, attachments, notes | |
|
|
56
|
+
| chat, attention (requests + alerts, one inbox), reports | |
|
|
57
|
+
| departments, grants, skills, network grants, limits, budgets | |
|
|
58
|
+
| sandbox with the `local` backend (Python venvs), MCP hub | |
|
|
59
|
+
| dashboard, task lifecycle (archive + trash), limits, backup, drain, check | |
|
|
60
|
+
|
|
61
|
+
Turning plugins off:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
"plugins": {"disable": ["office"], "strict": false}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`/api/plugins` (and `/api/health`, `xqo check`, the `serve` banner) lists every plugin as **active**,
|
|
68
|
+
**inactive** (not configured), **disabled**, **missing** (not installed, or its module can't be imported; a
|
|
69
|
+
warning) or **failed** with the reason. A failed plugin's routes, tools and
|
|
70
|
+
dashboard buttons disappear; startup goes on. `"strict": true`, `XQO_STRICT=1` and `xqo check` turn plugin
|
|
71
|
+
errors back into startup errors. Security settings (grants, network grants, limits, departments) are core and
|
|
72
|
+
always fail closed.
|
|
73
|
+
|
|
74
|
+
Writing a plugin (its own pip package, importing only `xqo.api`), a storage or sandbox backend or dashboard UI:
|
|
75
|
+
see [PLUGINS.md](PLUGINS.md).
|
|
76
|
+
|
|
77
|
+
## Judged fit (optional)
|
|
78
|
+
|
|
79
|
+
Skill match stays the free first filter. With `"offer_judge": {"enabled": true, "model": "claude-haiku-5-5"}` a small model is asked only when two or more offers **tie** or the task is **large** (`large_chars`, default 2000), and it only affects who gets the handover. Prompt caching (shared prefix) and an in-memory fit cache (`cache_ttl`) keep the cost down; any failure falls back to the free ranking. Details: [docs/REFERENCE.md](docs/REFERENCE.md) (Departments → Judged fit).
|
|
80
|
+
|
|
81
|
+
## Storage
|
|
82
|
+
|
|
83
|
+
Events go to `logs/events.jsonl`, one line per event (`"storage": {"backend": "jsonl", "path": "events.jsonl"}`;
|
|
84
|
+
`path` is optional and relative to `logs/`). `xqo-sqlite` and `xqo-postgres` add `"backend": "sqlite"` and
|
|
85
|
+
`"backend": "postgres"`.
|
|
86
|
+
|
|
87
|
+
- Every task is restored at startup, whatever its age; only the recent 5000 events are kept in memory for the
|
|
88
|
+
live views (older events load on demand, e.g. a task's full history or `/api/tasks?roots=true&limit=50&offset=50`).
|
|
89
|
+
- A backend that is not installed, or can't open, stops startup with the reason. `XQO_STORAGE` overrides
|
|
90
|
+
`"backend"` for one run.
|
|
91
|
+
|
|
92
|
+
## Docs
|
|
93
|
+
|
|
94
|
+
- [docs/REFERENCE.md](docs/REFERENCE.md): every feature in detail (commands are `xqo …`)
|
|
95
|
+
- [docs/SETUP.md](docs/SETUP.md), [docs/OPERATIONS.md](docs/OPERATIONS.md): install, upgrade, backup
|
xqo-3.0.0/pyproject.toml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "xqo"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "xqo: a small, plugin-based HQ of Claude agents with a live dashboard and a 2D / 3D office"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{name = "dudikkocak"}]
|
|
13
|
+
keywords = ["claude", "agents", "llm", "orchestration", "dashboard"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"anthropic>=2.2.0",
|
|
20
|
+
"fastapi>=0.110",
|
|
21
|
+
"uvicorn>=0.29",
|
|
22
|
+
"httpx>=0.27",
|
|
23
|
+
"claude-agent-sdk>=0.2.170", # agents run as Claude Code sessions; also brings the mcp package
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
# One extra per plugin package, pinned to this release (all = every xqo-* plugin); more come with each phase.
|
|
28
|
+
sqlite = ["xqo-sqlite==3.0.0"]
|
|
29
|
+
postgres = ["xqo-postgres==3.0.0"]
|
|
30
|
+
docker = ["xqo-docker==3.0.0"]
|
|
31
|
+
all = ["xqo-sqlite==3.0.0", "xqo-postgres==3.0.0", "xqo-docker==3.0.0"]
|
|
32
|
+
dev = ["pytest>=8", "pytest-timeout>=2.3", "httpx>=0.27"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/dudikkocak/xqo"
|
|
36
|
+
Issues = "https://github.com/dudikkocak/xqo/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
xqo = "xqo.__main__:main"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools]
|
|
42
|
+
packages = {find = {include = ["xqo*"]}}
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.dynamic]
|
|
45
|
+
version = {attr = "xqo.__version__"}
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
xqo = ["static/*", "static/js/*", "static/vendor/*", "static/vendor/fonts/*", "default_agents.json", "default_departments/*", "plugins/office/static/*", "plugins/office/static/office/*/*", "plugins/office/static/office3d/*", "plugins/office/static/office3d/*/*", "plugins/office/static/office3d/*/*/*", "plugins/office/static/vendor/miniverse/*", "plugins/office/static/vendor/three/*", "plugins/office/static/vendor/three/addons/*/*"]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
52
|
+
timeout = 60
|
xqo-3.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Attention alerts: questions, errors, finished root tasks and auto-answered questions stay until dealt with."""
|
|
2
|
+
import asyncio
|
|
3
|
+
from types import SimpleNamespace as NS
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from fastapi.testclient import TestClient
|
|
8
|
+
|
|
9
|
+
from xqo.agent import Agent
|
|
10
|
+
from xqo.artifacts import ArtifactStore
|
|
11
|
+
from xqo.bus import MessageBus
|
|
12
|
+
from xqo.config import build
|
|
13
|
+
from xqo.models import Event, Task
|
|
14
|
+
from xqo.web import create_app
|
|
15
|
+
|
|
16
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def text(t):
|
|
20
|
+
return NS(content=[NS(type="text", text=t)], stop_reason="end_turn", usage=None, model="m")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def tool(name, **inp):
|
|
24
|
+
return NS(content=[NS(type="tool_use", id=f"t_{name}", name=name, input=inp)],
|
|
25
|
+
stop_reason="tool_use", usage=None, model="m")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Script:
|
|
29
|
+
def __init__(self, fn):
|
|
30
|
+
self.fn = fn
|
|
31
|
+
|
|
32
|
+
async def create(self, *, messages, **_):
|
|
33
|
+
return self.fn(sum(1 for m in messages if m["role"] == "assistant"), messages)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def make(tmp_path, fn, timeout=0.2):
|
|
37
|
+
store = ArtifactStore(tmp_path / "art")
|
|
38
|
+
b = MessageBus(tmp_path / "ev.jsonl")
|
|
39
|
+
b.artifacts = store
|
|
40
|
+
b.attention.timeout = timeout
|
|
41
|
+
b.register(Agent("a", ["x"], "sys", Script(fn), store))
|
|
42
|
+
return b
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def asker(turn, msgs):
|
|
46
|
+
if turn == 0:
|
|
47
|
+
return tool("ask_user", question="Deploy?", reason="approval", options=["yes", "no"], default="no")
|
|
48
|
+
return text("ok")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_finished_root_task_alerts_until_dismissed(tmp_path):
|
|
52
|
+
b = make(tmp_path, lambda turn, m: text("done!"))
|
|
53
|
+
t = asyncio.run(b.submit("go"))
|
|
54
|
+
al = b.attention.alerts_pending()
|
|
55
|
+
assert [a.kind for a in al] == ["done"] and al[0].agent == "a" and al[0].root_id == t.id
|
|
56
|
+
assert b.attention.by_agent() == {"a": {"count": 1, "kinds": ["done"]}}
|
|
57
|
+
assert b.attention.ack() == [al[0].id] and not b.attention.alerts_pending()
|
|
58
|
+
# survives a restart: the dismissal is logged too
|
|
59
|
+
assert not MessageBus(tmp_path / "ev.jsonl").attention.alerts_pending()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_alerts_survive_a_restart(tmp_path):
|
|
63
|
+
b = make(tmp_path, lambda turn, m: text("done!"))
|
|
64
|
+
asyncio.run(b.submit("go"))
|
|
65
|
+
assert [a.kind for a in MessageBus(tmp_path / "ev.jsonl").attention.alerts_pending()] == ["done"]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_auto_confirmed_question_keeps_alerting(tmp_path):
|
|
69
|
+
b = make(tmp_path, asker)
|
|
70
|
+
asyncio.run(b.submit("go"))
|
|
71
|
+
kinds = sorted(a.kind for a in b.attention.alerts_pending())
|
|
72
|
+
assert kinds == ["auto_confirmed", "done"]
|
|
73
|
+
auto = next(a for a in b.attention.alerts_pending() if a.kind == "auto_confirmed")
|
|
74
|
+
assert "'no' was taken" in auto.text
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_answered_question_clears_its_alert(tmp_path):
|
|
78
|
+
b = make(tmp_path, asker, timeout=30)
|
|
79
|
+
|
|
80
|
+
async def go():
|
|
81
|
+
job = asyncio.create_task(b.submit("go"))
|
|
82
|
+
while not b.attention.pending():
|
|
83
|
+
await asyncio.sleep(0.01)
|
|
84
|
+
assert [a.kind for a in b.attention.alerts_pending()] == ["attention"]
|
|
85
|
+
assert b.attention.ack() == [] # an open question stays until it is answered
|
|
86
|
+
b.attention.resolve(b.attention.pending()[0].id, "yes")
|
|
87
|
+
return await job
|
|
88
|
+
|
|
89
|
+
asyncio.run(go())
|
|
90
|
+
assert [a.kind for a in b.attention.alerts_pending()] == ["done"]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_errors_alert_and_subtasks_do_not_alert_when_done(tmp_path):
|
|
94
|
+
b = MessageBus(tmp_path / "ev.jsonl")
|
|
95
|
+
b.tasks["t1"] = Task("p", "a", id="t1", root_id="t1")
|
|
96
|
+
b.tasks["t2"] = Task("p", "b", parent_id="t1", id="t2", root_id="t1")
|
|
97
|
+
b.emit(Event("error", "a", "user", "t1", None, "Boom"))
|
|
98
|
+
b.emit(Event("done", "b", "a", "t2", "t1", "sub done", {"status": "done"})) # a sub-task: no alert
|
|
99
|
+
al = b.attention.alerts_pending()
|
|
100
|
+
assert [a.kind for a in al] == ["error"] and al[0].text == "Boom"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_cancelled_tasks_and_unattended_runs_do_not_alert(tmp_path):
|
|
104
|
+
b = MessageBus(tmp_path / "ev.jsonl")
|
|
105
|
+
b.emit(Event("done", "a", "user", "t1", None, "Cancelled", {"status": "cancelled"}))
|
|
106
|
+
assert not b.attention.alerts_pending()
|
|
107
|
+
b.emit(Event("attention_requested", "a", "user", "t1", None, "q?", {"id": "att_1", "timeout": 0, "agent": "a"}))
|
|
108
|
+
assert not b.attention.alerts_pending()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_deleting_or_reopening_a_task_clears_its_alerts(tmp_path):
|
|
112
|
+
b = MessageBus(tmp_path / "ev.jsonl")
|
|
113
|
+
b.emit(Event("done", "a", "user", "t1", None, "ok", {"status": "done"}))
|
|
114
|
+
b.emit(Event("error", "a", "user", "t2", None, "bad"))
|
|
115
|
+
b.emit(Event("task_reopened", "user", None, "t1", None, ""))
|
|
116
|
+
b.emit(Event("task_deleted", "user", None, "t2", None, ""))
|
|
117
|
+
assert not b.attention.alerts_pending()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_api(tmp_path):
|
|
121
|
+
b = build(ROOT / "agents.json", tmp_path / "art", str(tmp_path / "ev.jsonl"), mock=True)
|
|
122
|
+
name = next(iter(b.agents))
|
|
123
|
+
b.emit(Event("done", name, "user", "t1", None, "ok", {"status": "done"}))
|
|
124
|
+
with TestClient(create_app(b)) as c:
|
|
125
|
+
r = c.get("/api/alerts").json()
|
|
126
|
+
assert [a["kind"] for a in r["items"]] == ["done"] and r["by_agent"][name]["count"] == 1
|
|
127
|
+
assert next(a for a in c.get("/api/agents").json() if a["name"] == name)["alerts"]["count"] == 1
|
|
128
|
+
assert c.post("/api/alerts/ack", json={"agent": name}).json()["acked"] == [r["items"][0]["id"]]
|
|
129
|
+
assert c.get("/api/alerts").json()["items"] == []
|