agentwire-dev 1.51.0__tar.gz → 1.52.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 (456) hide show
  1. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.gitignore +3 -0
  2. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/PKG-INFO +1 -1
  3. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/__init__.py +1 -1
  4. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/agents/tmux.py +3 -3
  5. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/config.py +7 -0
  6. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/inbox.py +23 -2
  7. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/listen.py +67 -26
  8. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/voice.py +14 -1
  9. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/security.py +165 -4
  10. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/send_cli.py +3 -3
  11. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/server.py +36 -19
  12. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/artifact-window.js +21 -24
  13. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/__init__.py +19 -0
  14. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/engines/kokoro.py +29 -6
  15. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voiceclone.py +64 -18
  16. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/communication/hammerspoon.md +1 -1
  17. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_inbox.py +48 -0
  18. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_portal_api.py +138 -0
  19. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_security.py +107 -0
  20. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_stt_backend.py +21 -1
  21. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tts_local.py +31 -3
  22. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.agentwire.yml.example +0 -0
  23. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.claude/commands/handoff.md +0 -0
  24. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.claude/skills/wiki/SKILL.md +0 -0
  25. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/FUNDING.yml +0 -0
  26. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  27. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  28. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  29. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  30. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/workflows/pytest.yml +0 -0
  31. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/workflows/ruff.yml +0 -0
  32. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/workflows/security.yml +0 -0
  33. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/workflows/tts-smoke.yml +0 -0
  34. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/AGENTS.md +0 -0
  35. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/CODE_OF_CONDUCT.md +0 -0
  36. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/CONTRIBUTING.md +0 -0
  37. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/Dockerfile.local +0 -0
  38. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/LICENSE +0 -0
  39. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/README.md +0 -0
  40. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/RELEASING.md +0 -0
  41. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/SECURITY.md +0 -0
  42. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/SPONSORS.md +0 -0
  43. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/__main__.py +0 -0
  44. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/agents/__init__.py +0 -0
  45. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/agents/base.py +0 -0
  46. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/cached_status.py +0 -0
  47. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/__init__.py +0 -0
  48. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/base.py +0 -0
  49. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/email.py +0 -0
  50. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/push.py +0 -0
  51. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/quo.py +0 -0
  52. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels_cli.py +0 -0
  53. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/completion.py +0 -0
  54. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/core.py +0 -0
  55. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/__init__.py +0 -0
  56. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/cli.py +0 -0
  57. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/inbox.py +0 -0
  58. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/state.py +0 -0
  59. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/view.py +0 -0
  60. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/devices.py +0 -0
  61. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/diff_cli.py +0 -0
  62. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/doctor_cli.py +0 -0
  63. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/doctor_voice.py +0 -0
  64. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/ensure_cli.py +0 -0
  65. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/fetch.py +0 -0
  66. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/__init__.py +0 -0
  67. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/git_state.py +0 -0
  68. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/instructions.py +0 -0
  69. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/parser.py +0 -0
  70. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/renderer.py +0 -0
  71. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/schema.py +0 -0
  72. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff_cli.py +0 -0
  73. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/history.py +0 -0
  74. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/history_cli.py +0 -0
  75. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/__init__.py +0 -0
  76. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/agentwire-permission.sh +0 -0
  77. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/__init__.py +0 -0
  78. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/audit_logger.py +0 -0
  79. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/bash-tool-damage-control.py +0 -0
  80. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/edit-tool-damage-control.py +0 -0
  81. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/mcp-tool-damage-control.py +0 -0
  82. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/read-tool-damage-control.py +0 -0
  83. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/agentwire.yaml +0 -0
  84. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/aws.yaml +0 -0
  85. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/cloud-hosting.yaml +0 -0
  86. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/containers.yaml +0 -0
  87. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/control-plane.yaml +0 -0
  88. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/core.yaml +0 -0
  89. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/databases.yaml +0 -0
  90. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/firebase.yaml +0 -0
  91. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/gcp.yaml +0 -0
  92. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/git.yaml +0 -0
  93. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/gws.yaml +0 -0
  94. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/infrastructure.yaml +0 -0
  95. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/outbound.yaml +0 -0
  96. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/publish.yaml +0 -0
  97. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/remote.yaml +0 -0
  98. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/write-tool-damage-control.py +0 -0
  99. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/idle-handler.sh +0 -0
  100. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/queue-processor.sh +0 -0
  101. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks_cli.py +0 -0
  102. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/limits_cli.py +0 -0
  103. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/locking.py +0 -0
  104. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/machine_cli.py +0 -0
  105. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_channels.py +0 -0
  106. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_cli.py +0 -0
  107. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_core.py +0 -0
  108. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_council.py +0 -0
  109. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_desktop.py +0 -0
  110. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_handoff.py +0 -0
  111. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_history.py +0 -0
  112. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_listen.py +0 -0
  113. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_lock.py +0 -0
  114. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_machine.py +0 -0
  115. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_msg.py +0 -0
  116. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_notify.py +0 -0
  117. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_pane.py +0 -0
  118. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_projects.py +0 -0
  119. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_scheduler.py +0 -0
  120. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_scratchpad.py +0 -0
  121. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_server.py +0 -0
  122. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_services.py +0 -0
  123. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_session.py +0 -0
  124. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_status.py +0 -0
  125. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_task.py +0 -0
  126. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_tunnels.py +0 -0
  127. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_voice.py +0 -0
  128. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_voiceclone.py +0 -0
  129. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_wiki.py +0 -0
  130. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_worktree.py +0 -0
  131. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/msg_cli.py +0 -0
  132. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/network.py +0 -0
  133. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/notify_cli.py +0 -0
  134. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/onboarding.py +0 -0
  135. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/pane_cli.py +0 -0
  136. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/pane_manager.py +0 -0
  137. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/portal_cli.py +0 -0
  138. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/project_config.py +0 -0
  139. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/projects.py +0 -0
  140. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/prompt_router.py +0 -0
  141. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/prompts_cli.py +0 -0
  142. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/push_cli.py +0 -0
  143. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/push_store.py +0 -0
  144. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/repo_cli.py +0 -0
  145. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/research.py +0 -0
  146. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/__init__.py +0 -0
  147. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/agentwire.md +0 -0
  148. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/anchor.md +0 -0
  149. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/chatbot.md +0 -0
  150. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/contributor.md +0 -0
  151. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/correspondent.md +0 -0
  152. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-brain.md +0 -0
  153. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-conscience.md +0 -0
  154. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-critic.md +0 -0
  155. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-devils-advocate.md +0 -0
  156. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-gut.md +0 -0
  157. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-historian.md +0 -0
  158. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-member.md +0 -0
  159. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-orchestrator.md +0 -0
  160. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/init.md +0 -0
  161. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/notifications.md +0 -0
  162. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/orchestrator.md +0 -0
  163. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/soul.md +0 -0
  164. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/task-runner.md +0 -0
  165. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/voice.md +0 -0
  166. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/worker.md +0 -0
  167. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/worktree-session.md +0 -0
  168. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles_cli.py +0 -0
  169. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/__init__.py +0 -0
  170. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/artifacts.py +0 -0
  171. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/config.py +0 -0
  172. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/council.py +0 -0
  173. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/desktop.py +0 -0
  174. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/history.py +0 -0
  175. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/machines.py +0 -0
  176. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/notify.py +0 -0
  177. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/permission.py +0 -0
  178. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/projects.py +0 -0
  179. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/push.py +0 -0
  180. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/safety.py +0 -0
  181. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/scheduler.py +0 -0
  182. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/scratchpad.py +0 -0
  183. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/sessions.py +0 -0
  184. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/sessions_admin.py +0 -0
  185. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/safety/__init__.py +0 -0
  186. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/safety/_core.py +0 -0
  187. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/safety_cli.py +0 -0
  188. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/safety_commands.py +0 -0
  189. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/scheduler.py +0 -0
  190. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/scheduler_cli.py +0 -0
  191. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/scratchpad.py +0 -0
  192. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/services.py +0 -0
  193. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/session_cli.py +0 -0
  194. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/session_context.py +0 -0
  195. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/session_ready.py +0 -0
  196. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/ssh.py +0 -0
  197. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--agentwire-text.png +0 -0
  198. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--echo-claw-fg.png +0 -0
  199. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--echo.png +0 -0
  200. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--telephone.png +0 -0
  201. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--tree.png +0 -0
  202. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/announcements.json +0 -0
  203. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/css/desktop.css +0 -0
  204. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/css/mobile.css +0 -0
  205. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/favicon.png +0 -0
  206. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/bear.webp +0 -0
  207. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/cat.webp +0 -0
  208. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/crown.webp +0 -0
  209. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/custom/agentwire-portal.webp +0 -0
  210. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/custom/agentwire-tts.webp +0 -0
  211. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/custom/agentwire.webp +0 -0
  212. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/deer.webp +0 -0
  213. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/drone.webp +0 -0
  214. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/eagle.webp +0 -0
  215. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/fox.webp +0 -0
  216. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/hawk.webp +0 -0
  217. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/horse.webp +0 -0
  218. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/lion.webp +0 -0
  219. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/rabbit.webp +0 -0
  220. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/robot.webp +0 -0
  221. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/tiger.webp +0 -0
  222. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/wolf.webp +0 -0
  223. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/img/icon-192.png +0 -0
  224. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/img/icon-512.png +0 -0
  225. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/.gitkeep +0 -0
  226. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/announcement-modal.js +0 -0
  227. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/api.js +0 -0
  228. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/collage.js +0 -0
  229. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/command-palette.js +0 -0
  230. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/council-window.js +0 -0
  231. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/dead-key-suppressor.js +0 -0
  232. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/desktop-manager.js +0 -0
  233. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/desktop.js +0 -0
  234. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/help-modal.js +0 -0
  235. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/icon-manager.js +0 -0
  236. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/mobile.js +0 -0
  237. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/notification-prefs.js +0 -0
  238. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/notifications-panel.js +0 -0
  239. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/push-client.js +0 -0
  240. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/review-window.js +0 -0
  241. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/safety-shared.js +0 -0
  242. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/safety-window.js +0 -0
  243. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/scratchpad.js +0 -0
  244. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/service-classification.js +0 -0
  245. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/session-id.js +0 -0
  246. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/session-window.js +0 -0
  247. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/shortcuts.js +0 -0
  248. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/artifacts-section.js +0 -0
  249. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/config-section.js +0 -0
  250. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/council-section.js +0 -0
  251. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/machines-section.js +0 -0
  252. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/projects-section.js +0 -0
  253. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/safety-section.js +0 -0
  254. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/scheduler-section.js +0 -0
  255. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/services-section.js +0 -0
  256. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/sessions-section.js +0 -0
  257. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar.js +0 -0
  258. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/terminal-font-prefs.js +0 -0
  259. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/tile-manager.js +0 -0
  260. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/toast.js +0 -0
  261. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/token-modal.js +0 -0
  262. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/utils/ansi.js +0 -0
  263. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/utils/swipe.js +0 -0
  264. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/autosend-prefs.js +0 -0
  265. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/browser-stt.js +0 -0
  266. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/browser-tts.js +0 -0
  267. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/jargon.js +0 -0
  268. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/prompt.js +0 -0
  269. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/winbox.bundle.min.js +0 -0
  270. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/manifest.webmanifest +0 -0
  271. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/service-worker.js +0 -0
  272. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/base.py +0 -0
  273. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/cloud.py +0 -0
  274. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/engine.py +0 -0
  275. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/local.py +0 -0
  276. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/server_backend.py +0 -0
  277. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/stt_server.py +0 -0
  278. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/system_cli.py +0 -0
  279. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tasks.py +0 -0
  280. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/__init__.py +0 -0
  281. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/base.html +0 -0
  282. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/desktop.html +0 -0
  283. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/email_notification.html +0 -0
  284. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/handoff/show-the-story.html.j2 +0 -0
  285. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/handoff/theme.css.j2 +0 -0
  286. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/mobile.html +0 -0
  287. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/pair.html +0 -0
  288. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/tmux.conf +0 -0
  289. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templating.py +0 -0
  290. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/aws.yaml +0 -0
  291. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/docker.yaml +0 -0
  292. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/gcp.yaml +0 -0
  293. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/gh.yaml +0 -0
  294. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/git.yaml +0 -0
  295. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/gws.yaml +0 -0
  296. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/kubectl.yaml +0 -0
  297. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/npm.yaml +0 -0
  298. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/terraform.yaml +0 -0
  299. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/uv.yaml +0 -0
  300. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/__init__.py +0 -0
  301. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/audio.py +0 -0
  302. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/base.py +0 -0
  303. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/engines/__init__.py +0 -0
  304. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/engines/chatterbox.py +0 -0
  305. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/engines/zonos.py +0 -0
  306. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/kokoro_server.py +0 -0
  307. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/local.py +0 -0
  308. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/registry.py +0 -0
  309. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts_cli.py +0 -0
  310. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts_server.py +0 -0
  311. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tunnels.py +0 -0
  312. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tunnels_cli.py +0 -0
  313. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/usage_limit.py +0 -0
  314. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/__init__.py +0 -0
  315. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/chunker.py +0 -0
  316. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/event_log.py +0 -0
  317. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/file_io.py +0 -0
  318. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/paths.py +0 -0
  319. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/speech.py +0 -0
  320. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/subprocess.py +0 -0
  321. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/validation.py +0 -0
  322. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voice_status.py +0 -0
  323. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/darren.wav +0 -0
  324. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/default.wav +0 -0
  325. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/jessica.wav +0 -0
  326. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/lisa.wav +0 -0
  327. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/may.wav +0 -0
  328. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/wiki.py +0 -0
  329. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/wiki_audit.py +0 -0
  330. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/wiki_cli.py +0 -0
  331. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/worktree.py +0 -0
  332. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/worktree_registry.py +0 -0
  333. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/decisions/obsidian-second-brain.md +0 -0
  334. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/logo.png +0 -0
  335. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/INDEX.md +0 -0
  336. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/architecture.md +0 -0
  337. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/briefing-mode.md +0 -0
  338. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/communication/channels.md +0 -0
  339. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/communication/handoff.md +0 -0
  340. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/concepts.md +0 -0
  341. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/council.md +0 -0
  342. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/deployment/remote-access.md +0 -0
  343. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/deployment/remote-machines.md +0 -0
  344. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/glossary.md +0 -0
  345. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/integrations/gws-google-workspace-cli.md +0 -0
  346. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/damage-control.md +0 -0
  347. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/parallel-refactor.md +0 -0
  348. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/portal.md +0 -0
  349. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/shell-escaping.md +0 -0
  350. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/troubleshooting.md +0 -0
  351. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/window-collage.md +0 -0
  352. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/quickstart.md +0 -0
  353. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/research/briefing-mode-feasibility.md +0 -0
  354. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/research/orchestration-transport-alternatives.md +0 -0
  355. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/scheduling/scheduled-workloads.md +0 -0
  356. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/security/damage-control-hardening.md +0 -0
  357. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/security/pip-audit.md +0 -0
  358. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/security/remote-access-hardening.md +0 -0
  359. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/security/secrets.md +0 -0
  360. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/services.md +0 -0
  361. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/claude-code-auto-mode.md +0 -0
  362. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/messaging.md +0 -0
  363. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/pi.md +0 -0
  364. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/prompt-routing.md +0 -0
  365. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/window-sizing.md +0 -0
  366. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/worktree-sessions.md +0 -0
  367. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/usage-limit-recovery.md +0 -0
  368. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/voice/shim-contract.md +0 -0
  369. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/voice/stt-cloud.md +0 -0
  370. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/voice/stt-self-hosted.md +0 -0
  371. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/voice/tts-self-hosted.md +0 -0
  372. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/pyproject.toml +0 -0
  373. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/conftest.py +0 -0
  374. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/e2e/test_portal_ui.py +0 -0
  375. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/fixtures/sample_agentwire.yml +0 -0
  376. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/fixtures/sample_config.yaml +0 -0
  377. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/fixtures/sample_scheduler.yaml +0 -0
  378. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_council_portal.py +0 -0
  379. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_scheduler_board.py +0 -0
  380. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_send_verified_tmux.py +0 -0
  381. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_server_websockets.py +0 -0
  382. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_worktree_cmd.py +0 -0
  383. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_announcements.py +0 -0
  384. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_build_agent_command.py +0 -0
  385. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_channels.py +0 -0
  386. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_cli_commands.py +0 -0
  387. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_cli_output.py +0 -0
  388. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_cli_smoke.py +0 -0
  389. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_config.py +0 -0
  390. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_control_plane_protection.py +0 -0
  391. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_council_cli.py +0 -0
  392. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_council_inbox.py +0 -0
  393. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_council_state.py +0 -0
  394. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_council_view.py +0 -0
  395. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_damage_control_bypass.py +0 -0
  396. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_damage_control_hooks.py +0 -0
  397. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_damage_control_sync.py +0 -0
  398. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_devices.py +0 -0
  399. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_diff_cli.py +0 -0
  400. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_doctor_voice.py +0 -0
  401. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_event_log.py +0 -0
  402. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_file_io.py +0 -0
  403. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_handoff_git_state.py +0 -0
  404. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_handoff_instructions.py +0 -0
  405. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_handoff_parser.py +0 -0
  406. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_handoff_renderer.py +0 -0
  407. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_history.py +0 -0
  408. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_hooks_install.py +0 -0
  409. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_idle_handler.py +0 -0
  410. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_idle_nag.py +0 -0
  411. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_init_command.py +0 -0
  412. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_limits_tick_isolation.py +0 -0
  413. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_locking.py +0 -0
  414. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_mcp_server.py +0 -0
  415. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_mcp_tools_args.py +0 -0
  416. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_msg_cli.py +0 -0
  417. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_project_config.py +0 -0
  418. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_prompt_router.py +0 -0
  419. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_push.py +0 -0
  420. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_rebuild_guard.py +0 -0
  421. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_repo_cli.py +0 -0
  422. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_roles.py +0 -0
  423. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_routes_snapshot.py +0 -0
  424. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_commands.py +0 -0
  425. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_disabled_rules.py +0 -0
  426. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_escape_hatch.py +0 -0
  427. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_heal.py +0 -0
  428. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_kill_switch.py +0 -0
  429. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_scheduler.py +0 -0
  430. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_scheduler_parsing.py +0 -0
  431. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_scratchpad.py +0 -0
  432. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_server_async.py +0 -0
  433. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_server_pure.py +0 -0
  434. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_services.py +0 -0
  435. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_session_context.py +0 -0
  436. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_session_ready.py +0 -0
  437. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_shared_dir_guard.py +0 -0
  438. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_skill_install.py +0 -0
  439. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_speech_tags.py +0 -0
  440. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_ssh.py +0 -0
  441. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_stt_cloud.py +0 -0
  442. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_stt_engine.py +0 -0
  443. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_task_cli.py +0 -0
  444. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tasks.py +0 -0
  445. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_templating.py +0 -0
  446. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_terminal_resize.py +0 -0
  447. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tmux_template.py +0 -0
  448. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tts_engine_resolution.py +0 -0
  449. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tunnels_probe.py +0 -0
  450. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_usage_limit.py +0 -0
  451. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_validation.py +0 -0
  452. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_voice_status.py +0 -0
  453. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_wiki.py +0 -0
  454. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_wiki_audit.py +0 -0
  455. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_worktree.py +0 -0
  456. {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_worktree_registry.py +0 -0
@@ -60,3 +60,6 @@ snapshot_report.html
60
60
 
61
61
  # Node
62
62
  node_modules/
63
+
64
+ # Private security-audit findings (never publish — see SECURITY.md)
65
+ /security-audit/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentwire-dev
3
- Version: 1.51.0
3
+ Version: 1.52.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.51.0"
3
+ __version__ = "1.52.0"
@@ -236,11 +236,11 @@ class TmuxAgent(AgentBackend):
236
236
  # Use base64 + load-buffer on remote to avoid PTY flooding
237
237
  import base64
238
238
  encoded = base64.b64encode(text.encode()).decode()
239
+ # Pipe straight into the tmux buffer — no on-disk temp that could
240
+ # expose message content or be pre-planted on the remote host
239
241
  cmd = (
240
- f"echo {shlex.quote(encoded)} | base64 -d > /tmp/aw-send-$$.txt && "
241
- f"tmux load-buffer /tmp/aw-send-$$.txt && "
242
+ f"echo {shlex.quote(encoded)} | base64 -d | tmux load-buffer - && "
242
243
  f"tmux paste-buffer -t {shlex.quote(session_name)} && "
243
- f"rm -f /tmp/aw-send-$$.txt && "
244
244
  f"sleep 0.2 && "
245
245
  f"tmux send-keys -t {shlex.quote(session_name)} Enter"
246
246
  )
@@ -74,6 +74,12 @@ class ServerConfig:
74
74
  # bearer token then transits in cleartext. Required to start a non-loopback
75
75
  # bind without TLS — the BYO-tunnel case where the tunnel terminates TLS.
76
76
  allow_insecure: bool = False
77
+ # Emit HTTP Strict-Transport-Security. Leave OFF for self-signed /
78
+ # localhost / LAN — HSTS + a self-signed cert locks the browser out of the
79
+ # portal if the cert ever rotates. Enable ONLY when serving a real
80
+ # (CA-signed) cert directly. Behind Cloudflare Tunnel / a reverse proxy,
81
+ # set HSTS at the edge instead.
82
+ hsts: bool = False
77
83
 
78
84
 
79
85
  @dataclass
@@ -590,6 +596,7 @@ def _dict_to_config(data: dict) -> Config:
590
596
  # "" (explicit disable) must survive — don't collapse it to None.
591
597
  auth_token=server_data.get("auth_token"),
592
598
  allow_insecure=bool(server_data.get("allow_insecure", False)),
599
+ hsts=bool(server_data.get("hsts", False)),
593
600
  )
