agentwire-dev 1.28.0__tar.gz → 1.29.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.
Files changed (333) hide show
  1. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/CHANGELOG.md +1 -0
  2. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/PKG-INFO +1 -1
  3. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/__init__.py +1 -1
  4. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/__main__.py +161 -181
  5. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/bash-tool-damage-control.py +89 -0
  6. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/edit-tool-damage-control.py +89 -0
  7. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/write-tool-damage-control.py +89 -0
  8. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/mcp_server.py +282 -119
  9. agentwire_dev-1.29.0/agentwire/missions/__init__.py +30 -0
  10. agentwire_dev-1.29.0/agentwire/missions/cli.py +521 -0
  11. agentwire_dev-1.29.0/agentwire/missions/config.py +93 -0
  12. agentwire_dev-1.29.0/agentwire/missions/dispatcher.py +325 -0
  13. agentwire_dev-1.29.0/agentwire/missions/eligibility.py +61 -0
  14. agentwire_dev-1.29.0/agentwire/missions/feedback_router.py +248 -0
  15. agentwire_dev-1.29.0/agentwire/missions/gc.py +228 -0
  16. agentwire_dev-1.29.0/agentwire/missions/github.py +252 -0
  17. agentwire_dev-1.29.0/agentwire/missions/naming.py +83 -0
  18. agentwire_dev-1.29.0/agentwire/missions/state.py +136 -0
  19. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/pane_manager.py +53 -31
  20. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/safety/_core.py +89 -0
  21. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/scheduler.py +12 -239
  22. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/server.py +67 -142
  23. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/css/desktop.css +44 -205
  24. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/desktop.js +2 -2
  25. agentwire_dev-1.29.0/agentwire/static/js/sidebar/missions-section.js +144 -0
  26. agentwire_dev-1.29.0/agentwire/static/js/sidebar/scheduler-section.js +179 -0
  27. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/INDEX.md +2 -3
  28. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/architecture.md +17 -21
  29. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/concepts.md +5 -9
  30. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/glossary.md +2 -5
  31. agentwire_dev-1.29.0/docs/wiki/missions.md +263 -0
  32. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/quickstart.md +0 -1
  33. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/scheduling/scheduled-workloads.md +3 -23
  34. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/pyproject.toml +0 -2
  35. agentwire_dev-1.29.0/templates/launchd/dev.agentwire.mission-dispatcher.plist +70 -0
  36. agentwire_dev-1.29.0/templates/launchd/dev.agentwire.mission-feedback-router.plist +49 -0
  37. agentwire_dev-1.29.0/templates/launchd/dev.agentwire.mission-janitor.plist +51 -0
  38. agentwire_dev-1.29.0/tests/integration/test_missions_concurrency.py +150 -0
  39. agentwire_dev-1.29.0/tests/integration/test_missions_lifecycle.py +236 -0
  40. agentwire_dev-1.29.0/tests/unit/test_missions_cli.py +403 -0
  41. agentwire_dev-1.29.0/tests/unit/test_missions_config.py +97 -0
  42. agentwire_dev-1.29.0/tests/unit/test_missions_dispatcher.py +244 -0
  43. agentwire_dev-1.29.0/tests/unit/test_missions_eligibility.py +113 -0
  44. agentwire_dev-1.29.0/tests/unit/test_missions_feedback_router.py +283 -0
  45. agentwire_dev-1.29.0/tests/unit/test_missions_gc.py +276 -0
  46. agentwire_dev-1.29.0/tests/unit/test_missions_github.py +174 -0
  47. agentwire_dev-1.29.0/tests/unit/test_missions_naming.py +97 -0
  48. agentwire_dev-1.29.0/tests/unit/test_missions_state.py +120 -0
  49. agentwire_dev-1.29.0/tests/unit/test_safety_mission_worker.py +233 -0
  50. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_scheduler.py +4 -126
  51. agentwire_dev-1.28.0/agentwire/static/js/sidebar/scheduler-section.js +0 -101
  52. agentwire_dev-1.28.0/agentwire/static/js/sidebar/workflows-section.js +0 -114
  53. agentwire_dev-1.28.0/agentwire/static/js/windows/workflow-window.js +0 -166
  54. agentwire_dev-1.28.0/agentwire/workflows/__init__.py +0 -27
  55. agentwire_dev-1.28.0/agentwire/workflows/cli.py +0 -378
  56. agentwire_dev-1.28.0/agentwire/workflows/context.py +0 -52
  57. agentwire_dev-1.28.0/agentwire/workflows/definitions.py +0 -370
  58. agentwire_dev-1.28.0/agentwire/workflows/node.py +0 -137
  59. agentwire_dev-1.28.0/agentwire/workflows/outputs.py +0 -149
  60. agentwire_dev-1.28.0/agentwire/workflows/pi_runner.py +0 -245
  61. agentwire_dev-1.28.0/agentwire/workflows/runner.py +0 -378
  62. agentwire_dev-1.28.0/agentwire/workflows/runners/__init__.py +0 -56
  63. agentwire_dev-1.28.0/agentwire/workflows/runners/pi.py +0 -35
  64. agentwire_dev-1.28.0/agentwire/workflows/storage.py +0 -198
  65. agentwire_dev-1.28.0/docs/wiki/scheduling/workflows.md +0 -512
  66. agentwire_dev-1.28.0/tests/unit/test_pi_runner.py +0 -427
  67. agentwire_dev-1.28.0/tests/unit/test_runner_on_event.py +0 -86
  68. agentwire_dev-1.28.0/tests/unit/test_runners_registry.py +0 -108
  69. agentwire_dev-1.28.0/tests/unit/test_scheduler_workflow_dispatch.py +0 -213
  70. agentwire_dev-1.28.0/tests/unit/test_server_workflow_history.py +0 -173
  71. agentwire_dev-1.28.0/tests/unit/test_workflow_cli.py +0 -172
  72. agentwire_dev-1.28.0/tests/unit/test_workflow_storage.py +0 -128
  73. agentwire_dev-1.28.0/tests/unit/test_workflows.py +0 -487
  74. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/.github/FUNDING.yml +0 -0
  75. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  76. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  77. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  78. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  79. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/.gitignore +0 -0
  80. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/CLA.md +0 -0
  81. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/CODE_OF_CONDUCT.md +0 -0
  82. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/CONTRIBUTING.md +0 -0
  83. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/Dockerfile.local +0 -0
  84. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/Dockerfile.runpod +0 -0
  85. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/LICENSE +0 -0
  86. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/README.md +0 -0
  87. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/RELEASING.md +0 -0
  88. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/SECURITY.md +0 -0
  89. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/SPONSORS.md +0 -0
  90. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/agents/__init__.py +0 -0
  91. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/agents/base.py +0 -0
  92. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/agents/tmux.py +0 -0
  93. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/cached_status.py +0 -0
  94. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/channels/__init__.py +0 -0
  95. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/channels/base.py +0 -0
  96. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/channels/email.py +0 -0
  97. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/channels/quo.py +0 -0
  98. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/cli_safety.py +0 -0
  99. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/completion.py +0 -0
  100. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/config.py +0 -0
  101. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/fetch.py +0 -0
  102. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/handoff/__init__.py +0 -0
  103. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/handoff/git_state.py +0 -0
  104. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/handoff/instructions.py +0 -0
  105. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/handoff/parser.py +0 -0
  106. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/handoff/renderer.py +0 -0
  107. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/handoff/schema.py +0 -0
  108. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/history.py +0 -0
  109. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/__init__.py +0 -0
  110. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/agentwire-permission.sh +0 -0
  111. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/__init__.py +0 -0
  112. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/audit_logger.py +0 -0
  113. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/agentwire.yaml +0 -0
  114. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/aws.yaml +0 -0
  115. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/cloud-hosting.yaml +0 -0
  116. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/containers.yaml +0 -0
  117. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/core.yaml +0 -0
  118. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/databases.yaml +0 -0
  119. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/firebase.yaml +0 -0
  120. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/gcp.yaml +0 -0
  121. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/git.yaml +0 -0
  122. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/gws.yaml +0 -0
  123. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/infrastructure.yaml +0 -0
  124. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/damage-control/rules/remote.yaml +0 -0
  125. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/hooks/idle-handler.sh +0 -0
  126. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/listen.py +0 -0
  127. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/locking.py +0 -0
  128. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/network.py +0 -0
  129. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/onboarding.py +0 -0
  130. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/overnight.py +0 -0
  131. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/project_config.py +0 -0
  132. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/projects.py +0 -0
  133. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/prompts/__init__.py +0 -0
  134. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/prompts/init.md +0 -0
  135. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/__init__.py +0 -0
  136. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/agentwire.md +0 -0
  137. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/chatbot.md +0 -0
  138. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/init.md +0 -0
  139. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/notifications.md +0 -0
  140. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/orchestrator.md +0 -0
  141. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/task-runner.md +0 -0
  142. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/voice.md +0 -0
  143. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/roles/worker.md +0 -0
  144. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/safety/__init__.py +0 -0
  145. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/search.py +0 -0
  146. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-Echo--black.png +0 -0
  147. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-Echo--transparent.png +0 -0
  148. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-Echo.png +0 -0
  149. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-email-banner.png +0 -0
  150. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--agentwire-text.png +0 -0
  151. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--echo-claw-fg.png +0 -0
  152. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--echo.png +0 -0
  153. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--full--transparent-top.png +0 -0
  154. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--full-black.png +0 -0
  155. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--telephone-fg.png +0 -0
  156. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--telephone.png +0 -0
  157. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--transparent-top.png +0 -0
  158. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--transparent.png +0 -0
  159. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers--tree.png +0 -0
  160. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/agentwire-splash-logo-layers.png +0 -0
  161. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/favicon.png +0 -0
  162. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/android.jpeg +0 -0
  163. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/automaton.jpeg +0 -0
  164. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/bot.jpeg +0 -0
  165. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/cyborg.jpeg +0 -0
  166. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/droid.jpeg +0 -0
  167. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/drone.jpeg +0 -0
  168. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/guardian.jpeg +0 -0
  169. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/mech.jpeg +0 -0
  170. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/probe.jpeg +0 -0
  171. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/robot.jpeg +0 -0
  172. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/sentinel.jpeg +0 -0
  173. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/machines/unit.jpeg +0 -0
  174. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/blob.jpeg +0 -0
  175. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/cloud.jpeg +0 -0
  176. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/crystal.jpeg +0 -0
  177. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/cyclops.jpeg +0 -0
  178. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/flame.jpeg +0 -0
  179. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/fuzzy.jpeg +0 -0
  180. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/horned.jpeg +0 -0
  181. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/moon.jpeg +0 -0
  182. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/slime.jpeg +0 -0
  183. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/star.jpeg +0 -0
  184. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/tentacle.jpeg +0 -0
  185. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/projects/winged.jpeg +0 -0
  186. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/bear.jpeg +0 -0
  187. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/cat.jpeg +0 -0
  188. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/crown.jpeg +0 -0
  189. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/custom/agentwire-portal.png +0 -0
  190. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/custom/agentwire-tts.png +0 -0
  191. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/custom/agentwire.png +0 -0
  192. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/deer.jpeg +0 -0
  193. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/drone.jpeg +0 -0
  194. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/eagle.jpeg +0 -0
  195. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/fox.jpeg +0 -0
  196. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/hawk.jpeg +0 -0
  197. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/horse.jpeg +0 -0
  198. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/lion.jpeg +0 -0
  199. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/rabbit.jpeg +0 -0
  200. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/robot.jpeg +0 -0
  201. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/tiger.jpeg +0 -0
  202. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/icons/sessions/wolf.jpeg +0 -0
  203. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/.gitkeep +0 -0
  204. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/artifact-window.js +0 -0
  205. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/components/icon-picker.js +0 -0
  206. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/components/list-card.js +0 -0
  207. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/components/type-tag.js +0 -0
  208. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/desktop-manager.js +0 -0
  209. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/icon-manager.js +0 -0
  210. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/new-project-modal.js +0 -0
  211. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/notifications-panel.js +0 -0
  212. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/quicktask-modal.js +0 -0
  213. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/safety-shared.js +0 -0
  214. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/safety-window.js +0 -0
  215. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/session-id.js +0 -0
  216. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/session-window.js +0 -0
  217. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/sidebar/artifacts-section.js +0 -0
  218. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/sidebar/config-section.js +0 -0
  219. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/sidebar/machines-section.js +0 -0
  220. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/sidebar/projects-section.js +0 -0
  221. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/sidebar/safety-section.js +0 -0
  222. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/sidebar/services-section.js +0 -0
  223. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/sidebar/sessions-section.js +0 -0
  224. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/sidebar.js +0 -0
  225. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/terminal-font-prefs.js +0 -0
  226. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/tile-manager.js +0 -0
  227. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/utils/auto-refresh.js +0 -0
  228. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/winbox.bundle.min.js +0 -0
  229. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/static/js/windows/chat-window.js +0 -0
  230. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/stt/__init__.py +0 -0
  231. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/stt/base.py +0 -0
  232. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/stt/server_backend.py +0 -0
  233. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/stt/stt_server.py +0 -0
  234. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/stt/whisperkit.py +0 -0
  235. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tasks.py +0 -0
  236. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/templates/__init__.py +0 -0
  237. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/templates/base.html +0 -0
  238. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/templates/desktop.html +0 -0
  239. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/templates/email_notification.html +0 -0
  240. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/templates/handoff/show-the-story.html.j2 +0 -0
  241. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/templates/handoff/theme.css.j2 +0 -0
  242. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/templates/tmux.conf +0 -0
  243. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/templating.py +0 -0
  244. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/aws.yaml +0 -0
  245. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/docker.yaml +0 -0
  246. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/gcp.yaml +0 -0
  247. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/gh.yaml +0 -0
  248. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/git.yaml +0 -0
  249. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/gws.yaml +0 -0
  250. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/kubectl.yaml +0 -0
  251. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/npm.yaml +0 -0
  252. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/terraform.yaml +0 -0
  253. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tooldefs/uv.yaml +0 -0
  254. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/__init__.py +0 -0
  255. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/base.py +0 -0
  256. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/engines/__init__.py +0 -0
  257. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/engines/chatterbox.py +0 -0
  258. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/engines/kokoro.py +0 -0
  259. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/engines/qwen_base.py +0 -0
  260. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/engines/qwen_custom.py +0 -0
  261. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/engines/qwen_design.py +0 -0
  262. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/engines/zonos.py +0 -0
  263. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/registry.py +0 -0
  264. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts/runpod_handler.py +0 -0
  265. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tts_server.py +0 -0
  266. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/tunnels.py +0 -0
  267. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/utils/__init__.py +0 -0
  268. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/utils/chunker.py +0 -0
  269. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/utils/file_io.py +0 -0
  270. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/utils/paths.py +0 -0
  271. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/utils/subprocess.py +0 -0
  272. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/validation.py +0 -0
  273. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/voiceclone.py +0 -0
  274. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/voices/darren.wav +0 -0
  275. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/voices/default.wav +0 -0
  276. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/voices/jessica.wav +0 -0
  277. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/voices/lisa.wav +0 -0
  278. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/voices/may.wav +0 -0
  279. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/agentwire/worktree.py +0 -0
  280. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/logo.png +0 -0
  281. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/communication/channels.md +0 -0
  282. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/communication/hammerspoon.md +0 -0
  283. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/communication/handoff.md +0 -0
  284. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/deployment/remote-access.md +0 -0
  285. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/deployment/remote-machines.md +0 -0
  286. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/integrations/gws-google-workspace-cli.md +0 -0
  287. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/internals/damage-control.md +0 -0
  288. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/internals/portal.md +0 -0
  289. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/internals/shell-escaping.md +0 -0
  290. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/internals/troubleshooting.md +0 -0
  291. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/sessions/claude-code-auto-mode.md +0 -0
  292. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/sessions/pi.md +0 -0
  293. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/tts/runpod-tts.md +0 -0
  294. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/docs/wiki/tts/tts-self-hosted.md +0 -0
  295. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/requirements-tts.txt +0 -0
  296. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/conftest.py +0 -0
  297. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/e2e/test_portal_ui.py +0 -0
  298. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/fixtures/sample_agentwire.yml +0 -0
  299. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/fixtures/sample_config.yaml +0 -0
  300. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/fixtures/sample_scheduler.yaml +0 -0
  301. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/integration/test_scheduler_board.py +0 -0
  302. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/integration/test_server_websockets.py +0 -0
  303. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_build_agent_command.py +0 -0
  304. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_channels.py +0 -0
  305. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_cli_commands.py +0 -0
  306. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_cli_output.py +0 -0
  307. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_cli_safety.py +0 -0
  308. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_config.py +0 -0
  309. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_damage_control_hooks.py +0 -0
  310. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_damage_control_sync.py +0 -0
  311. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_file_io.py +0 -0
  312. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_handoff_git_state.py +0 -0
  313. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_handoff_instructions.py +0 -0
  314. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_handoff_parser.py +0 -0
  315. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_handoff_renderer.py +0 -0
  316. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_history.py +0 -0
  317. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_locking.py +0 -0
  318. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_mcp_server.py +0 -0
  319. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_mcp_tools_args.py +0 -0
  320. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_overnight_resume_flags.py +0 -0
  321. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_portal_api.py +0 -0
  322. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_project_config.py +0 -0
  323. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_roles.py +0 -0
  324. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_safety_disabled_rules.py +0 -0
  325. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_safety_escape_hatch.py +0 -0
  326. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_safety_kill_switch.py +0 -0
  327. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_scheduler_parsing.py +0 -0
  328. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_search.py +0 -0
  329. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_server_async.py +0 -0
  330. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_server_pure.py +0 -0
  331. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_tasks.py +0 -0
  332. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_templating.py +0 -0
  333. {agentwire_dev-1.28.0 → agentwire_dev-1.29.0}/tests/unit/test_worktree.py +0 -0
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Removed
11
11
 
