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.
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.gitignore +3 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/PKG-INFO +1 -1
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/__init__.py +1 -1
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/agents/tmux.py +3 -3
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/config.py +7 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/inbox.py +23 -2
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/listen.py +67 -26
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/voice.py +14 -1
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/security.py +165 -4
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/send_cli.py +3 -3
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/server.py +36 -19
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/artifact-window.js +21 -24
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/__init__.py +19 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/engines/kokoro.py +29 -6
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voiceclone.py +64 -18
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/communication/hammerspoon.md +1 -1
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_inbox.py +48 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_portal_api.py +138 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_security.py +107 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_stt_backend.py +21 -1
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tts_local.py +31 -3
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.agentwire.yml.example +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.claude/commands/handoff.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.claude/skills/wiki/SKILL.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/FUNDING.yml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/workflows/pytest.yml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/workflows/ruff.yml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/workflows/security.yml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/.github/workflows/tts-smoke.yml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/AGENTS.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/CODE_OF_CONDUCT.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/CONTRIBUTING.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/Dockerfile.local +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/LICENSE +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/README.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/RELEASING.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/SECURITY.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/SPONSORS.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/__main__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/agents/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/agents/base.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/cached_status.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/base.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/email.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/push.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels/quo.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/channels_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/completion.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/core.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/inbox.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/state.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/council/view.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/devices.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/diff_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/doctor_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/doctor_voice.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/ensure_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/fetch.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/git_state.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/instructions.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/parser.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/renderer.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff/schema.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/handoff_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/history.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/history_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/agentwire-permission.sh +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/audit_logger.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/bash-tool-damage-control.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/edit-tool-damage-control.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/mcp-tool-damage-control.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/read-tool-damage-control.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/agentwire.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/aws.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/cloud-hosting.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/containers.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/control-plane.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/core.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/databases.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/firebase.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/gcp.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/git.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/gws.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/infrastructure.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/outbound.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/publish.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/rules/remote.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/damage-control/write-tool-damage-control.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/idle-handler.sh +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks/queue-processor.sh +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/hooks_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/limits_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/locking.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/machine_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_channels.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_core.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_council.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_desktop.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_handoff.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_history.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_listen.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_lock.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_machine.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_msg.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_notify.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_pane.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_projects.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_scheduler.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_scratchpad.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_server.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_services.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_session.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_status.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_task.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_tunnels.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_voice.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_voiceclone.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_wiki.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/mcp_worktree.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/msg_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/network.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/notify_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/onboarding.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/pane_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/pane_manager.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/portal_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/project_config.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/projects.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/prompt_router.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/prompts_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/push_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/push_store.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/repo_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/research.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/agentwire.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/anchor.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/chatbot.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/contributor.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/correspondent.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-brain.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-conscience.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-critic.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-devils-advocate.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-gut.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-historian.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-member.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/council-orchestrator.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/init.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/notifications.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/orchestrator.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/soul.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/task-runner.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/voice.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/worker.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles/worktree-session.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/roles_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/artifacts.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/config.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/council.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/desktop.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/history.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/machines.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/notify.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/permission.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/projects.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/push.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/safety.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/scheduler.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/scratchpad.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/sessions.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/routes/sessions_admin.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/safety/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/safety/_core.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/safety_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/safety_commands.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/scheduler.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/scheduler_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/scratchpad.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/services.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/session_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/session_context.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/session_ready.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/ssh.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--agentwire-text.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--echo-claw-fg.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--echo.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--telephone.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/agentwire-splash-logo-layers--tree.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/announcements.json +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/css/desktop.css +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/css/mobile.css +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/favicon.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/bear.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/cat.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/crown.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/custom/agentwire-portal.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/custom/agentwire-tts.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/custom/agentwire.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/deer.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/drone.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/eagle.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/fox.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/hawk.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/horse.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/lion.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/rabbit.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/robot.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/tiger.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/icons/sessions/wolf.webp +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/img/icon-192.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/img/icon-512.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/.gitkeep +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/announcement-modal.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/api.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/collage.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/command-palette.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/council-window.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/dead-key-suppressor.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/desktop-manager.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/desktop.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/help-modal.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/icon-manager.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/mobile.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/notification-prefs.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/notifications-panel.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/push-client.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/review-window.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/safety-shared.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/safety-window.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/scratchpad.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/service-classification.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/session-id.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/session-window.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/shortcuts.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/artifacts-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/config-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/council-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/machines-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/projects-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/safety-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/scheduler-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/services-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar/sessions-section.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/sidebar.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/terminal-font-prefs.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/tile-manager.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/toast.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/token-modal.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/utils/ansi.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/utils/swipe.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/autosend-prefs.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/browser-stt.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/browser-tts.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/jargon.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/voice/prompt.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/js/winbox.bundle.min.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/manifest.webmanifest +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/static/service-worker.js +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/base.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/cloud.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/engine.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/local.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/server_backend.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/stt/stt_server.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/system_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tasks.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/base.html +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/desktop.html +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/email_notification.html +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/handoff/show-the-story.html.j2 +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/handoff/theme.css.j2 +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/mobile.html +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/pair.html +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templates/tmux.conf +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/templating.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/aws.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/docker.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/gcp.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/gh.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/git.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/gws.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/kubectl.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/npm.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/terraform.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tooldefs/uv.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/audio.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/base.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/engines/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/engines/chatterbox.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/engines/zonos.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/kokoro_server.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/local.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts/registry.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tts_server.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tunnels.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/tunnels_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/usage_limit.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/__init__.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/chunker.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/event_log.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/file_io.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/paths.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/speech.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/utils/subprocess.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/validation.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voice_status.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/darren.wav +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/default.wav +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/jessica.wav +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/lisa.wav +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/voices/may.wav +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/wiki.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/wiki_audit.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/wiki_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/worktree.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/agentwire/worktree_registry.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/decisions/obsidian-second-brain.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/logo.png +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/INDEX.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/architecture.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/briefing-mode.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/communication/channels.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/communication/handoff.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/concepts.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/council.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/deployment/remote-access.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/deployment/remote-machines.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/glossary.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/integrations/gws-google-workspace-cli.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/damage-control.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/parallel-refactor.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/portal.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/shell-escaping.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/troubleshooting.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/internals/window-collage.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/quickstart.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/research/briefing-mode-feasibility.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/research/orchestration-transport-alternatives.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/scheduling/scheduled-workloads.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/security/damage-control-hardening.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/security/pip-audit.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/security/remote-access-hardening.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/security/secrets.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/services.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/claude-code-auto-mode.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/messaging.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/pi.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/prompt-routing.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/window-sizing.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/sessions/worktree-sessions.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/usage-limit-recovery.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/voice/shim-contract.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/voice/stt-cloud.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/voice/stt-self-hosted.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/docs/wiki/voice/tts-self-hosted.md +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/pyproject.toml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/conftest.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/e2e/test_portal_ui.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/fixtures/sample_agentwire.yml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/fixtures/sample_config.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/fixtures/sample_scheduler.yaml +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_council_portal.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_scheduler_board.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_send_verified_tmux.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_server_websockets.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/integration/test_worktree_cmd.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_announcements.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_build_agent_command.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_channels.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_cli_commands.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_cli_output.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_cli_smoke.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_config.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_control_plane_protection.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_council_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_council_inbox.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_council_state.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_council_view.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_damage_control_bypass.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_damage_control_hooks.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_damage_control_sync.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_devices.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_diff_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_doctor_voice.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_event_log.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_file_io.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_handoff_git_state.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_handoff_instructions.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_handoff_parser.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_handoff_renderer.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_history.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_hooks_install.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_idle_handler.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_idle_nag.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_init_command.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_limits_tick_isolation.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_locking.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_mcp_server.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_mcp_tools_args.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_msg_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_project_config.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_prompt_router.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_push.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_rebuild_guard.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_repo_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_roles.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_routes_snapshot.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_commands.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_disabled_rules.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_escape_hatch.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_heal.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_safety_kill_switch.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_scheduler.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_scheduler_parsing.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_scratchpad.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_server_async.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_server_pure.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_services.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_session_context.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_session_ready.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_shared_dir_guard.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_skill_install.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_speech_tags.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_ssh.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_stt_cloud.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_stt_engine.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_task_cli.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tasks.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_templating.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_terminal_resize.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tmux_template.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tts_engine_resolution.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_tunnels_probe.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_usage_limit.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_validation.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_voice_status.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_wiki.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_wiki_audit.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_worktree.py +0 -0
- {agentwire_dev-1.51.0 → agentwire_dev-1.52.0}/tests/unit/test_worktree_registry.py +0 -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
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
206
|
+
_cleanup_audio_file()
|
|
170
207
|
time.sleep(0.1)
|
|
171
208
|
|
|
172
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
266
|
-
log(f"ERROR: Audio file too small ({
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
461
|
+
subprocess.run(["pkill", "-9", "-f", "ffmpeg.*agentwire-listen-"],
|
|
421
462
|
capture_output=True)
|
|
422
463
|
LOCK_FILE.unlink(missing_ok=True)
|
|
423
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
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
|
)
|