agentwire-dev 1.22.0__tar.gz → 1.23.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.22.0 → agentwire_dev-1.23.0}/CHANGELOG.md +16 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/PKG-INFO +1 -1
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/__init__.py +1 -1
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/__main__.py +82 -6
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/scheduler.py +299 -32
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/scheduled-workloads.md +24 -4
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/workflows.md +126 -1
- agentwire_dev-1.23.0/tests/integration/test_scheduler_workflow.py +213 -0
- agentwire_dev-1.23.0/tests/unit/test_scheduler.py +300 -0
- agentwire_dev-1.22.0/tests/unit/test_scheduler.py +0 -147
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/.github/FUNDING.yml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/.gitignore +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/CLA.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/CODE_OF_CONDUCT.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/CONTRIBUTING.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/Dockerfile.local +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/Dockerfile.runpod +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/LICENSE +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/README.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/RELEASING.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/SECURITY.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/SPONSORS.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/agents/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/agents/base.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/agents/tmux.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/bridges/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/bridges/telegram.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/cached_status.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/_template.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/base.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/discord.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/email.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/quo.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/slack.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/sms.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/telegram.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/channels/webhook.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/cli_safety.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/completion.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/config.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/errors.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/history.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/agentwire-permission.sh +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/audit_logger.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/bash-tool-damage-control.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/edit-tool-damage-control.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/agentwire.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/aws.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/cloud-hosting.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/containers.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/core.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/databases.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/firebase.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/gcp.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/git.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/gws.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/infrastructure.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/rules/remote.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/damage-control/write-tool-damage-control.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/hooks/idle-handler.sh +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/init_agentwire.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/listen.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/locking.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/mcp_server.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/network.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/onboarding.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/overnight.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/pane_manager.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/project_config.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/projects.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/prompts/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/prompts/init.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/agentwire.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/channel-admin.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/chatbot.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/discord-dm.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/init.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/notifications.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/orchestrator.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/slack-dm.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/task-runner.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/voice.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/roles/worker.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/server.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-Echo--black.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-Echo--transparent.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-Echo.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-email-banner.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--agentwire-text.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--echo-claw-fg.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--echo.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--full--transparent-top.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--full-black.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--telephone-fg.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--telephone.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--transparent-top.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--transparent.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers--tree.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/agentwire-splash-logo-layers.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/css/desktop.css +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/favicon.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/android.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/automaton.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/bot.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/cyborg.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/droid.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/drone.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/guardian.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/mech.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/probe.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/robot.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/sentinel.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/machines/unit.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/blob.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/cloud.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/crystal.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/cyclops.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/flame.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/fuzzy.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/horned.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/moon.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/slime.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/star.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/tentacle.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/projects/winged.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/bear.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/cat.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/crown.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/custom/agentwire-portal.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/custom/agentwire-tts.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/custom/agentwire.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/deer.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/drone.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/eagle.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/fox.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/hawk.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/horse.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/lion.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/rabbit.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/robot.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/tiger.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/icons/sessions/wolf.jpeg +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/.gitkeep +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/artifact-window.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/components/icon-picker.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/components/list-card.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/components/type-tag.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/desktop-manager.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/desktop.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/icon-manager.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/notifications-panel.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/session-window.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar/artifacts-section.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar/config-section.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar/machines-section.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar/projects-section.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar/scheduler-section.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar/services-section.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar/sessions-section.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar/socials-section.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/sidebar.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/tile-manager.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/utils/auto-refresh.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/winbox.bundle.min.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/static/js/windows/chat-window.js +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/stt/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/stt/base.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/stt/server_backend.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/stt/stt_server.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/stt/whisperkit.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tasks.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/templates/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/templates/base.html +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/templates/desktop.html +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/templates/email_notification.html +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/templates/tmux.conf +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/templating.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/aws.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/docker.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/gcp.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/gh.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/git.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/gws.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/kubectl.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/npm.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/terraform.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tooldefs/uv.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/base.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/engines/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/engines/chatterbox.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/engines/kokoro.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/engines/qwen_base.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/engines/qwen_custom.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/engines/qwen_design.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/engines/zonos.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/registry.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts/runpod_handler.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tts_server.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/tunnels.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/utils/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/utils/chunker.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/utils/file_io.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/utils/paths.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/utils/subprocess.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/validation.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/voiceclone.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/voices/darren.wav +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/voices/default.wav +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/voices/jessica.wav +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/voices/lisa.wav +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/voices/may.wav +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/__init__.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/cli.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/context.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/definitions.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/node.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/outputs.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/pi_runner.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/runner.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/workflows/storage.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/agentwire/worktree.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/FEATURE-REQUESTS.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/FR-auto-mode-session-type.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/PORTAL.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/SHELL_ESCAPING.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/SPONSORS.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/TROUBLESHOOTING.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/README.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-agent-hot-swap.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-ambient-context-stream.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-ambient-listening-mode.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-audio-cues.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-checkpoint-commits.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-context-compression-protocol.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-context-window-gauge.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-contextual-bookmarks.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-conversation-archaeology.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-cost-tracking-dashboard.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-cross-session-events.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-delegation-replay.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-device-session-tethering.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-failure-memory.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-notification-escalation.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-periodic-voice-briefings.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-presence-aware-sessions.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-session-drift-detection.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-session-energy-model.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-session-handshake.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-session-momentum.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-session-replay.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-session-snapshots.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-session-templates.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-session-thermal-throttling.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-smart-session-routing.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-spatial-voice-mixing.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-speculative-execution.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-task-pipeline-chaining.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-task-pivot-protocol.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-task-time-budgets.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-activity-zones.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-breakpoints.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-code-review.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-command-undo.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-handoff.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-identity.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-interrupts-v2.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-interrupts.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-macros.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-voice-transcript-logs.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-watchdog-mode.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-worker-fencing.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-worker-file-coordination.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-worker-health-dashboard.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-worker-heartbeat-watchdog.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-worker-progress-streaming.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-worker-proof-of-work.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-worker-warmup.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/brainstorms/idea-x-api-integration.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/channels.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/claude-code-auto-mode.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/critical-analysis.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/demo-script.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/gws-google-workspace-cli.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/hammerspoon.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/issues/pending-code-changes.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/issues/projects-dedup-ignores-machine.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/issues/remote-tts-session-detection.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/issues/tmux-config-onboarding.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/issues/tmux-paste-freeze.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/issues/tmux-recommended-config.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/logo.png +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/notification-hooks.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/pi-zai.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/progressive-loading-pattern.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/prompts/agentwire-website.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/releasing.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/remote-access.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/remote-machines.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/runpod-tts.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/security/damage-control-migration.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/security/damage-control.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/tmux-hooks.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/tts-self-hosted.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/docs/youtube-channel.md +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/pyproject.toml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/requirements-tts.txt +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/conftest.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/e2e/test_portal_ui.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/fixtures/sample_agentwire.yml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/fixtures/sample_config.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/fixtures/sample_scheduler.yaml +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/integration/test_build_agent_command.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/integration/test_channels_cli.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/integration/test_cli_commands.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/integration/test_cli_output.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/integration/test_mcp_tools.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/integration/test_portal_api.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/integration/test_scheduler_board.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_channels.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_cli_safety.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_config.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_file_io.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_history.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_locking.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_mcp_server.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_project_config.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_roles.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_server_async.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_server_pure.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_tasks.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_templating.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_workflows.py +0 -0
- {agentwire_dev-1.22.0 → agentwire_dev-1.23.0}/tests/unit/test_worktree.py +0 -0
|
@@ -7,8 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.23.0] - 2026-04-16
|
|
11
|
+
|
|
10
12
|
### Added
|
|
11
13
|
|
|
14
|
+
- **Workflow-backed scheduler tasks** — scheduler can now dispatch a pi workflow DAG in-process instead of shelling out to `agentwire ensure` (Phase 3 of the pi workflow roadmap)
|
|
15
|
+
- New `workflow:` + `inputs:` fields on scheduler tasks in `~/.agentwire/scheduler.yaml` (mutually exclusive with `task:`)
|
|
16
|
+
- `dispatch_task()` routes automatically: ensure path unchanged, new `_dispatch_workflow_task` calls `run_workflow()` in-process — no tmux, no Claude Code subprocess
|
|
17
|
+
- Status mapping: workflow `success→complete`, `partial→incomplete`, `failure→failed`
|
|
18
|
+
- Scheduler `{{ task }}`, `{{ project }}`, `{{ session }}`, `{{ workflow }}` variables expand in string `inputs:` values
|
|
19
|
+
- `agentwire scheduler run <name> --dry-run` prints the workflow plan without touching state
|
|
20
|
+
- `task_completed` events now carry `workflow`, `run_id`, and per-node `nodes[]` when the task is workflow-backed
|
|
21
|
+
- Morning report (`agentwire scheduler report --artifact`) renders per-node status badges + run-id breadcrumb for workflow rows
|
|
22
|
+
- `agentwire scheduler history --json` includes the `workflow` name per task
|
|
23
|
+
- Full reference: `docs/workflows.md` → "Scheduler integration"; compare/contrast: `docs/scheduled-workloads.md`
|
|
12
24
|
- **Kokoro TTS engine** — CPU-only ultra-lightweight backend (`kokoro`)
|
|
13
25
|
- Kokoro 82M ONNX model via `kokoro-onnx`, auto-downloads ~170 MB from HuggingFace on first use
|
|
14
26
|
- No GPU required — pure ONNX CPU inference, near real-time on Apple Silicon / modern Intel CPU
|
|
@@ -16,6 +28,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
28
|
- Streaming support via `create_stream()`
|
|
17
29
|
- Runs in dedicated `.venv-kokoro` with CPU-only PyTorch (~250 MB vs 2 GB+ CUDA builds)
|
|
18
30
|
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- `cmd_scheduler_report` was calling `read_events(limit=500)` with the wrong kwarg name; the `except` silently caught the `TypeError` so morning reports quietly returned 0 events. Now calls `read_events(tail=500)`.
|
|
34
|
+
|
|
19
35
|
## [1.9.0] - 2026-03-13
|
|
20
36
|
|
|
21
37
|
### Added
|
|
@@ -8744,9 +8744,10 @@ def cmd_scheduler_board(args) -> int:
|
|
|
8744
8744
|
|
|
8745
8745
|
def cmd_scheduler_run(args) -> int:
|
|
8746
8746
|
"""Force-run a specific task now."""
|
|
8747
|
-
from .scheduler import dispatch_task, load_board, save_board
|
|
8747
|
+
from .scheduler import _render_workflow_inputs, dispatch_task, load_board, save_board
|
|
8748
8748
|
|
|
8749
8749
|
json_mode = getattr(args, 'json', False)
|
|
8750
|
+
dry_run = getattr(args, 'dry_run', False)
|
|
8750
8751
|
name = args.name
|
|
8751
8752
|
|
|
8752
8753
|
try:
|
|
@@ -8760,6 +8761,40 @@ def cmd_scheduler_run(args) -> int:
|
|
|
8760
8761
|
f"Task '{name}' not found in board. Available: {', '.join(board.tasks.keys())}",
|
|
8761
8762
|
)
|
|
8762
8763
|
|
|
8764
|
+
task = board.tasks[name]
|
|
8765
|
+
|
|
8766
|
+
if dry_run:
|
|
8767
|
+
if not task.workflow:
|
|
8768
|
+
return _output_result(
|
|
8769
|
+
False, json_mode,
|
|
8770
|
+
f"--dry-run only applies to workflow tasks; '{name}' runs via ensure.",
|
|
8771
|
+
)
|
|
8772
|
+
from agentwire.workflows.definitions import resolve_workflow
|
|
8773
|
+
from agentwire.workflows.runner import run_workflow
|
|
8774
|
+
try:
|
|
8775
|
+
wf = resolve_workflow(task.workflow)
|
|
8776
|
+
except Exception as e:
|
|
8777
|
+
return _output_result(False, json_mode, f"Could not load workflow '{task.workflow}': {e}")
|
|
8778
|
+
rendered_inputs = _render_workflow_inputs(task.inputs, task)
|
|
8779
|
+
run = run_workflow(wf, runs_dir=None, inputs=rendered_inputs, dry_run=True)
|
|
8780
|
+
if json_mode:
|
|
8781
|
+
_output_json({
|
|
8782
|
+
"success": True,
|
|
8783
|
+
"task": name,
|
|
8784
|
+
"dry_run": True,
|
|
8785
|
+
"workflow": wf.name,
|
|
8786
|
+
"inputs": rendered_inputs,
|
|
8787
|
+
"nodes": [r.node_id for r in run.node_results],
|
|
8788
|
+
"status": run.status,
|
|
8789
|
+
})
|
|
8790
|
+
return 0
|
|
8791
|
+
print(f"Dry-run: {name} → workflow '{wf.name}'")
|
|
8792
|
+
if rendered_inputs:
|
|
8793
|
+
print(f" inputs: {rendered_inputs}")
|
|
8794
|
+
for r in run.node_results:
|
|
8795
|
+
print(f" - {r.node_id}: {r.final_text}")
|
|
8796
|
+
return 0
|
|
8797
|
+
|
|
8763
8798
|
if not json_mode:
|
|
8764
8799
|
print(f"Running: {name}")
|
|
8765
8800
|
|
|
@@ -8835,12 +8870,14 @@ def cmd_scheduler_history(args) -> int:
|
|
|
8835
8870
|
if json_mode:
|
|
8836
8871
|
history = []
|
|
8837
8872
|
for name, state in board.state.items():
|
|
8873
|
+
task = board.tasks.get(name)
|
|
8838
8874
|
history.append({
|
|
8839
8875
|
"task": name,
|
|
8840
8876
|
"last_run": state.last_run.isoformat() if state.last_run else None,
|
|
8841
8877
|
"last_status": state.last_status,
|
|
8842
8878
|
"last_duration": state.last_duration,
|
|
8843
8879
|
"run_count": state.run_count,
|
|
8880
|
+
"workflow": (task.workflow if task else "") or None,
|
|
8844
8881
|
})
|
|
8845
8882
|
_output_json({"success": True, "history": history})
|
|
8846
8883
|
return 0
|
|
@@ -8867,6 +8904,7 @@ def cmd_scheduler_history(args) -> int:
|
|
|
8867
8904
|
def cmd_scheduler_report(args) -> int:
|
|
8868
8905
|
"""Generate a morning report HTML artifact of recent task runs."""
|
|
8869
8906
|
import re as _re
|
|
8907
|
+
from html import escape as html_escape
|
|
8870
8908
|
from .scheduler import _parse_duration, format_interval, load_board, read_events
|
|
8871
8909
|
|
|
8872
8910
|
json_mode = getattr(args, 'json', False)
|
|
@@ -8886,7 +8924,7 @@ def cmd_scheduler_report(args) -> int:
|
|
|
8886
8924
|
|
|
8887
8925
|
# Load events in the window
|
|
8888
8926
|
try:
|
|
8889
|
-
events = read_events(
|
|
8927
|
+
events = read_events(tail=500)
|
|
8890
8928
|
except Exception:
|
|
8891
8929
|
events = []
|
|
8892
8930
|
|
|
@@ -8912,6 +8950,9 @@ def cmd_scheduler_report(args) -> int:
|
|
|
8912
8950
|
"timestamp": ts.strftime("%Y-%m-%d %H:%M"),
|
|
8913
8951
|
"work_branch": "",
|
|
8914
8952
|
"pr_url": "",
|
|
8953
|
+
"workflow": ev.get("workflow", ""),
|
|
8954
|
+
"run_id": ev.get("run_id", ""),
|
|
8955
|
+
"nodes": ev.get("nodes") or [],
|
|
8915
8956
|
}
|
|
8916
8957
|
runs.append(run)
|
|
8917
8958
|
|
|
@@ -8935,20 +8976,48 @@ def cmd_scheduler_report(args) -> int:
|
|
|
8935
8976
|
color = colors.get(status, "#78909c")
|
|
8936
8977
|
return f'<span style="background:{color};color:#fff;padding:2px 8px;border-radius:12px;font-size:0.85em">{status}</span>'
|
|
8937
8978
|
|
|
8979
|
+
def node_badges(nodes: list) -> str:
|
|
8980
|
+
if not nodes:
|
|
8981
|
+
return ""
|
|
8982
|
+
node_colors = {
|
|
8983
|
+
"success": "#00c853",
|
|
8984
|
+
"failure": "#ff5252",
|
|
8985
|
+
"timeout": "#ff7043",
|
|
8986
|
+
"skipped": "#78909c",
|
|
8987
|
+
}
|
|
8988
|
+
pieces = []
|
|
8989
|
+
for n in nodes:
|
|
8990
|
+
nid = html_escape(str(n.get("id", "?")))
|
|
8991
|
+
nstatus = str(n.get("status", "?"))
|
|
8992
|
+
color = node_colors.get(nstatus, "#556")
|
|
8993
|
+
pieces.append(
|
|
8994
|
+
f'<span style="background:{color};color:#fff;padding:1px 6px;border-radius:8px;font-size:0.75em;margin-right:3px">{nid}</span>'
|
|
8995
|
+
)
|
|
8996
|
+
return "".join(pieces)
|
|
8997
|
+
|
|
8938
8998
|
rows_html = ""
|
|
8939
8999
|
for r in runs:
|
|
8940
9000
|
duration_str = format_interval(r["duration"]) if r["duration"] else "-"
|
|
8941
9001
|
pr_link = f'<a href="{r["pr_url"]}" target="_blank" style="color:#00d4ff">{r["pr_url"][:40]}...</a>' if r.get("pr_url") else "-"
|
|
8942
|
-
|
|
9002
|
+
branch_col = ""
|
|
9003
|
+
if r.get("workflow"):
|
|
9004
|
+
wf_label = html_escape(r["workflow"])
|
|
9005
|
+
run_id = html_escape(r.get("run_id", ""))
|
|
9006
|
+
badges = node_badges(r["nodes"])
|
|
9007
|
+
run_hint = f' <span style="color:#556;font-size:0.75em">({run_id[:20]}…)</span>' if run_id else ""
|
|
9008
|
+
branch_col = f'<code style="font-size:0.85em">workflow:{wf_label}</code>{run_hint}<div style="margin-top:4px">{badges}</div>'
|
|
9009
|
+
else:
|
|
9010
|
+
branch_col = f'<code style="font-size:0.85em">{r.get("work_branch") or "-"}</code>'
|
|
9011
|
+
summary_text = r["summary"][:120] if r["summary"] else "-"
|
|
8943
9012
|
rows_html += f"""
|
|
8944
9013
|
<tr>
|
|
8945
9014
|
<td style="font-weight:600">{r["task"]}</td>
|
|
8946
9015
|
<td>{status_badge(r["status"])}</td>
|
|
8947
9016
|
<td>{r["timestamp"]}</td>
|
|
8948
9017
|
<td>{duration_str}</td>
|
|
8949
|
-
<td
|
|
9018
|
+
<td>{branch_col}</td>
|
|
8950
9019
|
<td>{pr_link}</td>
|
|
8951
|
-
<td style="color:#aaa;font-size:0.85em">{
|
|
9020
|
+
<td style="color:#aaa;font-size:0.85em">{summary_text}</td>
|
|
8952
9021
|
</tr>"""
|
|
8953
9022
|
|
|
8954
9023
|
if not rows_html:
|
|
@@ -10550,7 +10619,12 @@ def main() -> int:
|
|
|
10550
10619
|
scheduler_parser = subparsers.add_parser(
|
|
10551
10620
|
"scheduler",
|
|
10552
10621
|
help="Manage the task scheduler",
|
|
10553
|
-
description=
|
|
10622
|
+
description=(
|
|
10623
|
+
"Centralized daemon that dispatches tasks across projects on a shared cadence. "
|
|
10624
|
+
"Tasks in ~/.agentwire/scheduler.yaml are either ensure tasks (task: + session:) "
|
|
10625
|
+
"or workflow tasks (workflow: + inputs:) — the scheduler routes each automatically. "
|
|
10626
|
+
"See docs/scheduled-workloads.md."
|
|
10627
|
+
),
|
|
10554
10628
|
)
|
|
10555
10629
|
scheduler_subparsers = scheduler_parser.add_subparsers(dest="scheduler_command")
|
|
10556
10630
|
|
|
@@ -10580,6 +10654,8 @@ def main() -> int:
|
|
|
10580
10654
|
sched_run = scheduler_subparsers.add_parser("run", help="Force-run a task now")
|
|
10581
10655
|
sched_run.add_argument("name", help="Task name from board")
|
|
10582
10656
|
sched_run.add_argument("--json", action="store_true", help="Output JSON")
|
|
10657
|
+
sched_run.add_argument("--dry-run", action="store_true",
|
|
10658
|
+
help="For workflow tasks: print the execution plan without running")
|
|
10583
10659
|
sched_run.set_defaults(func=cmd_scheduler_run)
|
|
10584
10660
|
|
|
10585
10661
|
# scheduler enable <name>
|
|
@@ -74,16 +74,18 @@ class Schedule:
|
|
|
74
74
|
@dataclass
|
|
75
75
|
class SchedulerTask:
|
|
76
76
|
name: str
|
|
77
|
-
project: str
|
|
78
|
-
session: str
|
|
79
|
-
task: str
|
|
80
|
-
|
|
77
|
+
project: str = "" # ~/projects/foo (expanded at load time) — optional for workflow tasks
|
|
78
|
+
session: str = "" # session name for ensure (required iff task is set)
|
|
79
|
+
task: str = "" # task name in project's .agentwire.yml (mutually exclusive with workflow)
|
|
80
|
+
workflow: str = "" # workflow name or YAML path (mutually exclusive with task)
|
|
81
|
+
inputs: dict = field(default_factory=dict) # workflow inputs passed to run_workflow
|
|
82
|
+
schedule: Schedule = field(default_factory=Schedule) # REQUIRED (replaces interval)
|
|
81
83
|
enabled: bool = True
|
|
82
84
|
filler: bool = False # only runs in spare cycles
|
|
83
85
|
priority: int = 99 # task ordering (lower = higher priority)
|
|
84
|
-
type: str | None = None # session type override (e.g., claude-bypass)
|
|
85
|
-
roles: list[str] | None = None # role override
|
|
86
|
-
model: str | None = None # model override
|
|
86
|
+
type: str | None = None # session type override (e.g., claude-bypass) — ignored for workflow tasks
|
|
87
|
+
roles: list[str] | None = None # role override — ignored for workflow tasks
|
|
88
|
+
model: str | None = None # model override — ignored for workflow tasks
|
|
87
89
|
gate: dict | None = None # precondition gate (git_commit, git_diff, command)
|
|
88
90
|
max_runs: int | None = None # auto-disable after N successful dispatches
|
|
89
91
|
once: bool = False # shorthand for max_runs: 1
|
|
@@ -216,11 +218,28 @@ def load_board() -> Board:
|
|
|
216
218
|
|
|
217
219
|
schedule = _parse_schedule(t.get("schedule"))
|
|
218
220
|
|
|
221
|
+
raw_project = t.get("project", "")
|
|
222
|
+
project_path = str(Path(raw_project).expanduser()) if raw_project else ""
|
|
223
|
+
|
|
224
|
+
workflow = str(t.get("workflow") or "")
|
|
225
|
+
raw_inputs = t.get("inputs") or {}
|
|
226
|
+
if not isinstance(raw_inputs, dict):
|
|
227
|
+
raw_inputs = {}
|
|
228
|
+
|
|
229
|
+
if workflow:
|
|
230
|
+
session_name = str(t.get("session") or "")
|
|
231
|
+
task_name = str(t.get("task") or "")
|
|
232
|
+
else:
|
|
233
|
+
session_name = str(t.get("session") or name)
|
|
234
|
+
task_name = str(t.get("task") or name)
|
|
235
|
+
|
|
219
236
|
board.tasks[name] = SchedulerTask(
|
|
220
237
|
name=name,
|
|
221
|
-
project=
|
|
222
|
-
session=
|
|
223
|
-
task=
|
|
238
|
+
project=project_path,
|
|
239
|
+
session=session_name,
|
|
240
|
+
task=task_name,
|
|
241
|
+
workflow=workflow,
|
|
242
|
+
inputs=dict(raw_inputs),
|
|
224
243
|
schedule=schedule,
|
|
225
244
|
enabled=bool(t.get("enabled", True)),
|
|
226
245
|
filler=bool(t.get("filler", False)),
|
|
@@ -986,7 +1005,7 @@ def _auto_commit(task: SchedulerTask, task_name: str, status: str) -> None:
|
|
|
986
1005
|
|
|
987
1006
|
|
|
988
1007
|
def dispatch_task(board: Board, task_name: str) -> TaskState:
|
|
989
|
-
"""Run a task
|
|
1008
|
+
"""Run a task (workflow DAG or `agentwire ensure`) and return updated state.
|
|
990
1009
|
|
|
991
1010
|
Args:
|
|
992
1011
|
board: Current board (for reading task config).
|
|
@@ -998,16 +1017,8 @@ def dispatch_task(board: Board, task_name: str) -> TaskState:
|
|
|
998
1017
|
task = board.tasks[task_name]
|
|
999
1018
|
existing_state = board.state.get(task_name, TaskState())
|
|
1000
1019
|
|
|
1001
|
-
# Clear from gated set so gate can be re-evaluated after this run
|
|
1002
1020
|
_gated_tasks.discard(task_name)
|
|
1003
1021
|
|
|
1004
|
-
# Clean stale lock for this session before dispatching.
|
|
1005
|
-
# Stale locks (from crashed ensure processes) cause --skip-if-locked
|
|
1006
|
-
# to silently exit 0, making tasks appear to complete instantly.
|
|
1007
|
-
from .locking import remove_stale_lock
|
|
1008
|
-
remove_stale_lock(task.session)
|
|
1009
|
-
|
|
1010
|
-
# Set last_dispatch BEFORE running for restart safety
|
|
1011
1022
|
existing_state.last_dispatch = datetime.now(timezone.utc)
|
|
1012
1023
|
board.state[task_name] = existing_state
|
|
1013
1024
|
save_board(board)
|
|
@@ -1015,6 +1026,21 @@ def dispatch_task(board: Board, task_name: str) -> TaskState:
|
|
|
1015
1026
|
_log_event("task_started", task=task_name, session=task.session,
|
|
1016
1027
|
project=task.project, attempt=existing_state.run_count + 1)
|
|
1017
1028
|
|
|
1029
|
+
if task.workflow:
|
|
1030
|
+
return _dispatch_workflow_task(board, task, existing_state)
|
|
1031
|
+
return _dispatch_ensure_task(board, task, existing_state)
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
def _dispatch_ensure_task(board: Board, task: SchedulerTask, existing_state: TaskState) -> TaskState:
|
|
1035
|
+
"""Dispatch via `agentwire ensure` subprocess (tmux session path)."""
|
|
1036
|
+
task_name = task.name
|
|
1037
|
+
|
|
1038
|
+
# Clean stale lock for this session before dispatching.
|
|
1039
|
+
# Stale locks (from crashed ensure processes) cause --skip-if-locked
|
|
1040
|
+
# to silently exit 0, making tasks appear to complete instantly.
|
|
1041
|
+
from .locking import remove_stale_lock
|
|
1042
|
+
remove_stale_lock(task.session)
|
|
1043
|
+
|
|
1018
1044
|
has_overrides = bool(task.type or task.roles is not None or task.model)
|
|
1019
1045
|
|
|
1020
1046
|
if has_overrides:
|
|
@@ -1065,31 +1091,232 @@ def dispatch_task(board: Board, task_name: str) -> TaskState:
|
|
|
1065
1091
|
run_count=existing_state.run_count,
|
|
1066
1092
|
)
|
|
1067
1093
|
|
|
1068
|
-
# Parse summary from ensure output
|
|
1069
1094
|
summary, files_modified, blockers_list = _parse_ensure_summary(task, result)
|
|
1070
1095
|
|
|
1071
|
-
# Log completion event
|
|
1072
1096
|
_log_event("task_completed", task=task_name, session=task.session,
|
|
1073
1097
|
status=status, duration=duration, summary=summary,
|
|
1074
1098
|
files_modified=files_modified, blockers=blockers_list)
|
|
1075
1099
|
|
|
1076
|
-
# Notify portal
|
|
1077
1100
|
_notify_portal(task_name, status, duration, summary)
|
|
1078
1101
|
|
|
1079
|
-
# Auto-commit any changes the task made (each task = one revertable commit)
|
|
1080
1102
|
_auto_commit(task, task_name, status)
|
|
1081
1103
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1104
|
+
gate_commit = _capture_head(task.project)
|
|
1105
|
+
|
|
1106
|
+
new_run_count = existing_state.run_count + 1
|
|
1107
|
+
new_state = TaskState(
|
|
1108
|
+
last_run=datetime.now(timezone.utc),
|
|
1109
|
+
last_status=status,
|
|
1110
|
+
last_duration=duration,
|
|
1111
|
+
run_count=new_run_count,
|
|
1112
|
+
last_summary=summary,
|
|
1113
|
+
last_gate_commit=gate_commit,
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
if task.max_runs is not None and new_run_count >= task.max_runs:
|
|
1117
|
+
task.enabled = False
|
|
1118
|
+
board.state[task_name] = new_state
|
|
1119
|
+
save_board(board)
|
|
1120
|
+
_log_event("task_disabled", task=task_name, reason="max_runs_reached",
|
|
1121
|
+
run_count=new_run_count, max_runs=task.max_runs)
|
|
1122
|
+
|
|
1123
|
+
return new_state
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
def _capture_head(project: str) -> str:
|
|
1127
|
+
"""Return the current HEAD SHA of a git repo, or '' if unavailable."""
|
|
1128
|
+
if not project:
|
|
1129
|
+
return ""
|
|
1084
1130
|
try:
|
|
1085
|
-
|
|
1086
|
-
["git", "-C",
|
|
1131
|
+
result = subprocess.run(
|
|
1132
|
+
["git", "-C", project, "rev-parse", "HEAD"],
|
|
1087
1133
|
capture_output=True, text=True, timeout=_sched_config().git_timeout,
|
|
1088
1134
|
)
|
|
1089
|
-
if
|
|
1090
|
-
|
|
1135
|
+
if result.returncode == 0:
|
|
1136
|
+
return result.stdout.strip()
|
|
1091
1137
|
except Exception:
|
|
1092
1138
|
pass
|
|
1139
|
+
return ""
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
# Workflow dispatch ---------------------------------------------------------
|
|
1143
|
+
|
|
1144
|
+
_WORKFLOW_STATUS_TO_SCHED = {
|
|
1145
|
+
"success": "complete",
|
|
1146
|
+
"partial": "incomplete",
|
|
1147
|
+
"failure": "failed",
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
_SCHED_INPUT_VAR_RE = re.compile(r"\{\{\s*(\w+)\s*\}\}")
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
def _render_workflow_inputs(inputs: dict, task: SchedulerTask) -> dict:
|
|
1154
|
+
"""Expand `{{ task }}`, `{{ project }}`, `{{ session }}`, `{{ workflow }}` in string inputs.
|
|
1155
|
+
|
|
1156
|
+
Unknown variables pass through untouched so workflow nodes can reference their
|
|
1157
|
+
own Jinja variables without collision.
|
|
1158
|
+
"""
|
|
1159
|
+
vars_ = {
|
|
1160
|
+
"task": task.name,
|
|
1161
|
+
"project": task.project,
|
|
1162
|
+
"session": task.session,
|
|
1163
|
+
"workflow": task.workflow,
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
def _sub(value):
|
|
1167
|
+
if not isinstance(value, str):
|
|
1168
|
+
return value
|
|
1169
|
+
return _SCHED_INPUT_VAR_RE.sub(
|
|
1170
|
+
lambda m: vars_[m.group(1)] if m.group(1) in vars_ else m.group(0),
|
|
1171
|
+
value,
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
return {k: _sub(v) for k, v in (inputs or {}).items()}
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
def _parse_workflow_summary(run) -> tuple[str, list[str], list[str]]:
|
|
1178
|
+
"""Build a single-line summary + rough blockers list from a workflow run."""
|
|
1179
|
+
node_line = ", ".join(f"{r.node_id}={r.status}" for r in run.node_results) or "(no nodes)"
|
|
1180
|
+
head = f"{run.workflow} → {run.status}"
|
|
1181
|
+
last_text = ""
|
|
1182
|
+
for r in reversed(run.node_results):
|
|
1183
|
+
if r.final_text:
|
|
1184
|
+
first_line = r.final_text.strip().splitlines()[0] if r.final_text.strip() else ""
|
|
1185
|
+
last_text = first_line[:200]
|
|
1186
|
+
break
|
|
1187
|
+
summary = f"{head}: {node_line}" + (f" — {last_text}" if last_text else "")
|
|
1188
|
+
blockers: list[str] = []
|
|
1189
|
+
if run.error:
|
|
1190
|
+
blockers.append(run.error)
|
|
1191
|
+
for r in run.node_results:
|
|
1192
|
+
if r.status in ("failure", "timeout") and r.error:
|
|
1193
|
+
blockers.append(f"[{r.node_id}] {r.error}")
|
|
1194
|
+
return summary[:500], [], blockers
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
def _node_summary_records(run) -> list[dict]:
|
|
1198
|
+
"""Project workflow node results into compact dicts for event logs."""
|
|
1199
|
+
return [
|
|
1200
|
+
{
|
|
1201
|
+
"id": r.node_id,
|
|
1202
|
+
"status": r.status,
|
|
1203
|
+
"duration_ms": r.duration_ms,
|
|
1204
|
+
"attempts": r.attempts,
|
|
1205
|
+
"error": r.error,
|
|
1206
|
+
}
|
|
1207
|
+
for r in run.node_results
|
|
1208
|
+
]
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
def _workflow_failure_state(
|
|
1212
|
+
board: Board,
|
|
1213
|
+
task: SchedulerTask,
|
|
1214
|
+
existing_state: TaskState,
|
|
1215
|
+
duration: int,
|
|
1216
|
+
summary: str,
|
|
1217
|
+
error: str,
|
|
1218
|
+
workflow_name: str = "",
|
|
1219
|
+
run_id: str = "",
|
|
1220
|
+
) -> TaskState:
|
|
1221
|
+
"""Persist a failure outcome when the workflow couldn't be loaded or never started."""
|
|
1222
|
+
status = "failed"
|
|
1223
|
+
_log_event(
|
|
1224
|
+
"task_completed",
|
|
1225
|
+
task=task.name,
|
|
1226
|
+
session=task.session or "",
|
|
1227
|
+
status=status,
|
|
1228
|
+
duration=duration,
|
|
1229
|
+
summary=summary,
|
|
1230
|
+
files_modified=[],
|
|
1231
|
+
blockers=[error] if error else [],
|
|
1232
|
+
workflow=workflow_name or task.workflow,
|
|
1233
|
+
run_id=run_id,
|
|
1234
|
+
nodes=[],
|
|
1235
|
+
)
|
|
1236
|
+
_notify_portal(task.name, status, duration, summary)
|
|
1237
|
+
|
|
1238
|
+
new_run_count = existing_state.run_count + 1
|
|
1239
|
+
new_state = TaskState(
|
|
1240
|
+
last_run=datetime.now(timezone.utc),
|
|
1241
|
+
last_status=status,
|
|
1242
|
+
last_duration=duration,
|
|
1243
|
+
run_count=new_run_count,
|
|
1244
|
+
last_summary=summary,
|
|
1245
|
+
last_gate_commit=_capture_head(task.project),
|
|
1246
|
+
)
|
|
1247
|
+
|
|
1248
|
+
if task.max_runs is not None and new_run_count >= task.max_runs:
|
|
1249
|
+
task.enabled = False
|
|
1250
|
+
board.state[task.name] = new_state
|
|
1251
|
+
save_board(board)
|
|
1252
|
+
_log_event("task_disabled", task=task.name, reason="max_runs_reached",
|
|
1253
|
+
run_count=new_run_count, max_runs=task.max_runs)
|
|
1254
|
+
|
|
1255
|
+
return new_state
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def _dispatch_workflow_task(board: Board, task: SchedulerTask, existing_state: TaskState) -> TaskState:
|
|
1259
|
+
"""Dispatch a workflow task in-process via agentwire.workflows.runner.run_workflow."""
|
|
1260
|
+
from agentwire.workflows.cli import RUNS_DIR
|
|
1261
|
+
from agentwire.workflows.definitions import resolve_workflow
|
|
1262
|
+
from agentwire.workflows.runner import run_workflow
|
|
1263
|
+
|
|
1264
|
+
start_time = time.time()
|
|
1265
|
+
|
|
1266
|
+
try:
|
|
1267
|
+
wf = resolve_workflow(task.workflow)
|
|
1268
|
+
except FileNotFoundError:
|
|
1269
|
+
duration = int(time.time() - start_time)
|
|
1270
|
+
return _workflow_failure_state(
|
|
1271
|
+
board, task, existing_state, duration,
|
|
1272
|
+
summary=f"{task.workflow} → failed: workflow not found",
|
|
1273
|
+
error=f"workflow '{task.workflow}' not found",
|
|
1274
|
+
)
|
|
1275
|
+
except Exception as e:
|
|
1276
|
+
duration = int(time.time() - start_time)
|
|
1277
|
+
return _workflow_failure_state(
|
|
1278
|
+
board, task, existing_state, duration,
|
|
1279
|
+
summary=f"{task.workflow} → failed: {e}",
|
|
1280
|
+
error=f"workflow load error: {e}",
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
rendered_inputs = _render_workflow_inputs(task.inputs, task)
|
|
1284
|
+
|
|
1285
|
+
try:
|
|
1286
|
+
run = run_workflow(wf, runs_dir=RUNS_DIR, inputs=rendered_inputs, dry_run=False)
|
|
1287
|
+
except Exception as e:
|
|
1288
|
+
duration = int(time.time() - start_time)
|
|
1289
|
+
return _workflow_failure_state(
|
|
1290
|
+
board, task, existing_state, duration,
|
|
1291
|
+
summary=f"{task.workflow} → failed: runner crashed",
|
|
1292
|
+
error=f"runner crashed: {e}",
|
|
1293
|
+
workflow_name=wf.name,
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
duration = int(time.time() - start_time)
|
|
1297
|
+
status = _WORKFLOW_STATUS_TO_SCHED.get(run.status, "failed")
|
|
1298
|
+
summary, files_modified, blockers = _parse_workflow_summary(run)
|
|
1299
|
+
|
|
1300
|
+
_log_event(
|
|
1301
|
+
"task_completed",
|
|
1302
|
+
task=task.name,
|
|
1303
|
+
session=task.session or "",
|
|
1304
|
+
status=status,
|
|
1305
|
+
duration=duration,
|
|
1306
|
+
summary=summary,
|
|
1307
|
+
files_modified=files_modified,
|
|
1308
|
+
blockers=blockers,
|
|
1309
|
+
workflow=wf.name,
|
|
1310
|
+
run_id=run.run_id,
|
|
1311
|
+
nodes=_node_summary_records(run),
|
|
1312
|
+
)
|
|
1313
|
+
|
|
1314
|
+
_notify_portal(task.name, status, duration, summary)
|
|
1315
|
+
|
|
1316
|
+
if task.project:
|
|
1317
|
+
_auto_commit(task, task.name, status)
|
|
1318
|
+
|
|
1319
|
+
gate_commit = _capture_head(task.project)
|
|
1093
1320
|
|
|
1094
1321
|
new_run_count = existing_state.run_count + 1
|
|
1095
1322
|
new_state = TaskState(
|
|
@@ -1101,12 +1328,11 @@ def dispatch_task(board: Board, task_name: str) -> TaskState:
|
|
|
1101
1328
|
last_gate_commit=gate_commit,
|
|
1102
1329
|
)
|
|
1103
1330
|
|
|
1104
|
-
# Auto-disable if max_runs reached
|
|
1105
1331
|
if task.max_runs is not None and new_run_count >= task.max_runs:
|
|
1106
1332
|
task.enabled = False
|
|
1107
|
-
board.state[
|
|
1333
|
+
board.state[task.name] = new_state
|
|
1108
1334
|
save_board(board)
|
|
1109
|
-
_log_event("task_disabled", task=
|
|
1335
|
+
_log_event("task_disabled", task=task.name, reason="max_runs_reached",
|
|
1110
1336
|
run_count=new_run_count, max_runs=task.max_runs)
|
|
1111
1337
|
|
|
1112
1338
|
return new_state
|
|
@@ -1398,6 +1624,45 @@ def read_live_state() -> dict | None:
|
|
|
1398
1624
|
return None
|
|
1399
1625
|
|
|
1400
1626
|
|
|
1627
|
+
def _validate_task_payload(name: str, task: SchedulerTask) -> list[str]:
|
|
1628
|
+
"""Validate the task vs workflow payload shape for a single task."""
|
|
1629
|
+
errors: list[str] = []
|
|
1630
|
+
|
|
1631
|
+
has_workflow = bool(task.workflow)
|
|
1632
|
+
has_task = bool(task.task)
|
|
1633
|
+
|
|
1634
|
+
if has_workflow and has_task:
|
|
1635
|
+
errors.append(f"{name}: cannot set both 'task' and 'workflow'")
|
|
1636
|
+
if not has_workflow and not has_task:
|
|
1637
|
+
errors.append(f"{name}: must set either 'task' or 'workflow'")
|
|
1638
|
+
|
|
1639
|
+
if task.inputs and not has_workflow:
|
|
1640
|
+
errors.append(f"{name}: 'inputs' only valid with 'workflow'")
|
|
1641
|
+
|
|
1642
|
+
if has_workflow:
|
|
1643
|
+
try:
|
|
1644
|
+
from agentwire.workflows.definitions import resolve_workflow
|
|
1645
|
+
wf = resolve_workflow(task.workflow)
|
|
1646
|
+
except FileNotFoundError:
|
|
1647
|
+
errors.append(f"{name}: workflow '{task.workflow}' not found")
|
|
1648
|
+
return errors
|
|
1649
|
+
except Exception as e:
|
|
1650
|
+
errors.append(f"{name}: workflow '{task.workflow}' could not be loaded: {e}")
|
|
1651
|
+
return errors
|
|
1652
|
+
wf_errors = wf.validate()
|
|
1653
|
+
for err in wf_errors:
|
|
1654
|
+
errors.append(f"{name}: workflow: {err}")
|
|
1655
|
+
|
|
1656
|
+
gate = task.gate or {}
|
|
1657
|
+
if isinstance(gate, dict):
|
|
1658
|
+
needs_project = bool(gate.get("git_commit") or gate.get("git_diff"))
|
|
1659
|
+
if needs_project and not task.project:
|
|
1660
|
+
gate_type = "git_commit" if gate.get("git_commit") else "git_diff"
|
|
1661
|
+
errors.append(f"{name}: gate {gate_type} requires 'project' path")
|
|
1662
|
+
|
|
1663
|
+
return errors
|
|
1664
|
+
|
|
1665
|
+
|
|
1401
1666
|
def validate_board(board: Board) -> list[str]:
|
|
1402
1667
|
"""Validate board configuration for errors.
|
|
1403
1668
|
|
|
@@ -1426,6 +1691,8 @@ def validate_board(board: Board) -> list[str]:
|
|
|
1426
1691
|
if sched.after and sched.after in board.tasks and not board.tasks[sched.after].enabled:
|
|
1427
1692
|
errors.append(f"{name}: warning: dependency '{sched.after}' is disabled")
|
|
1428
1693
|
|
|
1694
|
+
errors.extend(_validate_task_payload(name, task))
|
|
1695
|
+
|
|
1429
1696
|
# Circular dependency detection via DFS
|
|
1430
1697
|
def _has_cycle(start: str, visited: set, path: set) -> bool:
|
|
1431
1698
|
if start in path:
|
|
@@ -8,12 +8,32 @@ Reliable headless task execution for overnight and automated agent workflows.
|
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Three paths for running scheduled work, picked per task:
|
|
12
12
|
|
|
13
|
-
1. **`agentwire ensure
|
|
14
|
-
2. **
|
|
13
|
+
1. **`agentwire ensure` tasks** — Reliable session management + task execution with lifecycle hooks. A full Claude Code session runs a single prompt from `.agentwire.yml`. Best for multi-step agent work that needs its own branch / PR / MCP tools.
|
|
14
|
+
2. **Pi workflow tasks** — A YAML DAG of `pi -p --mode json` invocations runs in-process. No tmux session, no project required. Best for deterministic pipelines where each step has clear inputs/outputs and you want cheap per-node retries.
|
|
15
|
+
3. **Tasks in `.agentwire.yml`** — Named tasks with pre/prompt/post phases and branch management — the substrate for path #1.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
All three are orchestrated by `~/.agentwire/scheduler.yaml` and the AgentWire scheduler daemon. A single board can mix ensure tasks and workflow tasks freely.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Choosing ensure vs workflow
|
|
22
|
+
|
|
23
|
+
| | ensure task | workflow task |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Schedule field | `task: <name>` + `session:` | `workflow: <name-or-path>` + `inputs:` |
|
|
26
|
+
| Dispatch | `agentwire ensure` subprocess → tmux session → Claude Code | `run_workflow()` in-process → pi subprocesses per node |
|
|
27
|
+
| Model family | Claude (subscription via session type) | Z.AI glm-5 / flash-tier pi (per-node choice) |
|
|
28
|
+
| Session needed | yes | no |
|
|
29
|
+
| Project needed | yes (for branch mgmt) | optional (only for git gates / auto-commit) |
|
|
30
|
+
| Multi-step logic | inside one Claude prompt | first-class DAG with retries, branches, outputs |
|
|
31
|
+
| Dry-run | no | `scheduler run <name> --dry-run` prints the plan |
|
|
32
|
+
| Cost profile | subscription-covered | Z.AI credits (or free flash tier) |
|
|
33
|
+
|
|
34
|
+
A task must set exactly one of `task:` or `workflow:`. Everything else (gates, priority, `max_runs`, `once`, `cooldown`, `not_before`/`not_after`) applies to both.
|
|
35
|
+
|
|
36
|
+
Full workflow task reference: [`docs/workflows.md#scheduler-integration`](workflows.md#scheduler-integration).
|
|
17
37
|
|
|
18
38
|
---
|
|
19
39
|
|