12
+ - **Workflow engine** — entire `agentwire/workflows/` package (DAG runner, definitions, storage, pi runner, CLI), the `agentwire workflow` CLI subcommand, 5 MCP tools (`workflow_list/validate/run/history/show`), 3 portal endpoints (`/api/workflows/list|runs|runs/{id}`), the Workflows sidebar section + WorkflowWindow + ~230 lines of CSS, the scheduler's `workflow:` dispatch branch (`SchedulerTask.workflow`/`inputs` fields, `_dispatch_workflow_task`, `_render_workflow_inputs`, `_parse_workflow_summary`, `_workflow_failure_state`, validation block), 9 workflow test files plus the workflow-specific cases in `test_scheduler.py`, the `agentwire-workflows` skill, `docs/wiki/scheduling/workflows.md`, and ~120 dirs of historical runs under `~/.agentwire/workflows/`. The user's actual recurring automations all live as scheduler ensure tasks (`task:`); the engine was unused in practice and the empty sidebar surface was just noise. Scheduler is now ensure-only.
12
13
  - **Inbound channel surface** — entire Telegram bridge (`agentwire/bridges/telegram.py`), Discord channel (`agentwire/channels/discord.py`), Slack channel (`agentwire/channels/slack.py`), Twilio SMS (`agentwire/channels/sms.py`), Webhook (`agentwire/channels/webhook.py`), outbound-Telegram (`agentwire/channels/telegram.py`), channel scaffolding template (`agentwire/channels/_template.py`), all `agentwire {telegram,discord,slack,sms,webhook}` CLI command groups, the `agentwire reply` command, MCP tools `discord_status` / `slack_status` / `sms_send` / `webhook_send`, the portal sidebar "socials" section, the `ServiceChannel` / `MessageQueueManager` / `QueuedMessage` / `compose_session_config` / `inject_instructions` / session-helper plumbing in `channels/base.py`, the TTS/STT primitives on the channel base class, the auto-injected `discord-dm` / `slack-dm` / `channel-admin` roles, the `OutputConfig.notify` task field plus its `_handle_task_notification` dispatcher (voice / alert / webhook / email / command), the doctor's Telegram bridge check, and the `aiogram` optional dep. Wire surface is now outbound-only: **email** (Resend) and **quo** (OpenPhone SMS) remain. Inbound user input flows through the portal. Security review traced the cut: Telegram bridge was secure (refuse-to-start on empty allowlist + silent default-deny), Discord/Slack fail-open on empty allowlist + unauthenticated channel `@mention`; rather than fix the gaps, the whole inbound surface comes out to keep the wire posture outbound-only.