594
601
 
595
602
  # Projects
@@ -30,6 +30,7 @@ from __future__ import annotations
30
30
  import errno
31
31
  import json
32
32
  import os
33
+ import re
33
34
  import time
34
35
  import uuid
35
36
  from dataclasses import dataclass
@@ -148,8 +149,28 @@ def _short_uuid() -> str:
148
149
  return uuid.uuid4().hex[:6]
149
150
 
150
151
 
152
+ # Session names are agent-controlled (msg_send `to`), so they must never be
153
+ # able to path-traverse out of INBOX_ROOT. Worktree session names legitimately
154
+ # contain `/` (they nest one directory level per segment — see module
155
+ # docstring), so segments are validated individually; `..`, absolute paths,
156
+ # and empty names/segments are rejected.
157
+ _SESSION_RE = re.compile(r"^[A-Za-z0-9._@-]+(?:/[A-Za-z0-9._@-]+)*$")
158
+
159
+
160
+ def _validate_session(session: str) -> str:
161
+ if not session or not _SESSION_RE.match(session) or ".." in session.split("/"):
162
+ raise ValueError(f"invalid session name: {session!r}")
163
+ return session
164
+
165
+
151
166
  def session_dir(session: str) -> Path:
152
- return INBOX_ROOT / session
167
+ _validate_session(session)
168
+ path = INBOX_ROOT / session
169
+ # Belt and braces: the regex already forbids traversal, but confine the
170
+ # result to INBOX_ROOT so a validator regression can't escape it.
171
+ if not path.resolve().is_relative_to(INBOX_ROOT.resolve()):
172
+ raise ValueError(f"invalid session name: {session!r}")
173
+ return path
153
174
 
