devops-bot-sdk 1.6.19__tar.gz → 1.6.25__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.
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/devops_bot_sdk.egg-info/SOURCES.txt +20 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agentd.py +36 -1
- devops_bot_sdk-1.6.25/sdk/agents/activity.py +250 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/run_log.py +12 -1
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/cli.py +19 -1
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/client.py +147 -107
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/config.py +11 -0
- devops_bot_sdk-1.6.25/sdk/github_ingest.py +161 -0
- devops_bot_sdk-1.6.25/sdk/graph_recon.py +330 -0
- devops_bot_sdk-1.6.25/sdk/graphify.py +475 -0
- devops_bot_sdk-1.6.25/sdk/intake.py +410 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ipc/handlers.py +605 -133
- devops_bot_sdk-1.6.25/sdk/phase_timer.py +192 -0
- devops_bot_sdk-1.6.25/sdk/plan_run.py +375 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/run_auto.py +270 -19
- devops_bot_sdk-1.6.25/sdk/task_file.py +221 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/api.py +295 -99
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/models.py +65 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/security.py +5 -9
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/server.py +14 -74
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/app.js +496 -1
- devops_bot_sdk-1.6.25/sdk/ui/static/chat.html +154 -0
- devops_bot_sdk-1.6.25/sdk/ui/static/runs.html +79 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/styles.css +92 -0
- devops_bot_sdk-1.6.25/sdk/vps_health.py +111 -0
- devops_bot_sdk-1.6.25/tests/test_agent_activity_tracking.py +449 -0
- devops_bot_sdk-1.6.25/tests/test_epic_decompose_run.py +173 -0
- devops_bot_sdk-1.6.25/tests/test_epic_session_isolation.py +100 -0
- devops_bot_sdk-1.6.25/tests/test_graph_recon.py +245 -0
- devops_bot_sdk-1.6.25/tests/test_graphify_build.py +274 -0
- devops_bot_sdk-1.6.25/tests/test_graphify_hook_install.py +187 -0
- devops_bot_sdk-1.6.25/tests/test_intake_plan_review.py +177 -0
- devops_bot_sdk-1.6.25/tests/test_phase_timer.py +139 -0
- devops_bot_sdk-1.6.25/tests/test_plan_new_command.py +140 -0
- devops_bot_sdk-1.6.25/tests/test_plan_run_activity.py +210 -0
- devops_bot_sdk-1.6.25/tests/test_recon_session_isolation.py +122 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_run_auto_agent_only.py +9 -0
- devops_bot_sdk-1.6.25/tests/test_task_file_and_plan_run.py +392 -0
- devops_bot_sdk-1.6.25/tests/test_task_file_format_doc.py +283 -0
- devops_bot_sdk-1.6.25/tests/test_ticket_speed_fixes.py +318 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_update_timer.py +45 -1
- devops_bot_sdk-1.6.19/sdk/agents/activity.py +0 -117
- devops_bot_sdk-1.6.19/sdk/github_ingest.py +0 -570
- devops_bot_sdk-1.6.19/sdk/graphify.py +0 -172
- devops_bot_sdk-1.6.19/sdk/ui/static/runs.html +0 -42
- devops_bot_sdk-1.6.19/sdk/vps_health.py +0 -264
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/README.md +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/login.html +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_otp_login.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.19 → devops_bot_sdk-1.6.25}/tests/test_vps_health.py +0 -0
|
@@ -17,13 +17,18 @@ sdk/exceptions.py
|
|
|
17
17
|
sdk/formats.py
|
|
18
18
|
sdk/git_ops.py
|
|
19
19
|
sdk/github_ingest.py
|
|
20
|
+
sdk/graph_recon.py
|
|
20
21
|
sdk/graphify.py
|
|
22
|
+
sdk/intake.py
|
|
21
23
|
sdk/local_exec.py
|
|
24
|
+
sdk/phase_timer.py
|
|
25
|
+
sdk/plan_run.py
|
|
22
26
|
sdk/platform_compat.py
|
|
23
27
|
sdk/py.typed
|
|
24
28
|
sdk/run_auto.py
|
|
25
29
|
sdk/scheduler.py
|
|
26
30
|
sdk/sse.py
|
|
31
|
+
sdk/task_file.py
|
|
27
32
|
sdk/test.py
|
|
28
33
|
sdk/test_pipeline.py
|
|
29
34
|
sdk/ticket_fields.py
|
|
@@ -88,6 +93,7 @@ sdk/ui/session.py
|
|
|
88
93
|
sdk/ui/setup_manager.py
|
|
89
94
|
sdk/ui/static/agents.html
|
|
90
95
|
sdk/ui/static/app.js
|
|
96
|
+
sdk/ui/static/chat.html
|
|
91
97
|
sdk/ui/static/custom-agents.html
|
|
92
98
|
sdk/ui/static/default-agents.html
|
|
93
99
|
sdk/ui/static/index.html
|
|
@@ -98,6 +104,7 @@ sdk/ui/static/setup.html
|
|
|
98
104
|
sdk/ui/static/styles.css
|
|
99
105
|
sdk/ui/static/tickets.html
|
|
100
106
|
tests/test_agent_activity.py
|
|
107
|
+
tests/test_agent_activity_tracking.py
|
|
101
108
|
tests/test_agent_chat_features.py
|
|
102
109
|
tests/test_agent_listing.py
|
|
103
110
|
tests/test_agent_prompt_async.py
|
|
@@ -113,19 +120,32 @@ tests/test_config_token_diagnostics.py
|
|
|
113
120
|
tests/test_configure_token_owner.py
|
|
114
121
|
tests/test_console_encoding.py
|
|
115
122
|
tests/test_departments.py
|
|
123
|
+
tests/test_epic_decompose_run.py
|
|
116
124
|
tests/test_epic_inherited_agent_id.py
|
|
125
|
+
tests/test_epic_session_isolation.py
|
|
117
126
|
tests/test_formats.py
|
|
118
127
|
tests/test_git_action.py
|
|
119
128
|
tests/test_github_ingest.py
|
|
129
|
+
tests/test_graph_recon.py
|
|
130
|
+
tests/test_graphify_build.py
|
|
131
|
+
tests/test_graphify_hook_install.py
|
|
120
132
|
tests/test_health.py
|
|
133
|
+
tests/test_intake_plan_review.py
|
|
121
134
|
tests/test_otp_login.py
|
|
135
|
+
tests/test_phase_timer.py
|
|
136
|
+
tests/test_plan_new_command.py
|
|
137
|
+
tests/test_plan_run_activity.py
|
|
138
|
+
tests/test_recon_session_isolation.py
|
|
122
139
|
tests/test_run_auto_agent_only.py
|
|
123
140
|
tests/test_run_outputs.py
|
|
124
141
|
tests/test_schedule_api.py
|
|
125
142
|
tests/test_scheduler.py
|
|
126
143
|
tests/test_service_token_warning.py
|
|
144
|
+
tests/test_task_file_and_plan_run.py
|
|
145
|
+
tests/test_task_file_format_doc.py
|
|
127
146
|
tests/test_ticket_fields.py
|
|
128
147
|
tests/test_ticket_run_resolve_by_id.py
|
|
148
|
+
tests/test_ticket_speed_fixes.py
|
|
129
149
|
tests/test_update_timer.py
|
|
130
150
|
tests/test_updater_extras.py
|
|
131
151
|
tests/test_vps_health.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.25
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -34,7 +34,7 @@ Rules:
|
|
|
34
34
|
- All data egress through submit_webhook only
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "1.6.
|
|
37
|
+
__version__ = "1.6.25" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
|
|
38
38
|
__author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
|
|
39
39
|
|
|
40
40
|
from sdk.client import BackendClient
|
|
@@ -114,6 +114,22 @@ def _stop_requested() -> bool:
|
|
|
114
114
|
return _STOP_FILE.exists()
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
def _auto_loop_owns_queue() -> bool:
|
|
118
|
+
"""True when an `agentos-auto` loop is live and processing the ticket queue.
|
|
119
|
+
|
|
120
|
+
Reuses run_auto's own liveness check, which already distinguishes a running
|
|
121
|
+
daemon from a stale PID file left by a crash. Best-effort: if we cannot tell,
|
|
122
|
+
say False — refusing to dispatch on an unreadable PID file would silently
|
|
123
|
+
disable ticket-label agents, which is worse than the overlap we are avoiding.
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
from sdk import run_auto
|
|
127
|
+
|
|
128
|
+
return run_auto._daemon_running() is not None
|
|
129
|
+
except Exception: # noqa: BLE001
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
|
|
117
133
|
def _set_stop() -> None:
|
|
118
134
|
_AGENTOS_DIR.mkdir(parents=True, exist_ok=True)
|
|
119
135
|
_STOP_FILE.write_text("stop")
|
|
@@ -585,11 +601,30 @@ class AgentDaemon:
|
|
|
585
601
|
`_TICKET_POLL_INTERVAL_S` between iterations. Keeps an in-memory set
|
|
586
602
|
of already-dispatched task ids so a task is never re-fired on a later
|
|
587
603
|
poll within this daemon's lifetime.
|
|
604
|
+
|
|
605
|
+
DEFERS while an `agentos-auto` loop owns the queue. That loop processes
|
|
606
|
+
tickets strictly sequentially, oldest `createdAt` first; dispatching from
|
|
607
|
+
here concurrently (`asyncio.create_task` below) would run a ticket
|
|
608
|
+
alongside it and out of order — two agents editing the same working
|
|
609
|
+
directory at once, and a queue that no longer honours creation time.
|
|
610
|
+
Deferring costs at most one poll interval: the ticket stays To Do and is
|
|
611
|
+
picked up on a later tick, or by the auto-loop itself.
|
|
588
612
|
"""
|
|
589
613
|
dispatched_task_ids: set[str] = set()
|
|
590
614
|
ticket_agents = [m for m in self._manifests if m.trigger.type == "ticket_label"]
|
|
615
|
+
deferring = False
|
|
591
616
|
|
|
592
617
|
while not _stop_requested():
|
|
618
|
+
if ticket_agents and _auto_loop_owns_queue():
|
|
619
|
+
if not deferring:
|
|
620
|
+
logger.info("agentd.watch_tickets_deferred reason=auto_loop_running")
|
|
621
|
+
deferring = True
|
|
622
|
+
await asyncio.sleep(_TICKET_POLL_INTERVAL_S)
|
|
623
|
+
continue
|
|
624
|
+
if deferring:
|
|
625
|
+
logger.info("agentd.watch_tickets_resumed")
|
|
626
|
+
deferring = False
|
|
627
|
+
|
|
593
628
|
if ticket_agents:
|
|
594
629
|
try:
|
|
595
630
|
tasks = await self.client.list_todo_tasks_raw()
|
|
@@ -611,7 +646,7 @@ class AgentDaemon:
|
|
|
611
646
|
|
|
612
647
|
await asyncio.sleep(_TICKET_POLL_INTERVAL_S)
|
|
613
648
|
|
|
614
|
-
async def run_forever(self) -> None:
|
|
649
|
+
async def run_forever(self) -> None: # noqa: D401 — see class docstring
|
|
615
650
|
"""Load + schedule agents, start the cron scheduler, then watch tickets
|
|
616
651
|
until stopped. Shuts the scheduler down cleanly on the way out."""
|
|
617
652
|
await self.load_and_schedule()
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""Agent-activity reporting — the ONE place a custom / department agent run is
|
|
2
|
+
recorded, for both sinks:
|
|
3
|
+
|
|
4
|
+
* the LOCAL usage ledger (``sdk.agents.usage`` → ``~/.agentos/usage.jsonl``),
|
|
5
|
+
which backs ``GET /api/ui/agents/usage`` and, through it, the AI backend
|
|
6
|
+
report's "VPS Agent Logs" section, and
|
|
7
|
+
* the PLATFORM activity feed — ``POST {ML_GATEWAY}/ml-api/agent-activity``
|
|
8
|
+
with ``{activity, agentId}``, which the ML backend writes into the Postgres
|
|
9
|
+
``agent_activities`` table (docs/DATABASE_SCHEMA.md #3: ``user_id``,
|
|
10
|
+
``activity``, ``agent_id``) and the AI backend reads back RBAC-scoped for
|
|
11
|
+
reporting + chat.
|
|
12
|
+
|
|
13
|
+
Every VPS-side agent run goes through here no matter how it was triggered — the
|
|
14
|
+
control panel (chat / run / run-on-ticket), an ``agentd`` cron schedule, an
|
|
15
|
+
``agentd`` ticket-label trigger, the CLI, or the desktop IPC bridge. Before this
|
|
16
|
+
module only the control-panel paths emitted, so scheduled and ticket-triggered
|
|
17
|
+
runs never reached ``agent_activities``.
|
|
18
|
+
|
|
19
|
+
``agent_activities.agent_id`` is an FK onto ``agents.id``, so only agents that
|
|
20
|
+
exist in the BACKEND emit — ``BackendClient.resolve_agent_id`` returns None for
|
|
21
|
+
a local-only manifest and the emit is skipped (the local ledger still records
|
|
22
|
+
it). Both custom and department-default agents resolve, so both are tracked.
|
|
23
|
+
|
|
24
|
+
Best-effort throughout: nothing in here raises into a run.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
import os
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
from typing import TYPE_CHECKING, Any
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING: # pragma: no cover — typing only
|
|
34
|
+
from sdk.client import BackendClient
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger("sdk.agents.activity")
|
|
37
|
+
|
|
38
|
+
# How a run reached us → the verb that opens the activity line. Keep these short:
|
|
39
|
+
# the feed is rendered as one line per row in reports and in chat answers.
|
|
40
|
+
_VERB = {
|
|
41
|
+
"chat": "Chatted with",
|
|
42
|
+
"run": "Ran",
|
|
43
|
+
"run-ticket": "Ran",
|
|
44
|
+
"scheduled": "Ran (schedule)",
|
|
45
|
+
"ticket_label": "Ran (ticket trigger)",
|
|
46
|
+
"cli": "Ran (cli)",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def describe(name: str, kind: str, result: dict | None, duration_s: float = 0.0,
|
|
51
|
+
detail: str = "") -> str:
|
|
52
|
+
"""The human-readable ``activity`` string stored in ``agent_activities``."""
|
|
53
|
+
res = result or {}
|
|
54
|
+
verb = _VERB.get(kind, "Ran")
|
|
55
|
+
status = "ok" if res.get("ok") else "error"
|
|
56
|
+
line = f"{verb} agent '{name}' — {status}"
|
|
57
|
+
if duration_s:
|
|
58
|
+
line += f" ({round(float(duration_s), 1)}s)"
|
|
59
|
+
err = str(res.get("error") or "").strip()
|
|
60
|
+
if status == "error" and err:
|
|
61
|
+
line += f": {err[:200]}"
|
|
62
|
+
if detail:
|
|
63
|
+
line += f" — {detail}"
|
|
64
|
+
return line
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ── Run-log lines → the activity event ────────────────────────────────────
|
|
68
|
+
# Every line `run_log.log_line` writes to a run's Markdown file is also noted
|
|
69
|
+
# here, so the activity event a run produces carries what the run actually DID
|
|
70
|
+
# rather than only that it finished. The buffer exists because the alternative —
|
|
71
|
+
# a webhook POST per line — is exactly the pattern that saturated the shared
|
|
72
|
+
# Postgres pool and led to `client.ActivityFlusher` (see sdk/client.py:100).
|
|
73
|
+
#
|
|
74
|
+
# Two sinks, matching that design:
|
|
75
|
+
# * a ticket-scoped `ActivityFlusher`, when one is active (the ticket loop sets
|
|
76
|
+
# it) — `record()` does no I/O and the flusher coalesces into one POST every
|
|
77
|
+
# 3-5 minutes;
|
|
78
|
+
# * otherwise this per-run buffer, drained by `record_run` into the ONE
|
|
79
|
+
# per-run event. So an agent run costs one POST no matter how chatty it is.
|
|
80
|
+
_LINE_BUF: dict[str, list[str]] = {}
|
|
81
|
+
_LINE_BUF_MAX_LINES = 40 # keep the tail — recent lines are the useful ones
|
|
82
|
+
_LINE_BUF_MAX_CHARS = 4000 # bounded like _ACTIVITY_FLUSH_MAX_CHARS
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def note(bucket: str, text: str) -> None:
|
|
86
|
+
"""Record one run-log line against ``bucket`` (a run's output folder name).
|
|
87
|
+
|
|
88
|
+
Never raises and never does I/O: this is called from `run_log.log_line`, on
|
|
89
|
+
the hot path of a streaming engine run.
|
|
90
|
+
"""
|
|
91
|
+
line = str(text or "").strip()
|
|
92
|
+
if not line:
|
|
93
|
+
return
|
|
94
|
+
try:
|
|
95
|
+
from sdk.client import _ACTIVE_ACTIVITY_FLUSHER
|
|
96
|
+
flusher = _ACTIVE_ACTIVITY_FLUSHER.get()
|
|
97
|
+
except Exception: # noqa: BLE001
|
|
98
|
+
flusher = None
|
|
99
|
+
if flusher is not None:
|
|
100
|
+
flusher.record(line)
|
|
101
|
+
return
|
|
102
|
+
buf = _LINE_BUF.setdefault(str(bucket), [])
|
|
103
|
+
buf.append(line)
|
|
104
|
+
if len(buf) > _LINE_BUF_MAX_LINES:
|
|
105
|
+
del buf[: len(buf) - _LINE_BUF_MAX_LINES]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def drain(bucket: str) -> list[str]:
|
|
109
|
+
"""Take and clear the lines noted for ``bucket``."""
|
|
110
|
+
return _LINE_BUF.pop(str(bucket), [])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _lines_block(lines: list[str]) -> str:
|
|
114
|
+
"""The recorded lines as one bounded block, newest-biased."""
|
|
115
|
+
if not lines:
|
|
116
|
+
return ""
|
|
117
|
+
out: list[str] = []
|
|
118
|
+
total = 0
|
|
119
|
+
for line in reversed(lines): # newest first, then re-reverse
|
|
120
|
+
if total + len(line) + 1 > _LINE_BUF_MAX_CHARS:
|
|
121
|
+
break
|
|
122
|
+
out.append(line)
|
|
123
|
+
total += len(line) + 1
|
|
124
|
+
return "\n".join(reversed(out))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def mirror_to_auto_log(name: str, kind: str, result: dict | None,
|
|
128
|
+
duration_s: float = 0.0, *, detail: str = "",
|
|
129
|
+
task_id: str = "") -> bool:
|
|
130
|
+
"""Append a one-line summary of an agent run to ``~/.agentos/auto.log``.
|
|
131
|
+
|
|
132
|
+
`agentos-auto logs [-f]` tails that file and nothing else, so before this an
|
|
133
|
+
agent run — including a ticket-triggered one — was invisible there: it landed
|
|
134
|
+
in `agent_outputs/<agent>/*.md` and `usage.jsonl`, and an operator watching
|
|
135
|
+
the log saw the ticket loop go quiet with no explanation. Mirroring here
|
|
136
|
+
rather than at each trigger site is deliberate: every VPS-side run already
|
|
137
|
+
funnels through `record_run`, so one append covers the panel, the CLI, an
|
|
138
|
+
agentd cron schedule, an agentd ticket-label trigger and the IPC bridge.
|
|
139
|
+
|
|
140
|
+
A summary, not a transcript — the transcript stays in `agent_outputs/`, and
|
|
141
|
+
the pointer below is how you get from one to the other. Best-effort and
|
|
142
|
+
silent on failure: a log line must never break a run. Set
|
|
143
|
+
``AGENTOS_NO_AGENT_LOG_MIRROR=1`` to turn it off.
|
|
144
|
+
"""
|
|
145
|
+
if os.getenv("AGENTOS_NO_AGENT_LOG_MIRROR"):
|
|
146
|
+
return False
|
|
147
|
+
try:
|
|
148
|
+
from sdk.agents.run_log import agent_dir
|
|
149
|
+
from sdk.run_auto import _LOG_FILE
|
|
150
|
+
except Exception: # noqa: BLE001 — never break a run over a log line
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
res = result or {}
|
|
154
|
+
stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%SZ")
|
|
155
|
+
line = f" • [{stamp}] {describe(name, kind, res, duration_s, detail)}"
|
|
156
|
+
# agentd passes the ticket BOTH as `detail` ("task T1") and as `task_id`, so an
|
|
157
|
+
# unconditional suffix renders "… — task T1 · task T1".
|
|
158
|
+
if task_id and f"task {task_id}" not in line:
|
|
159
|
+
line += f" · task {task_id}"
|
|
160
|
+
try:
|
|
161
|
+
where = agent_dir(name)
|
|
162
|
+
except Exception: # noqa: BLE001
|
|
163
|
+
where = None
|
|
164
|
+
if where:
|
|
165
|
+
line += f"\n ↳ transcript: {where}"
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
_LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
169
|
+
with _LOG_FILE.open("a", encoding="utf-8") as fh:
|
|
170
|
+
fh.write(line + "\n")
|
|
171
|
+
return True
|
|
172
|
+
except Exception as exc: # noqa: BLE001
|
|
173
|
+
logger.debug("auto.log mirror failed agent=%s err=%s", name, exc)
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def emit(name: str, kind: str, result: dict | None, duration_s: float = 0.0,
|
|
178
|
+
*, client: "BackendClient | None" = None, detail: str = "",
|
|
179
|
+
task_id: str = "", lines: list[str] | None = None) -> bool:
|
|
180
|
+
"""Emit ONE ``{activity, agentId[, taskId]}`` event for a finished agent run.
|
|
181
|
+
|
|
182
|
+
Returns True when the webhook accepted it, False otherwise (unknown agent,
|
|
183
|
+
no token, backend down) — never raises.
|
|
184
|
+
|
|
185
|
+
``task_id`` attributes a ticket-triggered run to its ticket. A scheduled or
|
|
186
|
+
CLI run has no ticket and omits it.
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
if client is None:
|
|
190
|
+
from sdk.client import BackendClient
|
|
191
|
+
client = BackendClient.from_config()
|
|
192
|
+
agent_id = await client.resolve_agent_id(name)
|
|
193
|
+
if not agent_id:
|
|
194
|
+
# Local-only agent → not tracked. This used to return in silence,
|
|
195
|
+
# which is why "the webhook never fires" had no diagnosable cause:
|
|
196
|
+
# the run succeeded, the ledger recorded it, and the only missing
|
|
197
|
+
# thing was a Postgres row nobody could tie back to a decision.
|
|
198
|
+
# `resolve_agent_id` logs WHY (unreachable listing vs genuinely
|
|
199
|
+
# absent); this says what it cost.
|
|
200
|
+
logger.warning(
|
|
201
|
+
"agent %r has no backend agentId — skipping the agent-activity "
|
|
202
|
+
"webhook, so this run will not appear in agent_activities. It is "
|
|
203
|
+
"still in the local ledger and in agent_outputs/.", name)
|
|
204
|
+
return False
|
|
205
|
+
activity_text = describe(name, kind, result, duration_s, detail)
|
|
206
|
+
block = _lines_block(lines or [])
|
|
207
|
+
if block:
|
|
208
|
+
activity_text += "\n" + block
|
|
209
|
+
return await client.log_agent_activity(activity_text, agent_id, task_id=task_id)
|
|
210
|
+
except Exception as exc: # noqa: BLE001 — activity emit must never break a run
|
|
211
|
+
logger.warning("activity emit failed agent=%s err=%s", name, exc)
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
async def record_run(name: str, kind: str, result: dict | None, duration_s: float = 0.0,
|
|
216
|
+
*, client: "BackendClient | None" = None, detail: str = "",
|
|
217
|
+
task_id: str = "") -> None:
|
|
218
|
+
"""Record a finished run to BOTH sinks: local ledger, then platform feed.
|
|
219
|
+
|
|
220
|
+
The ledger write happens first and independently of the webhook, so a
|
|
221
|
+
backend outage still leaves per-agent utilisation on the VPS (which is what
|
|
222
|
+
``/api/ui/agents/usage`` and the report's "VPS Agent Logs" read).
|
|
223
|
+
"""
|
|
224
|
+
res: dict[str, Any] = result or {}
|
|
225
|
+
try:
|
|
226
|
+
from sdk.agents import usage as usage_mod
|
|
227
|
+
usage_mod.record(
|
|
228
|
+
name,
|
|
229
|
+
kind=kind,
|
|
230
|
+
duration_s=duration_s,
|
|
231
|
+
input_tokens=res.get("input_tokens", 0) or 0,
|
|
232
|
+
output_tokens=res.get("output_tokens", 0) or 0,
|
|
233
|
+
cost_usd=res.get("cost_usd", 0.0) or 0.0,
|
|
234
|
+
ok=bool(res.get("ok")),
|
|
235
|
+
)
|
|
236
|
+
except Exception as exc: # noqa: BLE001 — ledger write must never break a run
|
|
237
|
+
logger.debug("activity ledger write failed agent=%s err=%s", name, exc)
|
|
238
|
+
|
|
239
|
+
mirror_to_auto_log(name, kind, res, duration_s, detail=detail, task_id=task_id)
|
|
240
|
+
|
|
241
|
+
# Whatever this run's log lines recorded travels with the ONE event, so the
|
|
242
|
+
# activity feed shows what the agent did, not just that it finished.
|
|
243
|
+
try:
|
|
244
|
+
from sdk.agents.run_log import _folder_name
|
|
245
|
+
lines = drain(_folder_name(name))
|
|
246
|
+
except Exception: # noqa: BLE001
|
|
247
|
+
lines = []
|
|
248
|
+
|
|
249
|
+
await emit(name, kind, res, duration_s, client=client, detail=detail,
|
|
250
|
+
task_id=task_id, lines=lines)
|
|
@@ -175,7 +175,13 @@ def start_run(agent_name: str, *, scope: str = "custom", engine: str = "",
|
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
def log_line(path: Path | None, text: str) -> None:
|
|
178
|
-
"""Append a raw line to the activity block (best-effort, no-op if path is None).
|
|
178
|
+
"""Append a raw line to the activity block (best-effort, no-op if path is None).
|
|
179
|
+
|
|
180
|
+
The line is also noted for the run's activity event (``activity.note``), so
|
|
181
|
+
what the run did reaches the platform feed and not merely the fact that it
|
|
182
|
+
finished. That is a buffer append with no I/O — a POST per line is what
|
|
183
|
+
saturated the shared Postgres pool and is why the coalescing exists.
|
|
184
|
+
"""
|
|
179
185
|
if not path:
|
|
180
186
|
return
|
|
181
187
|
try:
|
|
@@ -183,6 +189,11 @@ def log_line(path: Path | None, text: str) -> None:
|
|
|
183
189
|
fh.write(f"[{_now()}] {text}\n")
|
|
184
190
|
except Exception as exc: # noqa: BLE001
|
|
185
191
|
logger.debug("run_log.line failed: %s", exc)
|
|
192
|
+
try:
|
|
193
|
+
from sdk.agents import activity
|
|
194
|
+
activity.note(path.parent.name, str(text))
|
|
195
|
+
except Exception as exc: # noqa: BLE001 — a feed note must never break a run
|
|
196
|
+
logger.debug("run_log.note failed: %s", exc)
|
|
186
197
|
|
|
187
198
|
|
|
188
199
|
def log_event(path: Path | None, event: dict) -> None:
|
|
@@ -12,6 +12,14 @@ Command surface:
|
|
|
12
12
|
Store (or replace) the co_<uuid>_<hex> backend token. See
|
|
13
13
|
sdk.config.configure_cli.
|
|
14
14
|
|
|
15
|
+
agentos chat [--product-id ID] [--pv-id ID] [--no-autorun]
|
|
16
|
+
Describe work in plain language; the backend asks the follow-up
|
|
17
|
+
questions until it has a goal, scope, acceptance criteria,
|
|
18
|
+
constraints, owning department and priority, then builds the plan,
|
|
19
|
+
creates the ticket(s) and wakes the auto-loop. The new ticket still
|
|
20
|
+
runs in createdAt order behind anything already queued.
|
|
21
|
+
See sdk.intake.chat_cli.
|
|
22
|
+
|
|
15
23
|
agentos agent new
|
|
16
24
|
Interactive wizard (input() prompts) — name, description, engine
|
|
17
25
|
(default "claude_code"), trigger type (schedule/ticket_label/manual/
|
|
@@ -86,6 +94,15 @@ def main() -> None:
|
|
|
86
94
|
configure_cli(rotate="--rotate" in sys.argv)
|
|
87
95
|
return
|
|
88
96
|
|
|
97
|
+
if noun == "chat":
|
|
98
|
+
from sdk.intake import IntakeError, chat_cli
|
|
99
|
+
try:
|
|
100
|
+
chat_cli(rest)
|
|
101
|
+
except IntakeError as exc:
|
|
102
|
+
print(f"error: {exc}")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
return
|
|
105
|
+
|
|
89
106
|
if noun == "agent":
|
|
90
107
|
_agent_command(rest)
|
|
91
108
|
return
|
|
@@ -111,9 +128,10 @@ def main() -> None:
|
|
|
111
128
|
|
|
112
129
|
|
|
113
130
|
def _print_usage() -> None:
|
|
114
|
-
print("usage: agentos <configure|agent|integration|default-agents|agentd|github> ...")
|
|
131
|
+
print("usage: agentos <configure|chat|agent|integration|default-agents|agentd|github> ...")
|
|
115
132
|
print()
|
|
116
133
|
print(" agentos configure [--rotate]")
|
|
134
|
+
print(" agentos chat [--product-id ID] [--pv-id ID] [--no-autorun] # requirements → ticket")
|
|
117
135
|
print(" agentos agent <list|run NAME|chat NAME MSG|logs NAME|enable NAME|disable NAME|rm NAME>")
|
|
118
136
|
print(" agentos agent <set-key NAME KEY[=VALUE]…|keys NAME|rm-key NAME KEY…> # persistent API keys (custom agents)")
|
|
119
137
|
print(" agentos integration <add PATH|test NAME|list|rm NAME>")
|