13
14
  - **Anthropic SDK surface** — entire `agentwire/sdk/` package, `agentwire/repl/` (Textual REPL), `agentwire/workflows/runners/{anthropic,human_gate}.py`, `sdk-bypass` / `sdk-prompted` / `sdk-restricted` session types, `sdk-watch` portal window + sidebar section, `--runner` workflow CLI override, and the `claude-agent-sdk` dependency. Anthropic moved Agent SDK + `claude -p` usage out of the monthly subscription tier on 2026-05-13 (now API-billed); the in-tree consumers were too expensive to keep running. The `pi` workflow runner is now the only runner. For Anthropic-quality scheduled work, use a `claude-bypass` tmux session via `.agentwire.yml` + scheduler. Mission [#184](https://github.com/dotdevdotdev/agentwire-dev/issues/184). Findings captured in `~/.agentwire/wiki/` (anthropic-agent-sdk, anthropic-runner-findings, textual-repl-retrospective, workflow-runner-choice, claude-code-print-mode).
14
15
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentwire-dev
3
- Version: 1.28.0
3
+ Version: 1.29.0
4
4
  Summary: Multi-session voice web interface for AI coding agents
5
5
  Project-URL: Homepage, https://agentwire.dev
6
6
  Project-URL: Repository, https://github.com/dotdevdotdev/agentwire-dev
@@ -1,3 +1,3 @@
1
1
  """AgentWire - Multi-session voice web interface for AI coding agents."""
2
2
 
3
- __version__ = "1.28.0"
3
+ __version__ = "1.29.0"
@@ -2,7 +2,6 @@
2
2
 
3
3
  import argparse
4
4
  import base64
5
- from dataclasses import dataclass, field
6
5
  import datetime
7
6
  import importlib.resources
8
7
  import json
@@ -17,6 +16,7 @@ import tempfile
17
16
  import time
18
17
  import urllib.error
19
18
  import urllib.request
19
+ from dataclasses import dataclass, field
20
20
  from pathlib import Path
21
21
 
22
22
  from dotenv import load_dotenv
@@ -101,6 +101,17 @@ def _build_tmux_env_flags_shell(env: dict[str, str]) -> str:
101
101
  return " ".join(parts) + " "
102
102
 
103
103
 
104
+ def _set_session_name_env(agent: "AgentCommand", session_name: str) -> None:
105
+ """Stamp ``AGENTWIRE_SESSION_NAME`` onto an ``AgentCommand.env``.
106
+
107
+ Every session created via ``cmd_new`` / ``cmd_spawn`` / ``cmd_recreate``
108
+ / ``cmd_fork`` / scheduler-spawn paths gets this so downstream tooling
109
+ (notably the mission-worker damage-control rules in ``safety/_core.py``)
110
+ can identify which agentwire session the running tool is part of.
111
+ """
112
+ agent.env["AGENTWIRE_SESSION_NAME"] = session_name
113
+
114
+
104
115
  def inject_session_env(session: str, env: dict[str, str], remote_host: str | None = None) -> None:
105
116
  """Set env vars on an existing tmux session for FUTURE shells in that session.
106
117
 
@@ -1386,9 +1397,10 @@ def cmd_tts_restart(args) -> int:
1386
1397
  Used by CLI when venv_mismatch occurs during TTS request.
1387
1398
  Supports both local and remote TTS servers.
1388
1399
  """
1389
- from .network import NetworkContext
1390
1400
  import time
1391
1401
 
1402
+ from .network import NetworkContext
1403
+
1392
1404
  ctx = NetworkContext.from_config()
1393
1405
  session_name = get_tts_session_name()
1394
1406
 
@@ -2907,40 +2919,14 @@ def cmd_send(args) -> int:
2907
2919
  print(f"Session '{session}' not found", file=sys.stderr)
2908
2920
  return 1
2909
2921
 
2910
- # Send the prompt use load-buffer for anything non-trivial to avoid PTY flooding
2911
- use_buffer = len(prompt) > 10 or "\n" in prompt
2912
-
2913
- if use_buffer:
2914
- with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
2915
- f.write(prompt)
2916
- temp_path = f.name
2917
- try:
2918
- subprocess.run(["tmux", "load-buffer", temp_path], check=True)
2919
- subprocess.run(["tmux", "paste-buffer", "-t", session], check=True)
2920
- finally:
2921
- os.unlink(temp_path)
2922
- else:
2923
- subprocess.run(
2924
- ["tmux", "send-keys", "-t", session, "-l", prompt],
2925
- check=True
2926
- )
2927
-
2928
- # Wait for text to be displayed before pressing Enter
2929
- time.sleep(0.5)
2930
-
2931
- subprocess.run(
2932
- ["tmux", "send-keys", "-t", session, "Enter"],
2933
- check=True
2934
- )
2935
-
2936
- # For multi-line text, Claude Code shows "[Pasted text...]" and waits for Enter
2937
- # Send another Enter after a short delay to confirm the paste
2938
- if "\n" in prompt or len(prompt) > 200:
2939
- time.sleep(0.5)
2940
- subprocess.run(
2941
- ["tmux", "send-keys", "-t", session, "Enter"],
2942
- check=True
2943
- )
2922
+ # Delegate paste + Enter handling to the shared pane_manager helper so
2923
+ # send-to-session and send-to-pane stay in lockstep. The helper handles
2924
+ # buffer-vs-send-keys, settle delay, and the bracketed-paste double-Enter
2925
+ # in one place.
2926
+ try:
2927
+ pane_manager.send_to_target(session, prompt, enter=True)
2928
+ except RuntimeError as e:
2929
+ return _output_result(False, json_mode, str(e))
2944
2930
 
2945
2931
  if json_mode:
2946
2932
  print(json.dumps({"success": True, "session": session_full, "machine": None, "message": "Prompt sent"}))
@@ -3372,6 +3358,40 @@ def cmd_new(args) -> int:
3372
3358
  # Simple session: ~/projects/project/
3373
3359
  session_path = projects_dir / project
3374
3360
 
3361
+ # Safety: refuse to attach a new session to a path that is already the working
3362
+ # directory of another active session. Two agents sharing the same working tree
3363
+ # is the dangerous footgun (one's dirty state visible to the other, branches
3364
+ # mixing). Worktree sessions (project/branch) get unique paths and won't trip
3365
+ # this. --force overrides.
3366
+ if not args.force:
3367
+ target = str(session_path.resolve()) if session_path.exists() else str(session_path)
3368
+ panes_result = subprocess.run(
3369
+ ["tmux", "list-panes", "-a", "-F", "#{session_name}\t#{pane_current_path}"],
3370
+ capture_output=True, text=True,
3371
+ )
3372
+ if panes_result.returncode == 0:
3373
+ conflicting: set[str] = set()
3374
+ for line in panes_result.stdout.splitlines():
3375
+ if "\t" not in line:
3376
+ continue
3377
+ sess, p = line.split("\t", 1)
3378
+ if sess == session_name:
3379
+ continue
3380
+ try:
3381
+ if str(Path(p).resolve()) == target:
3382
+ conflicting.add(sess)
3383
+ except (OSError, RuntimeError):
3384
+ continue
3385
+ if conflicting:
3386
+ others = ", ".join(sorted(conflicting))
3387
+ hint = (
3388
+ f"Refusing to attach session '{session_name}' to {session_path}: "
3389
+ f"already the working directory of active session(s): {others}. "
3390
+ "Use a 'project/branch' name to create an isolated worktree, "
3391
+ "pick a different path, or pass --force to override."
3392
+ )
3393
+ return _output_result(False, json_mode, hint)
3394
+
3375
3395
  if not session_path.exists():
3376
3396
  if args.force or path:
3377
3397
  # Auto-create directory with -f flag or when custom path explicitly provided
@@ -3431,6 +3451,7 @@ def cmd_new(args) -> int:
3431
3451
  model_override = getattr(args, 'model', None)
3432
3452
  agent = build_agent_command(session_type, roles if roles else None, model=model_override)
3433
3453
  agent.env.update(parse_env_args(getattr(args, 'env', None)))
3454
+ _set_session_name_env(agent, session_name)
3434
3455
 
3435
3456
  agent_cmd = agent.command
3436
3457
 
@@ -6291,9 +6312,10 @@ def cmd_voiceclone_list(args) -> int:
6291
6312
  """List available voices."""
6292
6313
  json_mode = getattr(args, 'json', False)
6293
6314
 
6294
- from .voiceclone import is_runpod_backend, list_voices_runpod, get_tts_url
6295
6315
  import requests
6296
6316
 
6317
+ from .voiceclone import get_tts_url, is_runpod_backend, list_voices_runpod
6318
+
6297
6319
  if is_runpod_backend():
6298
6320
  success, result = list_voices_runpod()
6299
6321
  if success:
@@ -7754,22 +7776,16 @@ def cmd_ensure(args) -> int:
7754
7776
  10. Handle retries on failure
7755
7777
  """
7756
7778
  from .completion import (
7757
- CompletionTimeout,
7758
- generate_summary_filename,
7759
7779
  get_summary_prompt,
7760
- status_to_exit_code,
7761
7780
  )
7762
7781
  from .locking import LockConflict, LockTimeout, session_lock
7763
7782
  from .tasks import (
7764
- PreCommandError,
7765
7783
  TaskNotFound,
7766
7784
  TaskValidationError,
7767
7785
  load_task,
7768
- run_post_command,
7769
- run_pre_command,
7770
7786
  validate_task,
7771
7787
  )
7772
- from .templating import TemplateContext, TemplateError, expand_all, preview_template
7788
+ from .templating import TemplateContext, preview_template
7773
7789
 
7774
7790
  session_name = args.session
7775
7791
  task_name = args.task
@@ -8109,7 +8125,7 @@ def _run_ensure_task(args, session, task, ctx, shell, project_path, json_mode) -
8109
8125
  capture_output=True, text=True,
8110
8126
  )
8111
8127
  if fork_result.returncode != 0 and not json_mode:
8112
- print(f"Warning: context fork failed, starting fresh session")
8128
+ print("Warning: context fork failed, starting fresh session")
8113
8129
  elif not json_mode:
8114
8130
  print(f"Warning: starting_session '{task.starting_session}' not found, starting fresh")
8115
8131
 
@@ -8813,10 +8829,9 @@ def cmd_scheduler_board(args) -> int:
8813
8829
 
8814
8830
  def cmd_scheduler_run(args) -> int:
8815
8831
  """Force-run a specific task now."""
8816
- from .scheduler import _render_workflow_inputs, dispatch_task, load_board, save_board
8832
+ from .scheduler import dispatch_task, load_board, save_board
8817
8833
 
8818
8834
  json_mode = getattr(args, 'json', False)
8819
- dry_run = getattr(args, 'dry_run', False)
8820
8835
  name = args.name
8821
8836
 
8822
8837
  try:
@@ -8830,40 +8845,6 @@ def cmd_scheduler_run(args) -> int:
8830
8845
  f"Task '{name}' not found in board. Available: {', '.join(board.tasks.keys())}",
8831
8846
  )
8832
8847
 
8833
- task = board.tasks[name]
8834
-
8835
- if dry_run:
8836
- if not task.workflow:
8837
- return _output_result(
8838
- False, json_mode,
8839
- f"--dry-run only applies to workflow tasks; '{name}' runs via ensure.",
8840
- )
8841
- from agentwire.workflows.definitions import resolve_workflow
8842
- from agentwire.workflows.runner import run_workflow
8843
- try:
8844
- wf = resolve_workflow(task.workflow)
8845
- except Exception as e:
8846
- return _output_result(False, json_mode, f"Could not load workflow '{task.workflow}': {e}")
8847
- rendered_inputs = _render_workflow_inputs(task.inputs, task)
8848
- run = run_workflow(wf, runs_dir=None, inputs=rendered_inputs, dry_run=True)
8849
- if json_mode:
8850
- _output_json({
8851
- "success": True,
8852
- "task": name,
8853
- "dry_run": True,
8854
- "workflow": wf.name,
8855
- "inputs": rendered_inputs,
8856
- "nodes": [r.node_id for r in run.node_results],
8857
- "status": run.status,
8858
- })
8859
- return 0
8860
- print(f"Dry-run: {name} → workflow '{wf.name}'")
8861
- if rendered_inputs:
8862
- print(f" inputs: {rendered_inputs}")
8863
- for r in run.node_results:
8864
- print(f" - {r.node_id}: {r.final_text}")
8865
- return 0
8866
-
8867
8848
  if not json_mode:
8868
8849
  print(f"Running: {name}")
8869
8850
 
@@ -8946,7 +8927,6 @@ def cmd_scheduler_history(args) -> int:
8946
8927
  "last_status": state.last_status,
8947
8928
  "last_duration": state.last_duration,
8948
8929
  "run_count": state.run_count,
8949
- "workflow": (task.workflow if task else "") or None,
8950
8930
  })
8951
8931
  _output_json({"success": True, "history": history})
8952
8932
  return 0
@@ -8972,8 +8952,8 @@ def cmd_scheduler_history(args) -> int:
8972
8952
 
8973
8953
  def cmd_scheduler_report(args) -> int:
8974
8954
  """Generate a morning report HTML artifact of recent task runs."""
8975
- import re as _re
8976
8955
  from html import escape as html_escape
8956
+
8977
8957
  from .scheduler import _parse_duration, format_interval, load_board, read_events
8978
8958
 
8979
8959
  json_mode = getattr(args, 'json', False)
@@ -9045,38 +9025,11 @@ def cmd_scheduler_report(args) -> int:
9045
9025
  color = colors.get(status, "#78909c")
9046
9026
  return f'<span style="background:{color};color:#fff;padding:2px 8px;border-radius:12px;font-size:0.85em">{status}</span>'
9047
9027
 
9048
- def node_badges(nodes: list) -> str:
9049
- if not nodes:
9050
- return ""
9051
- node_colors = {
9052
- "success": "#00c853",
9053
- "failure": "#ff5252",
9054
- "timeout": "#ff7043",
9055
- "skipped": "#78909c",
9056
- }
9057
- pieces = []
9058
- for n in nodes:
9059
- nid = html_escape(str(n.get("id", "?")))
9060
- nstatus = str(n.get("status", "?"))
9061
- color = node_colors.get(nstatus, "#556")
9062
- pieces.append(
9063
- f'<span style="background:{color};color:#fff;padding:1px 6px;border-radius:8px;font-size:0.75em;margin-right:3px">{nid}</span>'
9064
- )
9065
- return "".join(pieces)
9066
-
9067
9028
  rows_html = ""
9068
9029
  for r in runs:
9069
9030
  duration_str = format_interval(r["duration"]) if r["duration"] else "-"
9070
9031
  pr_link = f'<a href="{r["pr_url"]}" target="_blank" style="color:#00d4ff">{r["pr_url"][:40]}...</a>' if r.get("pr_url") else "-"
9071
- branch_col = ""
9072
- if r.get("workflow"):
9073
- wf_label = html_escape(r["workflow"])
9074
- run_id = html_escape(r.get("run_id", ""))
9075
- badges = node_badges(r["nodes"])
9076
- run_hint = f' <span style="color:#556;font-size:0.75em">({run_id[:20]}…)</span>' if run_id else ""
9077
- branch_col = f'<code style="font-size:0.85em">workflow:{wf_label}</code>{run_hint}<div style="margin-top:4px">{badges}</div>'
9078
- else:
9079
- branch_col = f'<code style="font-size:0.85em">{r.get("work_branch") or "-"}</code>'
9032
+ branch_col = f'<code style="font-size:0.85em">{r.get("work_branch") or "-"}</code>'
9080
9033
  summary_text = r["summary"][:120] if r["summary"] else "-"
9081
9034
  rows_html += f"""
9082
9035
  <tr>
@@ -9536,7 +9489,7 @@ def cmd_overnight_prepare(args) -> int:
9536
9489
  "status": item.status,
9537
9490
  })
9538
9491
  else:
9539
- print(f"Queued for overnight execution:")
9492
+ print("Queued for overnight execution:")
9540
9493
  print(f" ID: {item.id}")
9541
9494
  print(f" Session: {item.session}")
9542
9495
  print(f" Branch: {item.work_branch}")
@@ -9548,7 +9501,7 @@ def cmd_overnight_prepare(args) -> int:
9548
9501
 
9549
9502
  def cmd_overnight_list(args) -> int:
9550
9503
  """List overnight queue items."""
9551
- from .overnight import load_queue, load_done
9504
+ from .overnight import load_done, load_queue
9552
9505
 
9553
9506
  json_mode = getattr(args, "json", False)
9554
9507
  show_all = getattr(args, "all", False)
@@ -9587,7 +9540,7 @@ def cmd_overnight_list(args) -> int:
9587
9540
 
9588
9541
  def cmd_overnight_status(args) -> int:
9589
9542
  """Show overnight orchestrator state and queue summary."""
9590
- from .overnight import load_queue, read_live_state, in_overnight_window
9543
+ from .overnight import in_overnight_window, load_queue, read_live_state
9591
9544
 
9592
9545
  json_mode = getattr(args, "json", False)
9593
9546
 
@@ -9633,7 +9586,7 @@ def cmd_overnight_status(args) -> int:
9633
9586
 
9634
9587
  def cmd_overnight_cancel(args) -> int:
9635
9588
  """Cancel a queued overnight item."""
9636
- from .overnight import load_item, save_item, delete_item
9589
+ from .overnight import delete_item, load_item
9637
9590
 
9638
9591
  item_id = args.id
9639
9592
  json_mode = getattr(args, "json", False)
@@ -10209,70 +10162,6 @@ def main() -> int:
10209
10162
  )
10210
10163
  dev_parser.set_defaults(func=cmd_dev)
10211
10164
 
10212
- # === workflow command group ===
10213
- from agentwire.workflows.cli import (
10214
- cmd_workflow_history,
10215
- cmd_workflow_list,
10216
- cmd_workflow_run,
10217
- cmd_workflow_show,
10218
- cmd_workflow_validate,
10219
- )
10220
-
10221
- workflow_parser = subparsers.add_parser("workflow", help="Pi workflow engine")
10222
- workflow_subparsers = workflow_parser.add_subparsers(dest="workflow_command")
10223
-
10224
- wf_list = workflow_subparsers.add_parser("list", help="List discoverable workflows")
10225
- wf_list.add_argument("--json", action="store_true", help="Output as JSON")
10226
- wf_list.set_defaults(func=cmd_workflow_list)
10227
-
10228
- wf_validate = workflow_subparsers.add_parser(
10229
- "validate", help="Validate a workflow YAML without running it"
10230
- )
10231
- wf_validate.add_argument("workflow", help="Workflow name or path to YAML")
10232
- wf_validate.set_defaults(func=cmd_workflow_validate)
10233
-
10234
- wf_run = workflow_subparsers.add_parser("run", help="Execute a workflow")
10235
- wf_run.add_argument("workflow", help="Workflow name or path to YAML")
10236
- wf_run.add_argument(
10237
- "--input", action="append", metavar="KEY=VALUE",
10238
- help="Workflow input (repeatable). Overrides --input-file."
10239
- )
10240
- wf_run.add_argument(
10241
- "--input-file", metavar="PATH",
10242
- help="JSON file with inputs (object mapping name → value)"
10243
- )
10244
- wf_run.add_argument("--dry-run", action="store_true", help="Print plan without running")
10245
- wf_run.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
10246
- wf_run.add_argument("--json", action="store_true", help="Output as JSON")
10247
- wf_run.set_defaults(func=cmd_workflow_run)
10248
-
10249
- wf_history = workflow_subparsers.add_parser(
10250
- "history", help="List past workflow runs"
10251
- )
10252
- wf_history.add_argument(
10253
- "--workflow", metavar="NAME", help="Filter by workflow name"
10254
- )
10255
- wf_history.add_argument(
10256
- "--limit", type=int, default=20, help="Max runs to show (default: 20)"
10257
- )
10258
- wf_history.add_argument("--json", action="store_true", help="Output as JSON")
10259
- wf_history.set_defaults(func=cmd_workflow_history)
10260
-
10261
- wf_show = workflow_subparsers.add_parser(
10262
- "show", help="Inspect a past workflow run"
10263
- )
10264
- wf_show.add_argument("run_id", help="Run ID (from `workflow history`)")
10265
- wf_show.add_argument(
10266
- "--events", action="store_true",
10267
- help="Dump raw event JSONL for all nodes",
10268
- )
10269
- wf_show.add_argument(
10270
- "--node", metavar="ID",
10271
- help="Filter events to one node id (implies --events)",
10272
- )
10273
- wf_show.add_argument("--json", action="store_true", help="Output as JSON")
10274
- wf_show.set_defaults(func=cmd_workflow_show)
10275
-
10276
10165
  # === listen command group ===
10277
10166
  listen_parser = subparsers.add_parser("listen", help="Voice input recording")
10278
10167
  listen_parser.add_argument(
@@ -10738,8 +10627,6 @@ def main() -> int:
10738
10627
  sched_run = scheduler_subparsers.add_parser("run", help="Force-run a task now")
10739
10628
  sched_run.add_argument("name", help="Task name from board")
10740
10629
  sched_run.add_argument("--json", action="store_true", help="Output JSON")
10741
- sched_run.add_argument("--dry-run", action="store_true",
10742
- help="For workflow tasks: print the execution plan without running")
10743
10630
  sched_run.set_defaults(func=cmd_scheduler_run)
10744
10631
 
10745
10632
  # scheduler enable <name>
@@ -10782,6 +10669,95 @@ def main() -> int:
10782
10669
  sched_report.add_argument("--json", action="store_true", help="Output JSON")
10783
10670
  sched_report.set_defaults(func=cmd_scheduler_report)
10784
10671
 
10672
+ # === mission command group ===
10673
+ from .missions import cli as mission_cli
10674
+
10675
+ mission_parser = subparsers.add_parser(
10676
+ "mission",
10677
+ help="Manage agent-driven missions (issue→branch→draft PR cycles)",
10678
+ description=(
10679
+ "First-class auto-dispatcher: stateless orchestrators tick the GitHub "
10680
+ "issue board, spawn worker sessions in isolated worktrees, route PR "
10681
+ "feedback back, and gc when the PR closes. See docs/MISSIONS.md."
10682
+ ),
10683
+ )
10684
+ mission_subparsers = mission_parser.add_subparsers(dest="mission_command")
10685
+
10686
+ # mission list
10687
+ m_list = mission_subparsers.add_parser("list", help="List active workers + eligible issues")
10688
+ m_list.add_argument("--json", action="store_true", help="Output JSON")
10689
+ m_list.set_defaults(func=mission_cli.cmd_mission_list)
10690
+
10691
+ # mission show <N>
10692
+ m_show = mission_subparsers.add_parser("show", help="Show one issue + dispatch + PR state")
10693
+ m_show.add_argument("number", type=int, help="GitHub issue number")
10694
+ m_show.add_argument("--repo", required=True, help="Repo short name (from missions config)")
10695
+ m_show.add_argument("--json", action="store_true", help="Output JSON")
10696
+ m_show.set_defaults(func=mission_cli.cmd_mission_show)
10697
+
10698
+ # mission status
10699
+ m_status = mission_subparsers.add_parser("status", help="Per-repo summary of active + eligible")
10700
+ m_status.add_argument("--json", action="store_true", help="Output JSON")
10701
+ m_status.set_defaults(func=mission_cli.cmd_mission_status)
10702
+
10703
+ # mission spawn <N>
10704
+ m_spawn = mission_subparsers.add_parser(
10705
+ "spawn", help="Force-dispatch an issue, bypassing eligibility"
10706
+ )
10707
+ m_spawn.add_argument("number", type=int, help="GitHub issue number")
10708
+ m_spawn.add_argument("--repo", required=True, help="Repo short name")
10709
+ m_spawn.add_argument("--json", action="store_true", help="Output JSON")
10710
+ m_spawn.set_defaults(func=mission_cli.cmd_mission_spawn)
10711
+
10712
+ # mission stall <N>
10713
+ m_stall = mission_subparsers.add_parser("stall", help="Stop dispatch for an issue with a reason")
10714
+ m_stall.add_argument("number", type=int, help="GitHub issue number")
10715
+ m_stall.add_argument("--repo", required=True, help="Repo short name")
10716
+ m_stall.add_argument("--reason", required=True, help="Reason (posted as comment)")
10717
+ m_stall.add_argument("--json", action="store_true", help="Output JSON")
10718
+ m_stall.set_defaults(func=mission_cli.cmd_mission_stall)
10719
+
10720
+ # mission resume <N>
10721
+ m_resume = mission_subparsers.add_parser("resume", help="Re-enable an issue for dispatch")
10722
+ m_resume.add_argument("number", type=int, help="GitHub issue number")
10723
+ m_resume.add_argument("--repo", required=True, help="Repo short name")
10724
+ m_resume.add_argument("--json", action="store_true", help="Output JSON")
10725
+ m_resume.set_defaults(func=mission_cli.cmd_mission_resume)
10726
+
10727
+ # mission kill <N>
10728
+ m_kill = mission_subparsers.add_parser(
10729
+ "kill", help="Kill worker session + worktree (does NOT close the PR)"
10730
+ )
10731
+ m_kill.add_argument("number", type=int, help="GitHub issue number")
10732
+ m_kill.add_argument("--repo", required=True, help="Repo short name")
10733
+ m_kill.add_argument("--json", action="store_true", help="Output JSON")
10734
+ m_kill.set_defaults(func=mission_cli.cmd_mission_kill)
10735
+
10736
+ # mission gc
10737
+ m_gc = mission_subparsers.add_parser("gc", help="Reap sessions whose PR is merged/closed")
10738
+ m_gc.add_argument("--json", action="store_true", help="Output JSON")
10739
+ m_gc.set_defaults(func=mission_cli.cmd_mission_gc)
10740
+
10741
+ # mission tick
10742
+ m_tick = mission_subparsers.add_parser("tick", help="Run one dispatcher tick now")
10743
+ m_tick.add_argument("--json", action="store_true", help="Output JSON")
10744
+ m_tick.set_defaults(func=mission_cli.cmd_mission_tick)
10745
+
10746
+ # mission route-feedback
10747
+ m_route = mission_subparsers.add_parser(
10748
+ "route-feedback", help="Run one PR-feedback router tick now"
10749
+ )
10750
+ m_route.add_argument("--json", action="store_true", help="Output JSON")
10751
+ m_route.set_defaults(func=mission_cli.cmd_mission_route_feedback)
10752
+
10753
+ # mission init <repo>
10754
+ m_init = mission_subparsers.add_parser(
10755
+ "init", help="Create the agent-ready label on a repo (idempotent)"
10756
+ )
10757
+ m_init.add_argument("repo", help="Repo short name (from config) or owner/repo form")
10758
+ m_init.add_argument("--json", action="store_true", help="Output JSON")
10759
+ m_init.set_defaults(func=mission_cli.cmd_mission_init)
10760
+
10785
10761
  # === overnight command group ===
10786
10762
  overnight_parser = subparsers.add_parser(
10787
10763
  "overnight",
@@ -10918,6 +10894,10 @@ def main() -> int:
10918
10894
  scheduler_parser.print_help()
10919
10895
  return 0
10920
10896
 
10897
+ if args.command == "mission" and getattr(args, "mission_command", None) is None:
10898
+ mission_parser.print_help()
10899
+ return 0
10900
+
10921
10901
  if args.command == "overnight" and getattr(args, "overnight_command", None) is None:
10922
10902
  overnight_parser.print_help()
10923
10903
  return 0
@@ -559,6 +559,82 @@ def detect_escape_hatch(command: str) -> Optional[str]:
559
559
  return reason or None
560
560
 
561
561
 
562
+ # ============================================================================
563
+ # MISSION-WORKER RULES
564
+ # ============================================================================
565
+ #
566
+ # When a tmux session is a mission worker (its name is "{repo}/mission-{N}-{slug}",
567
+ # carried through to hooks as the ``AGENTWIRE_SESSION_NAME`` env var), tighten
568
+ # damage control beyond the standard ruleset:
569
+ #
570
+ # 1. Edit/Write must target a path inside a mission worktree
571
+ # (``{...}-worktrees/mission-{N}-{slug}/...``). No writing to the canonical
572
+ # repo, sibling projects, or arbitrary filesystem locations.
573
+ # 2. Bash ``git push --force`` (or ``--force-with-lease``) is blocked unless
574
+ # the target is a mission-* branch and not main/master/develop.
575
+ #
576
+ # Detection uses the session-name regex (set by ``cmd_new`` / spawn lifecycle);
577
+ # enforcement uses a separate path regex to allow simple realpath checks.
578
+
579
+ _MISSION_SESSION_RE = re.compile(r"^[^/]+/mission-\d+-")
580
+ _MISSION_WORKTREE_PATH_RE = re.compile(r"-worktrees/mission-\d+-")
581
+ _PROTECTED_BRANCH_RE = re.compile(r"\b(?:main|master|develop)\b")
582
+ _FORCE_PUSH_RE = re.compile(r"\bgit\s+push\b[^|;&]*--force(?:-with-lease)?\b", re.IGNORECASE)
583
+
584
+
585
+ def _is_mission_worker_session() -> bool:
586
+ """True iff ``AGENTWIRE_SESSION_NAME`` env var matches the mission pattern."""
587
+ return bool(_MISSION_SESSION_RE.match(os.environ.get("AGENTWIRE_SESSION_NAME", "") or ""))
588
+
589
+
590
+ def check_mission_worker_path(file_path: str) -> Tuple[bool, str]:
591
+ """If we're in a mission worker, allow only paths under a mission worktree.
592
+
593
+ Returns ``(blocked, reason)``. ``(False, "")`` when not in a mission session
594
+ or when ``file_path`` resolves inside a ``*-worktrees/mission-N-...`` dir.
595
+
596
+ Realpath is used so symlink escapes from within the worktree still resolve
597
+ to their canonical location for the check.
598
+ """
599
+ if not _is_mission_worker_session():
600
+ return False, ""
601
+ try:
602
+ abs_path = os.path.realpath(os.path.expanduser(file_path))
603
+ except (OSError, ValueError):
604
+ return False, ""
605
+ if _MISSION_WORKTREE_PATH_RE.search(abs_path):
606
+ return False, ""
607
+ return True, (
608
+ "mission worker may only write inside its assigned worktree "
609
+ "({repo}-worktrees/mission-{N}-{slug}/...)"
610
+ )
611
+
612
+
613
+ def check_mission_worker_bash(command: str) -> Tuple[bool, str]:
614
+ """If we're in a mission worker, gate force-pushes by target branch.
615
+
616
+ Rules:
617
+ - Any ``git push --force`` referencing ``main`` / ``master`` / ``develop``
618
+ is blocked outright.
619
+ - A ``git push --force`` targeting a ``mission-*`` branch is allowed
620
+ (the worker's own branch; rebases on top of merge-base happen here).
621
+ - All other ``git push --force`` patterns are blocked — mission workers
622
+ should never overwrite shared history.
623
+
624
+ Returns ``(blocked, reason)``; ``(False, "")`` if not in a mission session
625
+ or the command isn't a force-push.
626
+ """
627
+ if not _is_mission_worker_session():
628
+ return False, ""
629
+ if not _FORCE_PUSH_RE.search(command):
630
+ return False, ""
631
+ if _PROTECTED_BRANCH_RE.search(command):
632
+ return True, "mission worker: --force push to main/master/develop is blocked"
633
+ if re.search(r"\bmission-\d+-", command):
634
+ return False, ""
635
+ return True, "mission worker: --force push allowed only on mission-* branches"
636
+
637
+
562
638
  # ============================================================================
563
639
  # DECISION LADDERS
564
640
  # ============================================================================
@@ -604,6 +680,15 @@ def check_command(command: str, config: Dict[str, Any]) -> Dict[str, Any]:
604
680
  "disabled": True,
605
681
  }
606
682
 
683
+ blocked, reason = check_mission_worker_bash(command)
684
+ if blocked:
685
+ return {
686
+ "decision": "block",
687
+ "reason": reason,
688
+ "pattern": "mission-worker:force-push",
689
+ "command": command,
690
+ }
691
+
607
692
  bash_patterns = config.get("bashToolPatterns", [])
608
693
  zero_access = config.get("zeroAccessPaths", [])
609
694
  read_only = config.get("readOnlyPaths", [])
@@ -706,6 +791,10 @@ def check_path(file_path: str, config: Dict[str, Any]) -> Tuple[bool, str]:
706
791
  if safety_cfg.get("enabled", True) is False:
707
792
  return False, ""
708
793
 
794
+ blocked, reason = check_mission_worker_path(file_path)
795
+ if blocked:
796
+ return True, reason
797
+
709
798
  allowed = load_allowed_paths(config)
710
799
 
711
800
  if is_path_allowed_for_op(file_path, allowed, "edit"):