154
175
 
155
176
  def dead_dir(session: str) -> Path:
@@ -440,7 +461,7 @@ def resolve_targets(to: str, sender: "str | None") -> list[str]:
440
461
  """
441
462
  if to == BROADCAST_TOKEN:
442
463
  return [s for s in _live_agent_sessions() if s != sender]
443
- return [to]
464
+ return [_validate_session(to)]
444
465
 
445
466
 
446
467
  def enqueue(
@@ -5,6 +5,7 @@ import shutil
5
5
  import signal
6
6
  import subprocess
7
7
  import sys
8
+ import tempfile
8
9
  import time
9
10
  from pathlib import Path
10
11
 
@@ -57,18 +58,53 @@ AGENTWIRE_PATH = _find_executable("agentwire", [
57
58
  str(Path.home() / ".local" / "bin" / "agentwire"),
58
59
  "/usr/local/bin/agentwire",
59
60
  ])
60
- LOCK_FILE = Path("/tmp/agentwire-listen.lock")
61
- PID_FILE = Path("/tmp/agentwire-listen.pid")
62
- AUDIO_FILE = Path("/tmp/agentwire-listen.wav")
63
- DEBUG_LOG = Path("/tmp/agentwire-listen.log")
61
+ # Runtime state lives under user-private dirs (0700), never world-writable /tmp:
62
+ # fixed /tmp names are pre-plantable by co-tenants (CWE-377).
63
+ RUN_DIR = Path.home() / ".agentwire" / "run"
64
+ LOG_DIR = Path.home() / ".agentwire" / "logs"
65
+ LOCK_FILE = RUN_DIR / "listen.lock"
66
+ PID_FILE = RUN_DIR / "listen.pid"
67
+ AUDIO_PATH_FILE = RUN_DIR / "listen.audio-path"
68
+ DEBUG_LOG = LOG_DIR / "listen.log"
69
+
70
+
71
+ def _ensure_private_dir(path: Path) -> None:
72
+ path.mkdir(mode=0o700, parents=True, exist_ok=True)
64
73
 
65
74
 
66
75
  def log(msg: str) -> None:
67
- """Log debug message."""
68
- with open(DEBUG_LOG, "a") as f:
76
+ """Log debug message (O_NOFOLLOW so a planted symlink can't redirect writes)."""
77
+ _ensure_private_dir(LOG_DIR)
78
+ fd = os.open(DEBUG_LOG, os.O_WRONLY | os.O_CREAT | os.O_APPEND | os.O_NOFOLLOW, 0o600)
79
+ with os.fdopen(fd, "a") as f:
69
80
  f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')}: {msg}\n")
70
81
 
71
82
 
83
+ def _new_audio_file() -> Path:
84
+ """Create an unpredictable 0600 audio temp file and record its path."""
85
+ _ensure_private_dir(RUN_DIR)
86
+ fd, path = tempfile.mkstemp(prefix="agentwire-listen-", suffix=".wav", dir=RUN_DIR)
87
+ os.close(fd)
88
+ AUDIO_PATH_FILE.write_text(path)
89
+ return Path(path)
90
+
91
+
92
+ def _current_audio_file() -> Path | None:
93
+ """Path of the in-flight recording, if any (spans start/stop invocations)."""
94
+ try:
95
+ text = AUDIO_PATH_FILE.read_text().strip()
96
+ except OSError:
97
+ return None
98
+ return Path(text) if text else None
99
+
100
+
101
+ def _cleanup_audio_file() -> None:
102
+ audio = _current_audio_file()
103
+ if audio is not None:
104
+ audio.unlink(missing_ok=True)
105
+ AUDIO_PATH_FILE.unlink(missing_ok=True)
106
+
107
+
72
108
  def notify(msg: str) -> None:
73
109
  """Show system notification (non-blocking)."""
74
110
  if sys.platform == "darwin":
@@ -162,14 +198,18 @@ def start_recording() -> int:
162
198
  log("start_recording called")
163
199
 
164
200
  # Clean up any stale recording
165
- subprocess.run(["pkill", "-9", "-f", "ffmpeg.*agentwire-listen\\.wav"],
201
+ subprocess.run(["pkill", "-9", "-f", "ffmpeg.*agentwire-listen-"],
166
202
  capture_output=True)
203
+ _ensure_private_dir(RUN_DIR)
167
204
  LOCK_FILE.unlink(missing_ok=True)
168
205
  PID_FILE.unlink(missing_ok=True)
169
- AUDIO_FILE.unlink(missing_ok=True)
206
+ _cleanup_audio_file()
170
207
  time.sleep(0.1)
171
208
 
172
- LOCK_FILE.touch()
209
+ # Exclusive create: a pre-planted lock file is an error, not silently reused
210
+ lock_fd = os.open(LOCK_FILE, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
211
+ os.close(lock_fd)
212
+ audio_file = _new_audio_file()
173
213
  beep("start")
174
214
 
175
215
  # Record audio (16kHz mono for whisper)
@@ -186,7 +226,7 @@ def start_recording() -> int:
186
226
  [FFMPEG_PATH, "-f", "avfoundation", "-i", input_spec,
187
227
  "-ar", "16000", "-ac", "1",
188
228
  "-acodec", "pcm_s16le", # Uncompressed for quality
189
- str(AUDIO_FILE), "-y"],
229
+ str(audio_file), "-y"],
190
230
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
191
231
  )
192
232
  else:
@@ -195,7 +235,7 @@ def start_recording() -> int:
195
235
  ["ffmpeg", "-f", "pulse", "-i", "default",
196
236
  "-ar", "16000", "-ac", "1",
197
237
  "-acodec", "pcm_s16le",
198
- str(AUDIO_FILE), "-y"],
238
+ str(audio_file), "-y"],
199
239
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
200
240
  )
201
241
 
@@ -239,7 +279,7 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
239
279
  PID_FILE.unlink(missing_ok=True)
240
280
 
241
281
  # Force kill any remaining ffmpeg processes
242
- subprocess.run(["pkill", "-9", "-f", "ffmpeg.*agentwire-listen\\.wav"],
282
+ subprocess.run(["pkill", "-9", "-f", "ffmpeg.*agentwire-listen-"],
243
283
  capture_output=True)
244
284
  LOCK_FILE.unlink(missing_ok=True)
245
285
 
@@ -247,7 +287,8 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
247
287
  time.sleep(0.3)
248
288
 
249
289
  # Verify file exists and has content
250
- if not AUDIO_FILE.exists():
290
+ audio_file = _current_audio_file()
291
+ if audio_file is None or not audio_file.exists():
251
292
  log("ERROR: No audio file")
252
293
  notify("Recording failed")
253
294
  beep("error")
@@ -256,14 +297,14 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
256
297
  # Wait for file to stabilize (size stops changing)
257
298
  last_size = 0
258
299
  for _ in range(10): # Max 1 second wait
259
- current_size = AUDIO_FILE.stat().st_size
300
+ current_size = audio_file.stat().st_size
260
301
  if current_size > 0 and current_size == last_size:
261
302
  break
262
303
  last_size = current_size
263
304
  time.sleep(0.1)
264
305
 
265
- if AUDIO_FILE.stat().st_size < 1000: # Less than 1KB is likely corrupt
266
- log(f"ERROR: Audio file too small ({AUDIO_FILE.stat().st_size} bytes)")
306
+ if audio_file.stat().st_size < 1000: # Less than 1KB is likely corrupt
307
+ log(f"ERROR: Audio file too small ({audio_file.stat().st_size} bytes)")
267
308
  notify("Recording too short")
268
309
  beep("error")
269
310
  return 1
@@ -285,7 +326,7 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
285
326
  return 1
286
327
  stt_url = stt_config.get("url", "http://localhost:8101")
287
328
 
288
- text = transcribe_via_server(AUDIO_FILE, stt_url)
329
+ text = transcribe_via_server(audio_file, stt_url)
289
330
  if text:
290
331
  log(f"Used STT shim at {stt_url}")
291
332
  else:
@@ -298,7 +339,7 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
298
339
  log("ERROR: No speech detected")
299
340
  notify("No speech detected")
300
341
  beep("error")
301
- AUDIO_FILE.unlink(missing_ok=True)
342
+ _cleanup_audio_file()
302
343
  return 1
303
344
 
304
345
  log(f"Transcribed: {text}")
@@ -310,7 +351,7 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
310
351
  beep("done")
311
352
  notify(f"Transcribed: {text[:30]}...")
312
353
  print(text)
313
- AUDIO_FILE.unlink(missing_ok=True)
354
+ _cleanup_audio_file()
314
355
  return 0
315
356
 
316
357
  if type_at_cursor:
@@ -345,7 +386,7 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
345
386
  log(f"ERROR: Hammerspoon failed: {result.stderr}")
346
387
  notify("Failed to type text")
347
388
  beep("error")
348
- AUDIO_FILE.unlink(missing_ok=True)
389
+ _cleanup_audio_file()
349
390
  return 1
350
391
 
351
392
  beep("done")
@@ -366,7 +407,7 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
366
407
  beep("error")
367
408
  print(f"Transcribed: {text}")
368
409
  print(f"But session '{session}' not running. Start with: agentwire dev")
369
- AUDIO_FILE.unlink(missing_ok=True)
410
+ _cleanup_audio_file()
370
411
  return 1
371
412
 
372
413
  # Build message
@@ -388,14 +429,14 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
388
429
  log(f"ERROR: agentwire send raised exception: {e}")
389
430
  notify("Failed to send to session")
390
431
  beep("error")
391
- AUDIO_FILE.unlink(missing_ok=True)
432
+ _cleanup_audio_file()
392
433
  return 1
393
434
 
394
435
  if result.returncode != 0:
395
436
  log(f"ERROR: agentwire send failed: {result.stderr}")
396
437
  notify("Failed to send to session")
397
438
  beep("error")
398
- AUDIO_FILE.unlink(missing_ok=True)
439
+ _cleanup_audio_file()
399
440
  return 1
400
441
 
401
442
  beep("done")
@@ -403,7 +444,7 @@ def stop_recording(session: str, voice_prompt: bool = True, type_at_cursor: bool
403
444
  notify(f"Sent: {text[:30]}...")
404
445
  print(f"Sent to {session}: {text}")
405
446
 
406
- AUDIO_FILE.unlink(missing_ok=True)
447
+ _cleanup_audio_file()
407
448
  return 0
408
449
 
409
450
 
@@ -417,10 +458,10 @@ def cancel_recording() -> int:
417
458
  pass
418
459
  PID_FILE.unlink(missing_ok=True)
419
460
 
420
- subprocess.run(["pkill", "-9", "-f", "ffmpeg.*agentwire-listen\\.wav"],
461
+ subprocess.run(["pkill", "-9", "-f", "ffmpeg.*agentwire-listen-"],
421
462
  capture_output=True)
422
463
  LOCK_FILE.unlink(missing_ok=True)
423
- AUDIO_FILE.unlink(missing_ok=True)
464
+ _cleanup_audio_file()
424
465
 
425
466
  beep("error")
426
467
  notify("Cancelled")
@@ -19,6 +19,8 @@ from pathlib import Path
19
19
 
20
20
  from aiohttp import web
21
21
 
22
+ from ..security import read_multipart_field_limited
23
+
22
24
  logger = logging.getLogger(__name__)
23
25
 
24
26
 
@@ -119,7 +121,16 @@ class VoiceRoutesMixin:
119
121
  if audio_field is None:
120
122
  return web.json_response({"error": "No audio data"})
121
123
 
122
- audio_data = await audio_field.read()
124
+ # Stream with a size cap: abort 413 before buffering an over-limit
125
+ # body into RAM. Dedicated stt.max_upload_mb wins if configured,
126
+ # else reuse the general uploads cap.
127
+ max_mb = (
128
+ getattr(self.config.stt, "max_upload_mb", None)
129
+ or self.config.uploads.max_size_mb
130
+ )
131
+ audio_data = await read_multipart_field_limited(
132
+ audio_field, max_mb * 1024 * 1024
133
+ )
123
134
  if not audio_data:
124
135
  return web.json_response({"error": "Empty audio data"})
125
136
 
@@ -148,6 +159,8 @@ class VoiceRoutesMixin:
148
159
  finally:
149
160
  Path(wav_path).unlink(missing_ok=True)
150
161
 
162
+ except web.HTTPException:
163
+ raise
151
164
  except Exception as e:
152
165
  logger.error(f"Transcription failed: {e}")
153
166
  return web.json_response({"error": str(e)})
@@ -23,11 +23,15 @@ Two layers, both enforced by a single aiohttp middleware:
23
23
  disable auth (loopback binds only); any other string → explicit override.
24
24
  """
25
25
 
26
+ import base64
27
+ import hashlib
26
28
  import hmac
27
29
  import ipaddress
28
30
  import logging
31
+ import os
29
32
  import re
30
33
  import secrets
34
+ import tempfile
31
35
  import time
32
36
  from pathlib import Path
33
37
  from typing import Callable, Optional
@@ -80,10 +84,29 @@ def read_token_file() -> Optional[str]:
80
84
 
81
85
 
82
86
  def write_token_file(token: str) -> None:
83
- """Write the token file with owner-only permissions."""
84
- TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
85
- TOKEN_FILE.write_text(token + "\n")
86
- TOKEN_FILE.chmod(0o600)
87
+ """Write the token file with owner-only permissions.
88
+
89
+ Atomic and never wider than 0600: the token is written to a same-directory
90
+ temp file created at 0600 (fchmod before any bytes land), then renamed over
91
+ the destination — there is no window where the god-token is group/world
92
+ readable, even on first creation under a permissive umask. The config dir
93
+ itself is created 0700 when missing. Rotation over an existing 0600 file
94
+ keeps 0600 (os.replace swaps the inode; the new one is already 0600).
95
+ """
96
+ parent = TOKEN_FILE.parent
97
+ parent.mkdir(parents=True, exist_ok=True, mode=0o700)
98
+ fd, tmp_path = tempfile.mkstemp(dir=parent, prefix=".portal.token.")
99
+ try:
100
+ os.fchmod(fd, 0o600)
101
+ with os.fdopen(fd, "w") as f:
102
+ f.write(token + "\n")
103
+ os.replace(tmp_path, TOKEN_FILE)
104
+ except BaseException:
105
+ try:
106
+ os.unlink(tmp_path)
107
+ except OSError:
108
+ pass
109
+ raise
87
110
 
88
111
 
89
112
  def resolve_auth_token(config) -> Optional[str]:
@@ -347,6 +370,144 @@ def resolve_device(provided: Optional[str], auth_token: Optional[str]) -> Option
347
370
  return devices_mod.load_registry_cached().resolve(provided)
348
371
 
349
372
 
373
+ # ---------------------------------------------------------------------------
374
+ # Security response headers (CSP & friends)
375
+
376
+ _TEMPLATES_DIR = Path(__file__).parent / "templates"
377
+ _INLINE_SCRIPT_RE = re.compile(r"<script\b[^>]*>(.*?)</script>", re.DOTALL | re.IGNORECASE)
378
+
379
+
380
+ def _inline_script_hashes() -> list[str]:
381
+ """CSP sha256 sources for inline <script> blocks in the bundled templates.
382
+
383
+ The pair page ships a small inline module (no Jinja interpolation inside
384
+ it), so ``script-src 'self'`` alone would break pairing. Hashing the blocks
385
+ at startup keeps script-src locked down while auto-healing when a template's
386
+ inline script changes.
387
+ """
388
+ hashes: list[str] = []
389
+ try:
390
+ for tpl in sorted(_TEMPLATES_DIR.glob("*.html")):
391
+ for m in _INLINE_SCRIPT_RE.finditer(tpl.read_text()):
392
+ body = m.group(1)
393
+ if not body.strip():
394
+ continue
395
+ digest = base64.b64encode(hashlib.sha256(body.encode()).digest()).decode()
396
+ hashes.append(f"'sha256-{digest}'")
397
+ except OSError:
398
+ pass
399
+ return hashes
400
+
401
+
402
+ def _build_csp() -> str:
403
+ """Portal-wide Content-Security-Policy.
404
+
405
+ Constraints baked in from the real UI:
406
+ - xterm.js / marked load from cdn.jsdelivr.net (script + css).
407
+ - Inline <style> attributes are used throughout (and WinBox injects a
408
+ style element) → style-src needs 'unsafe-inline'.
409
+ - WinBox uses data: SVG background images; uploads render as blobs.
410
+ - The dashboard/session/terminal WebSockets need ws:/wss:, and the
411
+ announcement modal fetches raw.githubusercontent.com.
412
+ - Voice playback uses blob: audio; browser STT records via MediaRecorder.
413
+ """
414
+ script_src = " ".join(["'self'", "https://cdn.jsdelivr.net"] + _inline_script_hashes())
415
+ return "; ".join([
416
+ "default-src 'self'",
417
+ f"script-src {script_src}",
418
+ # fonts.googleapis.com: the Council window injects a Google Fonts
419
+ # stylesheet (council-window.js); fonts.gstatic.com serves the woff2s.
420
+ "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com",
421
+ "img-src 'self' data: blob:",
422
+ "font-src 'self' data: https://fonts.gstatic.com",
423
+ "connect-src 'self' ws: wss: https://raw.githubusercontent.com",
424
+ "media-src 'self' blob: data:",
425
+ "worker-src 'self' blob:",
426
+ "frame-src 'self'",
427
+ "object-src 'none'",
428
+ "base-uri 'self'",
429
+ "frame-ancestors 'self'",
430
+ ])
431
+
432
+
433
+ # Artifacts are agent-generated HTML rendered in sandboxed iframes; they may
434
+ # legitimately carry inline scripts/styles and pull CDN libraries, so the
435
+ # policy stays permissive on content while locking embedding (frame-ancestors)
436
+ # and plugins. The iframe sandbox attribute remains the primary containment.
437
+ _ARTIFACTS_CSP = "; ".join([
438
+ "default-src 'self' 'unsafe-inline' data: blob: https:",
439
+ "object-src 'none'",
440
+ "base-uri 'self'",
441
+ "frame-ancestors 'self'",
442
+ ])
443
+
444
+
445
+ def create_security_headers_middleware(hsts_enabled: bool = False):
446
+ """Middleware stamping security response headers on every response.
447
+
448
+ HSTS is opt-in (``server.hsts``) and emitted only on TLS requests: agentwire
449
+ commonly serves HTTPS with a self-signed cert, and a pinned HSTS policy
450
+ removes the cert-error click-through — a rotated/expired self-signed cert
451
+ would hard-lock the browser out of the portal.
452
+ """
453
+ csp = _build_csp()
454
+
455
+ @web.middleware
456
+ async def security_headers_middleware(request: web.Request, handler):
457
+ try:
458
+ response = await handler(request)
459
+ except web.HTTPException as exc:
460
+ _stamp_security_headers(request, exc, csp, hsts_enabled)
461
+ raise
462
+ _stamp_security_headers(request, response, csp, hsts_enabled)
463
+ return response
464
+
465
+ return security_headers_middleware
466
+
467
+
468
+ def _stamp_security_headers(
469
+ request: web.Request, response, csp: str, hsts_enabled: bool
470
+ ) -> None:
471
+ headers = response.headers
472
+ headers.setdefault("X-Content-Type-Options", "nosniff")
473
+ headers.setdefault("X-Frame-Options", "SAMEORIGIN")
474
+ headers.setdefault("Referrer-Policy", "same-origin")
475
+ if request.path.startswith("/artifacts/"):
476
+ headers["Content-Security-Policy"] = _ARTIFACTS_CSP
477
+ else:
478
+ headers.setdefault("Content-Security-Policy", csp)
479
+ if hsts_enabled and request.secure:
480
+ headers.setdefault("Strict-Transport-Security", "max-age=31536000")
481
+
482
+
483
+ # ---------------------------------------------------------------------------
484
+ # Bounded multipart reads (upload DoS guard)
485
+
486
+
487
+ async def read_multipart_field_limited(field, max_bytes: int) -> bytes:
488
+ """Read a multipart field, aborting with 413 once ``max_bytes`` is exceeded.
489
+
490
+ ``await field.read()`` buffers the entire part into RAM before any size
491
+ check can run (aiohttp's ``client_max_size`` does not bound the streaming
492
+ ``request.multipart()`` path) — an oversized upload would balloon the
493
+ portal's memory before being rejected. Streaming chunk-by-chunk caps the
494
+ buffered amount at ``max_bytes`` + one chunk.
495
+ """
496
+ chunks: list[bytes] = []
497
+ size = 0
498
+ while True:
499
+ chunk = await field.read_chunk()
500
+ if not chunk:
501
+ break
502
+ size += len(chunk)
503
+ if size > max_bytes:
504
+ raise web.HTTPRequestEntityTooLarge(
505
+ max_size=max_bytes, actual_size=size,
506
+ )
507
+ chunks.append(chunk)
508
+ return b"".join(chunks)
509
+
510
+
350
511
  # ---------------------------------------------------------------------------
351
512
  # Auth-failure tracking + lockout (gap #5)
352
513
 
@@ -116,11 +116,11 @@ def cmd_send(args) -> int:
116
116
 
117
117
  if use_buffer:
118
118
  encoded = base64.b64encode(prompt.encode()).decode()
119
+ # Pipe straight into the tmux buffer — no on-disk temp that could
120
+ # expose message content or be pre-planted on the remote host
119
121
  cmd = (
120
- f"echo {shlex.quote(encoded)} | base64 -d > /tmp/aw-send-$$.txt && "
121
- f"tmux load-buffer /tmp/aw-send-$$.txt && "
122
+ f"echo {shlex.quote(encoded)} | base64 -d | tmux load-buffer - && "
122
123
  f"tmux paste-buffer -t {quoted_session} && "
123
- f"rm -f /tmp/aw-send-$$.txt && "
124
124
  f"sleep 0.5 && "
125
125
  f"tmux send-keys -t {quoted_session} Enter"
126
126
  )