devops-bot-sdk 1.6.73__tar.gz → 1.6.76__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.
Files changed (238) hide show
  1. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/SOURCES.txt +2 -1
  4. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/pyproject.toml +1 -1
  5. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/__init__.py +2 -2
  6. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/recorder.py +15 -0
  7. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/cli.py +12 -9
  8. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/local_exec.py +140 -85
  9. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/service.py +121 -6
  10. devops_bot_sdk-1.6.76/tests/test_activity_claude_runs.py +134 -0
  11. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_fallback_cli.py +67 -43
  12. devops_bot_sdk-1.6.76/tests/test_windows_panel_task.py +135 -0
  13. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_windows_task_paths.py +10 -1
  14. devops_bot_sdk-1.6.73/sdk/vendor/aidraftsman-cli-0.1.0.tgz +0 -0
  15. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/README.md +0 -0
  16. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  17. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  18. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/requires.txt +0 -0
  19. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  20. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/__init__.py +0 -0
  21. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/classifier.py +0 -0
  22. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/__init__.py +0 -0
  23. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/common.py +0 -0
  24. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/linux_auditd.py +0 -0
  25. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/macos_eslogger.py +0 -0
  26. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/windows_eventlog.py +0 -0
  27. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/models.py +0 -0
  28. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/os_setup.py +0 -0
  29. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/redact.py +0 -0
  30. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/settings.py +0 -0
  31. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/shipper.py +0 -0
  32. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/spool.py +0 -0
  33. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agentd.py +0 -0
  34. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/__init__.py +0 -0
  35. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/activity.py +0 -0
  36. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/builder.py +0 -0
  37. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/chat.py +0 -0
  38. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/connectors.py +0 -0
  39. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/__init__.py +0 -0
  40. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/claude_code.py +0 -0
  41. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/http.py +0 -0
  42. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/mcp.py +0 -0
  43. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/n8n.py +0 -0
  44. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/shell.py +0 -0
  45. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/integrations.py +0 -0
  46. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/inventory.py +0 -0
  47. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/library/__init__.py +0 -0
  48. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/manifest.py +0 -0
  49. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_studio.py +0 -0
  50. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_template.py +0 -0
  51. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_vars.py +0 -0
  52. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/run_log.py +0 -0
  53. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/schedules.py +0 -0
  54. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/secrets.py +0 -0
  55. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/ticket_run.py +0 -0
  56. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/usage.py +0 -0
  57. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/varstore.py +0 -0
  58. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/browser_profile.py +0 -0
  59. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/client.py +0 -0
  60. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/collectors/__init__.py +0 -0
  61. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/collectors/files.py +0 -0
  62. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/collectors/process.py +0 -0
  63. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/collectors/screenshot.py +0 -0
  64. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/config.py +0 -0
  65. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/crucial.py +0 -0
  66. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/custom_agents.py +0 -0
  67. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/departments.py +0 -0
  68. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/__init__.py +0 -0
  69. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/assets.py +0 -0
  70. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/bootstrap.py +0 -0
  71. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/browser.py +0 -0
  72. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/compare.py +0 -0
  73. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/loop.py +0 -0
  74. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/estimator.py +0 -0
  75. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/exceptions.py +0 -0
  76. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/firewall.py +0 -0
  77. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/formats.py +0 -0
  78. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/git_ops.py +0 -0
  79. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/github_fetch.py +0 -0
  80. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/github_ingest.py +0 -0
  81. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/graph_recon.py +0 -0
  82. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/graphify.py +0 -0
  83. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/hooks/__init__.py +0 -0
  84. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/hooks/crucial_guard.py +0 -0
  85. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/intake.py +0 -0
  86. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ipc/__init__.py +0 -0
  87. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ipc/electron_bridge.py +0 -0
  88. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ipc/handlers.py +0 -0
  89. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/journal.py +0 -0
  90. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/memory_guard.py +0 -0
  91. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/__init__.py +0 -0
  92. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/envelope.py +0 -0
  93. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/requests.py +0 -0
  94. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/responses.py +0 -0
  95. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/snapshots.py +0 -0
  96. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/phase_timer.py +0 -0
  97. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/plan_run.py +0 -0
  98. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/platform_compat.py +0 -0
  99. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/prd_progress.py +0 -0
  100. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/proof_readers.py +0 -0
  101. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/providers.py +0 -0
  102. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/py.typed +0 -0
  103. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/resume.py +0 -0
  104. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/router.py +0 -0
  105. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/run_auto.py +0 -0
  106. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/schedule_tickets.py +0 -0
  107. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/scheduler.py +0 -0
  108. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/self_report.py +0 -0
  109. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/self_report_local.py +0 -0
  110. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/sse.py +0 -0
  111. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/swagger.py +0 -0
  112. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/task_file.py +0 -0
  113. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/task_types.py +0 -0
  114. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/test.py +0 -0
  115. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/test_pipeline.py +0 -0
  116. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ticket_fields.py +0 -0
  117. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/tickets.py +0 -0
  118. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/tiers/__init__.py +0 -0
  119. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/tiers/mini_agent.py +0 -0
  120. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/__init__.py +0 -0
  121. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/api.py +0 -0
  122. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/dashboard.py +0 -0
  123. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/health.py +0 -0
  124. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/ip_allowlist.py +0 -0
  125. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/jobs.py +0 -0
  126. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/models.py +0 -0
  127. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/runner.py +0 -0
  128. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/security.py +0 -0
  129. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/server.py +0 -0
  130. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/session.py +0 -0
  131. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/setup_manager.py +0 -0
  132. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/agents.html +0 -0
  133. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/app.js +0 -0
  134. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/chat.html +0 -0
  135. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/custom-agents.html +0 -0
  136. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/default-agents.html +0 -0
  137. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/index.html +0 -0
  138. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/report.html +0 -0
  139. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/runs.html +0 -0
  140. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/schedules.html +0 -0
  141. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/setup.html +0 -0
  142. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/styles.css +0 -0
  143. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/tickets.html +0 -0
  144. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/swagger.py +0 -0
  145. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/updater.py +0 -0
  146. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/url_access.py +0 -0
  147. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/vps_health.py +0 -0
  148. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/vps_identity.py +0 -0
  149. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/write_hook.py +0 -0
  150. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/setup.cfg +0 -0
  151. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_collectors.py +0 -0
  152. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_core.py +0 -0
  153. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_live_tracking.py +0 -0
  154. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_macos.py +0 -0
  155. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_os_setup.py +0 -0
  156. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_pipeline.py +0 -0
  157. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_activity.py +0 -0
  158. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_activity_tracking.py +0 -0
  159. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_chat_features.py +0 -0
  160. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_crud_cli.py +0 -0
  161. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_listing.py +0 -0
  162. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_prompt_async.py +0 -0
  163. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_prompt_models.py +0 -0
  164. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_run_ticket.py +0 -0
  165. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_schedules.py +0 -0
  166. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_workspace_settings.py +0 -0
  167. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agents_connectors_builder.py +0 -0
  168. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_allowed_tools_config.py +0 -0
  169. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_auto_agent_dispatch.py +0 -0
  170. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_browser_profile.py +0 -0
  171. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_cheap_tier.py +0 -0
  172. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_client_jira_projects.py +0 -0
  173. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_config_token_diagnostics.py +0 -0
  174. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_configure_token_owner.py +0 -0
  175. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_console_encoding.py +0 -0
  176. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_crash_recovery.py +0 -0
  177. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_departments.py +0 -0
  178. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_done_transition.py +0 -0
  179. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_epic_decompose_run.py +0 -0
  180. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_epic_inherited_agent_id.py +0 -0
  181. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_epic_session_isolation.py +0 -0
  182. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_estimator.py +0 -0
  183. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_firewall_open_on_update.py +0 -0
  184. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_formats.py +0 -0
  185. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_git_action.py +0 -0
  186. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_github_fetch.py +0 -0
  187. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_github_ingest.py +0 -0
  188. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_graph_recon.py +0 -0
  189. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_graphify_build.py +0 -0
  190. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_graphify_hook_install.py +0 -0
  191. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_health.py +0 -0
  192. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_intake_job_outcome.py +0 -0
  193. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_intake_plan_review.py +0 -0
  194. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_journal.py +0 -0
  195. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_local_exec_provider.py +0 -0
  196. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_mcp_route_contract.py +0 -0
  197. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_memory_guard.py +0 -0
  198. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_phase_timer.py +0 -0
  199. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_plan_new_command.py +0 -0
  200. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_plan_run_activity.py +0 -0
  201. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_pr_identity.py +0 -0
  202. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_prd_artifact.py +0 -0
  203. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_project_path_resolution.py +0 -0
  204. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_proof_readers.py +0 -0
  205. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_providers.py +0 -0
  206. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_recon_session_isolation.py +0 -0
  207. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_resume.py +0 -0
  208. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_router.py +0 -0
  209. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_run_auto_agent_only.py +0 -0
  210. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_run_outputs.py +0 -0
  211. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_run_verdict_is_honest.py +0 -0
  212. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_schedule_api.py +0 -0
  213. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_schedule_outputs_and_plans.py +0 -0
  214. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_schedule_tickets.py +0 -0
  215. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_scheduler.py +0 -0
  216. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_scheduler_transient_retry.py +0 -0
  217. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_self_report.py +0 -0
  218. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_service_token_warning.py +0 -0
  219. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_stale_panel_restart.py +0 -0
  220. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_swagger_command.py +0 -0
  221. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_task_file_and_plan_run.py +0 -0
  222. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_task_file_format_doc.py +0 -0
  223. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ticket_cli.py +0 -0
  224. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ticket_fields.py +0 -0
  225. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ticket_run_resolve_by_id.py +0 -0
  226. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ticket_speed_fixes.py +0 -0
  227. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_tier_flag_parity.py +0 -0
  228. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ui_gate.py +0 -0
  229. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ui_lifespan.py +0 -0
  230. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_update_timer.py +0 -0
  231. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_updater_extras.py +0 -0
  232. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_updater_pep668.py +0 -0
  233. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_updater_reports_the_truth.py +0 -0
  234. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_url_access.py +0 -0
  235. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_vps_health.py +0 -0
  236. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_vps_only_reporting.py +0 -0
  237. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_windows_loop_identity.py +0 -0
  238. {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_write_hook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.73
3
+ Version: 1.6.76
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.73
3
+ Version: 1.6.76
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -141,7 +141,7 @@ sdk/ui/static/schedules.html
141
141
  sdk/ui/static/setup.html
142
142
  sdk/ui/static/styles.css
143
143
  sdk/ui/static/tickets.html
144
- sdk/vendor/aidraftsman-cli-0.1.0.tgz
144
+ tests/test_activity_claude_runs.py
145
145
  tests/test_activity_collectors.py
146
146
  tests/test_activity_core.py
147
147
  tests/test_activity_live_tracking.py
@@ -230,5 +230,6 @@ tests/test_url_access.py
230
230
  tests/test_vps_health.py
231
231
  tests/test_vps_only_reporting.py
232
232
  tests/test_windows_loop_identity.py
233
+ tests/test_windows_panel_task.py
233
234
  tests/test_windows_task_paths.py
234
235
  tests/test_write_hook.py
@@ -68,7 +68,7 @@ where = ["."]
68
68
  include = ["sdk*"]
69
69
 
70
70
  [tool.setuptools.package-data]
71
- sdk = ["py.typed", "ui/static/*", "vendor/*.tgz"]
71
+ sdk = ["py.typed", "ui/static/*"]
72
72
 
73
73
  [tool.ruff]
74
74
  line-length = 100
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.73
3
+ Version: 1.6.76
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.73" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.76" # 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
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
  import logging
20
20
  import time
21
21
  import uuid
22
+ from contextvars import ContextVar
22
23
  from typing import Any
23
24
 
24
25
  from sdk.activity import settings
@@ -41,6 +42,17 @@ _FILE_TOOLS = {"Write", "Edit", "MultiEdit", "NotebookEdit", "Read"}
41
42
  #: SDK-origin records are exact, not inferred — hence full confidence.
42
43
  _SDK_CONFIDENCE = 100
43
44
 
45
+ #: The agent run open in THIS task's context. ``local_exec.run_claude_local``
46
+ #: records every claude run it starts, unless a caller (agentd) already opened one
47
+ #: around it — this is how the two avoid recording the same run twice. A
48
+ #: ContextVar, not a global: concurrent runs live in separate asyncio tasks.
49
+ _open_run: ContextVar[str | None] = ContextVar("agentos_open_agent_run", default=None)
50
+
51
+
52
+ def active_run_id() -> str | None:
53
+ """The agent run already open in the current context, if any."""
54
+ return _open_run.get()
55
+
44
56
 
45
57
  def record_session(session: ActivitySession) -> bool:
46
58
  """Queue a session (or its update). ``False`` when disabled or on any failure."""
@@ -94,6 +106,7 @@ def agent_run_started(
94
106
  ) -> str:
95
107
  """Record that agentos started an agent run. Returns the session key."""
96
108
  key = agent_session_key(run_id)
109
+ _open_run.set(run_id)
97
110
  record_session(
98
111
  ActivitySession(
99
112
  externalKey=key,
@@ -134,6 +147,8 @@ def agent_run_finished(
134
147
  ) -> None:
135
148
  """Record the end of an agent run and close its session."""
136
149
  key = agent_session_key(run_id)
150
+ if _open_run.get() == run_id:
151
+ _open_run.set(None)
137
152
  now = time.time()
138
153
  record_session(
139
154
  ActivitySession(
@@ -1383,13 +1383,15 @@ def _browser_command(args: list[str]) -> None:
1383
1383
  # ── fallback ─────────────────────────────────────────────────────────────
1384
1384
 
1385
1385
  def _fallback_usage() -> None:
1386
- print("usage: agentos fallback install # npm-install the bundled CLI now")
1386
+ print("usage: agentos fallback install # where `ad` is, or how to install it")
1387
1387
  print(" agentos fallback login [--browser | --api-key [KEY]] [--base-url URL] [--no-browser]")
1388
1388
  print(" agentos fallback status [--verify] # --verify asks the server too")
1389
1389
  print(" agentos fallback logout")
1390
1390
  print()
1391
- print(" When the `claude` CLI is not working, tickets re-run on the AI Draftsman CLI")
1392
- print(" bundled with the SDK. It needs its own login, made as the user the SDK runs as.")
1391
+ print(" When the `claude` CLI is not available (not installed or logged out), tickets")
1392
+ print(" run on the AI Draftsman CLI (`ad`) installed on this machine. A claude usage")
1393
+ print(" limit is NOT a fallback: that run pauses and resumes on claude.")
1394
+ print(" `ad` needs its own login, made as the user the SDK runs as.")
1393
1395
  print()
1394
1396
  print(" --browser prints a sign-in code and a link; approve it on the AI Draftsman")
1395
1397
  print(" frontend from any machine — works on a headless VPS")
@@ -1409,14 +1411,15 @@ def _fallback_command(args: list[str]) -> None:
1409
1411
  if le._fallback_disabled():
1410
1412
  print("error: the fallback is disabled (AGENTOS_FALLBACK_CLI=off)")
1411
1413
  sys.exit(1)
1412
- cli = asyncio.run(le.ensure_fallback_cli())
1414
+ # The SDK no longer installs the CLI: it is updated continuously, so the
1415
+ # copy on the machine is the one to use. This only reports where it is.
1416
+ cli = le.fallback_cli_path()
1413
1417
  if not cli:
1414
- print("error: could not install the AI Draftsman CLI (Node.js 20+ and npm are needed)")
1418
+ print(f"error: the AI Draftsman CLI (`ad`) was not found. Install it with:\n"
1419
+ f" {le.FALLBACK_INSTALL_HINT}\n"
1420
+ "or point AGENTOS_FALLBACK_CLI at it in ~/.agentos/.env")
1415
1421
  sys.exit(1)
1416
- print(f"installed: {cli}")
1417
- ad = Path(cli).with_name("ad.cmd" if cli.lower().endswith(".cmd") else "ad")
1418
- if ad.exists():
1419
- print(f"also as : {ad} (the same CLI under its short name)")
1422
+ print(f"found : {cli}")
1420
1423
  print("next : agentos fallback login")
1421
1424
  return
1422
1425
 
@@ -1045,16 +1045,23 @@ async def _run_claude_cli(
1045
1045
 
1046
1046
 
1047
1047
  # ── Fallback: the AI Draftsman CLI ─────────────────────────────────────────
1048
- # When the `claude` CLI is not working — not installed, logged out, out of
1049
- # usage, or dying before it even starts a session — the ticket is re-run on
1050
- # our own AI Draftsman CLI (`aidraftsman`, npm package @aidraftsman/cli) instead
1051
- # of failing or parking. It is the same job against the same project dir: the
1052
- # prompt goes in on stdin, `-p --output-format json` prints one JSON object.
1053
- # The CLI is bundled in sdk/vendor/ and npm-installed into ~/.agentos/aidraftsman
1054
- # the first time it is needed; one already on PATH is used instead. It needs
1055
- # its own login on the box, as the user the SDK runs as: `agentos fallback
1056
- # login` (browser sign-in code or an API key), or AD_API_KEY in the env.
1057
- # `agentos fallback status --verify` checks it against the server.
1048
+ # When the `claude` CLI is not AVAILABLE — not installed, logged out, or dying
1049
+ # before it even starts a session — the ticket runs on our own AI Draftsman CLI
1050
+ # (`ad`, npm package @aidraftsman/cli) instead of failing or parking. It is the
1051
+ # same job against the same project dir: the prompt goes in on stdin,
1052
+ # `-p --output-format json` prints one JSON object.
1053
+ #
1054
+ # A claude USAGE LIMIT is not "unavailable": that run parks and resumes on the
1055
+ # same claude session when the window resets, and is never handed to another
1056
+ # model mid-ticket.
1057
+ #
1058
+ # The CLI is the one already installed on the machine (`ad` on PATH, or an npm
1059
+ # global bin dir the service's PATH may not include). The SDK never installs it:
1060
+ # it is updated continuously, and a copy pinned inside the SDK went stale the
1061
+ # day after it shipped. It needs its own login on the box, as the user the SDK
1062
+ # runs as: `agentos fallback login` (browser sign-in code or an API key), or
1063
+ # AD_API_KEY in the env. `agentos fallback status --verify` checks it against
1064
+ # the server.
1058
1065
  #
1059
1066
  # What it does NOT do: honour the PreToolUse approval guard, resume a Claude
1060
1067
  # session, or stream progress events. So a resumed run never falls back (the
@@ -1064,7 +1071,7 @@ async def _run_claude_cli(
1064
1071
  # AGENTOS_FALLBACK_PERMISSION_MODE=bypass to let it run every command.
1065
1072
  #
1066
1073
  # Settings (process env or ~/.agentos/.env):
1067
- # AGENTOS_FALLBACK_CLI path to the CLI (default: PATH, then bundled), or "off"
1074
+ # AGENTOS_FALLBACK_CLI path to the CLI (default: `ad` on PATH), or "off"
1068
1075
  # AGENTOS_FALLBACK_AGENT agent id/name for `--agent` (default: gateway's choice)
1069
1076
  # AGENTOS_FALLBACK_PERMISSION_MODE default | acceptEdits | plan | bypass
1070
1077
 
@@ -1082,91 +1089,54 @@ def _setting(key: str) -> str:
1082
1089
  return ""
1083
1090
 
1084
1091
 
1085
- #: The CLI ships inside the SDK (sdk/vendor/*.tgz) so every box has it without
1086
- #: a separate install step; it is npm-installed from there on first use.
1087
- _VENDOR_DIR = Path(__file__).parent / "vendor"
1092
+ #: How to get the CLI when it is missing — shown in every "not found" message.
1093
+ FALLBACK_INSTALL_HINT = "npm install -g @aidraftsman/cli"
1088
1094
 
1089
1095
 
1090
1096
  def _fallback_disabled() -> bool:
1091
1097
  return _setting("AGENTOS_FALLBACK_CLI").lower() in ("off", "0", "false", "no", "none")
1092
1098
 
1093
1099
 
1094
- def _bundled_tarball() -> Path | None:
1095
- tarballs = sorted(_VENDOR_DIR.glob("aidraftsman-cli-*.tgz"))
1096
- return tarballs[-1] if tarballs else None
1097
-
1098
-
1099
- def _bundled_bin(tarball: Path) -> Path:
1100
- """Where the bundled tarball's `aidraftsman` lands once installed.
1101
-
1102
- One prefix per tarball name, so an SDK update that ships a new CLI version
1103
- installs it fresh rather than reusing the old one.
1104
- """
1105
- prefix = config._CONFIG_DIR / "aidraftsman" / tarball.name.removesuffix(".tgz")
1106
- name = "aidraftsman.cmd" if sys.platform == "win32" else "aidraftsman"
1107
- return prefix / "node_modules" / ".bin" / name
1100
+ def _npm_global_bins() -> list[Path]:
1101
+ """npm's global bin dirs, for a service whose PATH does not include them."""
1102
+ if sys.platform == "win32":
1103
+ appdata = os.environ.get("APPDATA")
1104
+ return [Path(appdata) / "npm"] if appdata else []
1105
+ home = Path.home()
1106
+ return [home / ".npm-global" / "bin", home / ".local" / "bin",
1107
+ Path("/usr/local/bin"), Path("/usr/bin")]
1108
1108
 
1109
1109
 
1110
1110
  def fallback_cli_path() -> str | None:
1111
- """Absolute path to an installed `aidraftsman` CLI, or None if absent or disabled.
1111
+ """Absolute path to the installed AI Draftsman CLI, or None if absent or disabled.
1112
1112
 
1113
- Resolution order: ``AGENTOS_FALLBACK_CLI``, then ``aidraftsman`` or ``ad``
1114
- on ``PATH``, then the copy installed from the bundled tarball. Never installs anything — see
1115
- :func:`ensure_fallback_cli` for that.
1113
+ Resolution order: ``AGENTOS_FALLBACK_CLI``, then ``ad`` (and its long name
1114
+ ``aidraftsman``) on ``PATH``, then npm's global bin dirs. Never installs
1115
+ anything — the CLI on the machine is the one kept up to date.
1116
1116
  """
1117
1117
  if _fallback_disabled():
1118
1118
  return None
1119
1119
  override = _setting("AGENTOS_FALLBACK_CLI")
1120
1120
  if override and Path(override).exists():
1121
1121
  return override
1122
- # The package installs the same entry point under two names; a box may
1123
- # only have `ad` on PATH.
1124
- for name in ("aidraftsman", "ad"):
1122
+ names = ("ad", "aidraftsman")
1123
+ for name in names:
1125
1124
  found = shutil.which(name)
1126
1125
  if found:
1127
1126
  return found
1128
- tarball = _bundled_tarball()
1129
- if tarball and _bundled_bin(tarball).exists():
1130
- return str(_bundled_bin(tarball))
1127
+ suffixes = (".cmd", ".exe", "") if sys.platform == "win32" else ("",)
1128
+ for d in _npm_global_bins():
1129
+ for name in names:
1130
+ for suffix in suffixes:
1131
+ candidate = d / f"{name}{suffix}"
1132
+ if candidate.is_file():
1133
+ return str(candidate)
1131
1134
  return None
1132
1135
 
1133
1136
 
1134
- def _install_bundled(tarball: Path) -> str | None:
1135
- """``npm install`` the bundled tarball into a private prefix (no sudo, no -g)."""
1136
- npm = shutil.which("npm")
1137
- if not npm:
1138
- print("aidraftsman fallback unavailable: npm not found (Node.js 20+ required)", flush=True)
1139
- return None
1140
- bin_path = _bundled_bin(tarball)
1141
- prefix = bin_path.parents[2]
1142
- prefix.mkdir(parents=True, exist_ok=True)
1143
- cmd = [npm, "install", "--prefix", str(prefix), "--no-audit", "--no-fund",
1144
- "--omit=dev", str(tarball)]
1145
- if sys.platform == "win32" and npm.lower().endswith((".cmd", ".bat")):
1146
- cmd = [os.environ.get("COMSPEC", "cmd.exe"), "/c", *cmd]
1147
- print(f"installing bundled {tarball.name} into {prefix}", flush=True)
1148
- try:
1149
- proc = subprocess.run(cmd, capture_output=True, text=True, errors="replace",
1150
- timeout=600)
1151
- except Exception as exc: # noqa: BLE001 — never raise into a ticket run
1152
- print(f"aidraftsman install failed: {exc}", flush=True)
1153
- return None
1154
- if proc.returncode != 0 or not bin_path.exists():
1155
- print(f"aidraftsman install failed: {(proc.stderr or proc.stdout).strip()[-300:]}",
1156
- flush=True)
1157
- return None
1158
- return str(bin_path)
1159
-
1160
-
1161
1137
  async def ensure_fallback_cli() -> str | None:
1162
- """:func:`fallback_cli_path`, installing the bundled CLI first if needed."""
1163
- found = fallback_cli_path()
1164
- if found or _fallback_disabled():
1165
- return found
1166
- tarball = _bundled_tarball()
1167
- if not tarball:
1168
- return None
1169
- return await asyncio.to_thread(_install_bundled, tarball)
1138
+ """:func:`fallback_cli_path`. Kept for callers; it no longer installs anything."""
1139
+ return fallback_cli_path()
1170
1140
 
1171
1141
 
1172
1142
  def _fallback_argv(cli: str, *args: str) -> list[str]:
@@ -1200,7 +1170,7 @@ def fallback_status(*, verify: bool = False, timeout: float = 20.0) -> dict:
1200
1170
  return out
1201
1171
  cli = fallback_cli_path()
1202
1172
  if not cli:
1203
- out["error"] = "not installed — run `agentos fallback install`"
1173
+ out["error"] = f"`ad` not found — install it with `{FALLBACK_INSTALL_HINT}`"
1204
1174
  return out
1205
1175
  out.update(installed=True, path=cli)
1206
1176
  try:
@@ -1238,8 +1208,8 @@ def fallback_status(*, verify: bool = False, timeout: float = 20.0) -> dict:
1238
1208
  #: Shown when neither CLI can take a ticket; the entry points' "CLI missing" error.
1239
1209
  NO_LOCAL_CLI_MESSAGE = (
1240
1210
  "Neither the Claude Code CLI nor the AI Draftsman fallback is ready on this "
1241
- "machine. Install claude and run `claude setup-token`, or run "
1242
- "`agentos fallback install` then `agentos fallback login`."
1211
+ "machine. Install claude and run `claude setup-token`, or install the AI "
1212
+ f"Draftsman CLI (`{FALLBACK_INSTALL_HINT}`) and run `agentos fallback login`."
1243
1213
  )
1244
1214
 
1245
1215
 
@@ -1274,13 +1244,12 @@ def fallback_login(*, method: str | None = None, api_key: str | None = None,
1274
1244
  a link to approve on the AI Draftsman frontend (works on a headless VPS: open
1275
1245
  the link on any machine), ``method="api-key"`` validates and stores a key
1276
1246
  from Settings → API keys (prompted with hidden input when ``api_key`` is not
1277
- given). ``None`` shows the CLI's own menu. Installs the bundled CLI first.
1247
+ given). ``None`` shows the CLI's own menu.
1278
1248
  """
1279
- cli = asyncio.run(ensure_fallback_cli())
1249
+ cli = fallback_cli_path()
1280
1250
  if not cli:
1281
- print("error: the AI Draftsman CLI is not available "
1282
- "(Node.js 20+ and npm are needed to install the bundled copy, "
1283
- "or it is disabled with AGENTOS_FALLBACK_CLI=off)")
1251
+ print("error: the AI Draftsman CLI (`ad`) was not found — install it with "
1252
+ f"`{FALLBACK_INSTALL_HINT}` (or it is disabled with AGENTOS_FALLBACK_CLI=off)")
1284
1253
  return 1
1285
1254
  args = ["auth", "login"]
1286
1255
  if api_key:
@@ -1317,7 +1286,11 @@ def _claude_not_working(result: dict[str, Any]) -> bool:
1317
1286
  """
1318
1287
  if result.get("ok"):
1319
1288
  return False
1320
- if result.get("auth_expired") or result.get("limit_exceeded"):
1289
+ if result.get("limit_exceeded"):
1290
+ # Out of usage is not "unavailable": the run parks and resumes on the same
1291
+ # claude session when the window resets (local_exec's limit handling).
1292
+ return False
1293
+ if result.get("auth_expired"):
1321
1294
  return True
1322
1295
  if result.get("context_exceeded") or result.get("output_cap_exceeded"):
1323
1296
  return False
@@ -1356,9 +1329,8 @@ async def run_fallback_cli(
1356
1329
  """Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run."""
1357
1330
  cli = await ensure_fallback_cli()
1358
1331
  if not cli:
1359
- return _result(ok=False, error="AI Draftsman CLI not available (needs Node.js 20+ "
1360
- "and npm to install the bundled copy; "
1361
- "`agentos fallback install`).",
1332
+ return _result(ok=False, error="AI Draftsman CLI (`ad`) not found on this "
1333
+ f"machine — install it with `{FALLBACK_INSTALL_HINT}`.",
1362
1334
  tier=tier, provider=provider)
1363
1335
 
1364
1336
  path = Path(project_path).expanduser()
@@ -1462,11 +1434,94 @@ async def run_claude_local(
1462
1434
  settings_env: dict[str, str] | None = None,
1463
1435
  tier: str | None = None,
1464
1436
  provider: str | None = None,
1437
+ activity_label: str | None = None,
1465
1438
  ) -> dict[str, Any]:
1466
1439
  """Run the local `claude` CLI; if the CLI itself is not working, re-run on
1467
1440
  the AI Draftsman CLI. See ``_run_claude_cli`` for the arguments and the
1468
1441
  result shape, and the fallback notes above ``run_fallback_cli``.
1442
+
1443
+ Every run is recorded in the VPS activity trail as an AGENT_RUN session with
1444
+ its tool calls — ticket runs, scheduled runs and panel runs alike, since they
1445
+ all come through here. Before this only agentd recorded its runs, so the
1446
+ trail showed no agent work at all for most VPSes. A caller that already
1447
+ opened a run (agentd) is left to record its own; ``activity_label`` names the
1448
+ run, defaulting to the repository it ran in.
1469
1449
  """
1450
+ from sdk.activity import recorder as _audit
1451
+
1452
+ if _audit.active_run_id() is not None:
1453
+ return await _run_claude_local(
1454
+ prompt, project_path, allowed_tools=allowed_tools, on_event=on_event,
1455
+ timeout=timeout, github_token=github_token,
1456
+ resume_session_id=resume_session_id, approval_dir=approval_dir,
1457
+ idle_timeout=idle_timeout, max_output_tokens=max_output_tokens,
1458
+ extra_env=extra_env, unset_env=unset_env, settings_env=settings_env,
1459
+ tier=tier, provider=provider,
1460
+ )
1461
+
1462
+ import uuid as _uuid
1463
+
1464
+ run_id = str(_uuid.uuid4())
1465
+ label = activity_label or f"claude: {Path(project_path).name or project_path}"
1466
+ _audit.agent_run_started(run_id, agent_name=label[:255])
1467
+
1468
+ async def _audited(event: dict) -> None:
1469
+ if isinstance(event, dict) and event.get("type") == "assistant":
1470
+ content = (event.get("message") or {}).get("content") or []
1471
+ for block in content:
1472
+ if isinstance(block, dict) and block.get("type") == "tool_use":
1473
+ _audit.agent_tool_call(run_id, str(block.get("name") or "tool"),
1474
+ block.get("input"))
1475
+ if on_event is not None:
1476
+ await on_event(event)
1477
+
1478
+ try:
1479
+ result = await _run_claude_local(
1480
+ prompt, project_path, allowed_tools=allowed_tools, on_event=_audited,
1481
+ timeout=timeout, github_token=github_token,
1482
+ resume_session_id=resume_session_id, approval_dir=approval_dir,
1483
+ idle_timeout=idle_timeout, max_output_tokens=max_output_tokens,
1484
+ extra_env={**(extra_env or {}), "AGENTOS_ACTOR": "agent",
1485
+ "AGENTOS_RUN_ID": run_id},
1486
+ unset_env=unset_env, settings_env=settings_env, tier=tier, provider=provider,
1487
+ )
1488
+ except BaseException as exc: # incl. CancelledError: the session must still close
1489
+ _audit.agent_run_finished(run_id, ok=False, agent_name=label[:255],
1490
+ detail=(str(exc) or type(exc).__name__)[:500])
1491
+ raise
1492
+ ok = bool(result.get("ok"))
1493
+ _audit.agent_run_finished(run_id, ok=ok, agent_name=label[:255],
1494
+ detail=None if ok else str(result.get("error") or "")[:500])
1495
+ return result
1496
+
1497
+
1498
+ async def _run_claude_local(
1499
+ prompt: str,
1500
+ project_path: str,
1501
+ *,
1502
+ allowed_tools: list[str] | None,
1503
+ on_event: EventHandler | None,
1504
+ timeout: float,
1505
+ github_token: str | None,
1506
+ resume_session_id: str | None,
1507
+ approval_dir: str | None,
1508
+ idle_timeout: float,
1509
+ max_output_tokens: int | None,
1510
+ extra_env: dict[str, str] | None,
1511
+ unset_env: list[str] | None,
1512
+ settings_env: dict[str, str] | None,
1513
+ tier: str | None,
1514
+ provider: str | None,
1515
+ ) -> dict[str, Any]:
1516
+ """The un-audited body of :func:`run_claude_local`."""
1517
+ if not resume_session_id and not is_available() and fallback_cli_path():
1518
+ # No claude here at all: go straight to `ad` rather than spawn a
1519
+ # process that cannot exist just to learn that it failed.
1520
+ print("claude CLI not installed — running on the AI Draftsman CLI", flush=True)
1521
+ return await run_fallback_cli(
1522
+ prompt, project_path, timeout=timeout, github_token=github_token,
1523
+ extra_env=extra_env, tier=tier, provider=provider,
1524
+ )
1470
1525
  result = await _run_claude_cli(
1471
1526
  prompt, project_path,
1472
1527
  allowed_tools=allowed_tools, on_event=on_event, timeout=timeout,
@@ -410,20 +410,118 @@ def _uninstall_launchd() -> tuple[bool, str]:
410
410
 
411
411
  # ── Windows (Scheduled Task) ────────────────────────────────────────────────
412
412
 
413
+ #: Written when Windows refused the logged-out (S4U) panel task, so ``ensure`` does
414
+ #: not rebuild the task on every update only to be refused again.
415
+ _S4U_REFUSED_MARKER = Path.home() / ".agentos" / "panel_task_s4u_refused"
416
+
417
+
418
+ def _windows_account() -> str:
419
+ """``DOMAIN\\user`` for the task principal — the user whose ~/.agentos holds the token."""
420
+ import getpass
421
+
422
+ user = os.environ.get("USERNAME") or getpass.getuser()
423
+ domain = os.environ.get("USERDOMAIN") or os.environ.get("COMPUTERNAME") or ""
424
+ return f"{domain}\\{user}" if domain else user
425
+
426
+
427
+ def _panel_task_xml(exe: str, args: str, home: str, account: str) -> str:
428
+ """Task Scheduler XML for the panel: runs whether or not anyone is signed in.
429
+
430
+ The old task was ``/SC ONLOGON`` with an interactive token, so the panel — and
431
+ with it the health ping, the activity collector and the shipper — stopped the
432
+ moment the user signed out of RDP. A VPS used all day then reported nothing
433
+ from sign-out until the next sign-in ("VPS not reporting since 8h ago").
434
+
435
+ * ``S4U`` logon: runs without a stored password, signed in or not. It has no
436
+ Windows network credentials, which the panel does not use (HTTPS only).
437
+ * Boot AND logon triggers: up after a reboot with nobody signed in, and started
438
+ again at sign-in if it was down.
439
+ * Restart on failure, no execution time limit (the default is 72 h).
440
+ * ``HighestAvailable``: the collector reads the Security log, which needs an
441
+ elevated token — same as the old ``/RL HIGHEST``.
442
+ """
443
+ from xml.sax.saxutils import escape
444
+
445
+ who = escape(account)
446
+ return (
447
+ '<?xml version="1.0" encoding="UTF-16"?>\n'
448
+ '<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">\n'
449
+ " <RegistrationInfo><Description>AgentOS control panel (health, activity, "
450
+ "ticket runs)</Description></RegistrationInfo>\n"
451
+ " <Triggers>\n"
452
+ " <BootTrigger><Enabled>true</Enabled><Delay>PT1M</Delay></BootTrigger>\n"
453
+ f" <LogonTrigger><Enabled>true</Enabled><UserId>{who}</UserId></LogonTrigger>\n"
454
+ " </Triggers>\n"
455
+ f' <Principals><Principal id="Author"><UserId>{who}</UserId>'
456
+ "<LogonType>S4U</LogonType><RunLevel>HighestAvailable</RunLevel>"
457
+ "</Principal></Principals>\n"
458
+ " <Settings>\n"
459
+ " <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
460
+ " <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\n"
461
+ " <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\n"
462
+ " <StartWhenAvailable>true</StartWhenAvailable>\n"
463
+ " <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>\n"
464
+ " <RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure>\n"
465
+ " </Settings>\n"
466
+ ' <Actions Context="Author"><Exec>'
467
+ f"<Command>{escape(exe)}</Command><Arguments>{escape(args)}</Arguments>"
468
+ f"<WorkingDirectory>{escape(home)}</WorkingDirectory>"
469
+ "</Exec></Actions>\n"
470
+ "</Task>\n"
471
+ )
472
+
473
+
474
+ def _create_task_from_xml(task: str, xml: str) -> tuple[int, str]:
475
+ import tempfile
476
+
477
+ fd, path = tempfile.mkstemp(suffix=".xml")
478
+ try:
479
+ with os.fdopen(fd, "w", encoding="utf-16") as fh: # schtasks /XML wants UTF-16
480
+ fh.write(xml)
481
+ return _run(["schtasks", "/Create", "/TN", task, "/XML", path, "/F"])
482
+ except OSError as exc:
483
+ return 1, str(exc)
484
+ finally:
485
+ try:
486
+ os.unlink(path)
487
+ except OSError:
488
+ pass
489
+
490
+
413
491
  def _install_schtask(host: str, port: int) -> tuple[bool, str]:
414
492
  exe = _exe()
415
493
  refusal = _require_absolute(exe, WIN_TASK)
416
494
  if refusal:
417
495
  return False, refusal
418
- tr = f'"{exe}" --host {host} --port {port} --no-browser'
419
- rc, out = _run(["schtasks", "/Create", "/TN", WIN_TASK, "/TR", tr,
420
- "/SC", "ONLOGON", "/RL", "HIGHEST", "/F"])
496
+ args = f"--host {host} --port {port} --no-browser"
497
+ rc, out = _create_task_from_xml(
498
+ WIN_TASK, _panel_task_xml(exe, args, str(Path.home()), _windows_account()))
499
+ if rc == 0:
500
+ try:
501
+ _S4U_REFUSED_MARKER.unlink()
502
+ except OSError:
503
+ pass
504
+ _run(["schtasks", "/Run", "/TN", WIN_TASK])
505
+ return True, (f"installed scheduled task '{WIN_TASK}' — runs whether or not anyone "
506
+ f"is signed in, restarts on failure, and is running now.")
507
+
508
+ # Windows refused it (e.g. a policy denying "Log on as a batch job"). Fall back to
509
+ # the at-logon task: worse — it stops at sign-out — but never worse than before.
510
+ try:
511
+ _S4U_REFUSED_MARKER.parent.mkdir(parents=True, exist_ok=True)
512
+ _S4U_REFUSED_MARKER.write_text(out[-2000:], encoding="utf-8")
513
+ except OSError:
514
+ pass
515
+ tr = f'"{exe}" {args}'
516
+ rc, out2 = _run(["schtasks", "/Create", "/TN", WIN_TASK, "/TR", tr,
517
+ "/SC", "ONLOGON", "/RL", "HIGHEST", "/F"])
421
518
  if rc != 0:
422
- return False, f"schtasks create failed:\n{out}"
519
+ return False, f"schtasks create failed:\n{out}\n{out2}"
423
520
  _run(["schtasks", "/Run", "/TN", WIN_TASK])
424
521
  return True, (f"installed scheduled task '{WIN_TASK}' — starts at logon and is "
425
- f"running now.\n For crash-restart and no-login start, mark the task "
426
- f"'Run whether user is logged on' + 'Restart on failure' in Task Scheduler.")
522
+ f"running now. NOTE: Windows refused the run-while-signed-out task, so "
523
+ f"the panel STOPS when you sign out — disconnect from RDP instead of "
524
+ f"signing out. Reason: {out.strip()[-300:]}")
427
525
 
428
526
 
429
527
  def _update_task_xml(exe: str, home: str) -> str:
@@ -527,6 +625,19 @@ def _schtask_points_nowhere(task: str) -> bool:
527
625
  return cmd is not None and not (Path(cmd).is_absolute() and Path(cmd).is_file())
528
626
 
529
627
 
628
+ def _schtask_runs_signed_out(task: str) -> bool | None:
629
+ """True when ``task`` keeps running after sign-out; ``None`` if it cannot be read."""
630
+ rc, out = _run(["schtasks", "/Query", "/TN", task, "/XML"])
631
+ if rc != 0:
632
+ return None
633
+ return bool(re.search(r"<LogonType>\s*(S4U|Password)\s*</LogonType>", out))
634
+
635
+
636
+ def _panel_task_needs_upgrade() -> bool:
637
+ """An at-logon panel task (pre-1.6.76) that Windows has not already refused to replace."""
638
+ return _schtask_runs_signed_out(WIN_TASK) is False and not _S4U_REFUSED_MARKER.exists()
639
+
640
+
530
641
  def _uninstall_schtask() -> tuple[bool, str]:
531
642
  _run(["schtasks", "/Delete", "/TN", WIN_UPDATE_TASK, "/F"])
532
643
  _run(["schtasks", "/End", "/TN", WIN_TASK])
@@ -855,6 +966,10 @@ def ensure(restart_running: bool = False) -> tuple[bool, str]:
855
966
  rok, rmsg = refresh() # rebuilds the task with an absolute path
856
967
  return rok, (_with_token_warning(f"panel task pointed at a missing program — "
857
968
  f"rebuilt; {rmsg}") if rok else rmsg)
969
+ if IS_WINDOWS and _panel_task_needs_upgrade():
970
+ rok, rmsg = refresh() # at-logon task → runs while signed out
971
+ return rok, (_with_token_warning(f"panel task now runs while signed out; {rmsg}")
972
+ if rok else rmsg)
858
973
  if _is_active():
859
974
  stale = _restart_if_stale()
860
975
  if stale is not None: