agentirc-cli 0.10.7__tar.gz → 0.11.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.
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/CHANGELOG.md +15 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/PKG-INFO +1 -1
- agentirc_cli-0.11.0/agentirc/__init__.py +1 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/cli.py +177 -32
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/config.py +4 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/daemon.py +100 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/irc_transport.py +5 -1
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/observer.py +23 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/cli.md +46 -1
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/pyproject.toml +1 -1
- agentirc_cli-0.11.0/tests/test_daemon_ipc.py +81 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/uv.lock +1 -1
- agentirc_cli-0.10.7/agentirc/__init__.py +0 -1
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/.gitignore +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/.pr_agent.toml +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/CLAUDE.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/CNAME +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/Gemfile +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/Gemfile.lock +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/LICENSE +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/README.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/_config.yml +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/__main__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/ipc.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/socket_server.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/supervisor.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/claude/webhook.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/config.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/daemon.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/ipc.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/irc_transport.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/socket_server.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/supervisor.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/codex/webhook.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/config.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/daemon.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/ipc.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/irc_transport.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/copilot/webhook.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/agent_runner.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/config.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/daemon.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/ipc.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/irc_transport.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/message_buffer.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/skill/SKILL.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/skill/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/skill/irc_client.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/socket_server.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/supervisor.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/clients/opencode/webhook.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/pidfile.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/protocol/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/protocol/commands.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/protocol/extensions/federation.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/protocol/extensions/history.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/protocol/message.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/protocol/protocol-index.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/protocol/replies.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/__main__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/channel.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/client.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/config.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/ircd.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/remote_client.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/server_link.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/skill.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/skills/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/agentirc/server/skills/history.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/agent-client.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/agent-harness-spec.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/ci.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/opencode/configuration.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/opencode/context-management.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/opencode/irc-tools.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/opencode/overview.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/opencode/setup.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/opencode/supervisor.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/clients/opencode/webhooks.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/codex-backend.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/copilot-backend.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/design.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/docs-site.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/getting-started.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/grow-your-agent.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/layer1-core-irc.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/layer2-attention.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/layer3-skills.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/layer4-federation.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/layer5-agent-harness.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/opencode-backend.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/publishing.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/server-architecture.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases/10-grow-your-agent.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/docs/use-cases-index.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/index.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/daemon.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/irc_transport.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/plugins/codex/skills/agentirc-irc/SKILL.md +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/__init__.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/conftest.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_channel.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_connection.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_daemon.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_discovery.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_federation.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_history.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_ipc.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_mentions.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_message.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_messaging.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_modes.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_opencode_daemon.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_skill_client.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_skills.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_socket_server.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_supervisor.py +0 -0
- {agentirc_cli-0.10.7 → agentirc_cli-0.11.0}/tests/test_webhook.py +0 -0
|
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## [0.11.0] - 2026-03-28
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- agentirc send command for sending messages to channels and agents
|
|
13
|
+
- agentirc status --full flag and per-agent detailed view
|
|
14
|
+
- agentirc sleep/wake commands with configurable schedule (default 23:00-08:00)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Extended IPC protocol with status, pause, and resume handlers
|
|
20
|
+
- Added sleep_start/sleep_end config fields to DaemonConfig
|
|
21
|
+
|
|
7
22
|
## [0.10.7] - 2026-03-28
|
|
8
23
|
|
|
9
24
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.11.0"
|
|
@@ -5,10 +5,13 @@ Subcommands:
|
|
|
5
5
|
agentirc init Register an agent for the current directory
|
|
6
6
|
agentirc start [nick] [--all] Start agent daemon(s)
|
|
7
7
|
agentirc stop [nick] [--all] Stop agent daemon(s)
|
|
8
|
-
agentirc status
|
|
8
|
+
agentirc status [nick] [--full] List running agents (--full queries activity)
|
|
9
|
+
agentirc send <target> <message> Send a message to a channel or agent
|
|
9
10
|
agentirc read <channel> Read recent channel messages
|
|
10
11
|
agentirc who <channel> List channel members
|
|
11
12
|
agentirc channels List active channels
|
|
13
|
+
agentirc sleep [nick] [--all] Pause agent(s) — stay connected but idle
|
|
14
|
+
agentirc wake [nick] [--all] Resume paused agent(s)
|
|
12
15
|
"""
|
|
13
16
|
from __future__ import annotations
|
|
14
17
|
|
|
@@ -115,6 +118,8 @@ def main() -> None:
|
|
|
115
118
|
|
|
116
119
|
# -- status subcommand -------------------------------------------------
|
|
117
120
|
status_parser = sub.add_parser("status", help="List running agents")
|
|
121
|
+
status_parser.add_argument("nick", nargs="?", help="Show detailed status for a specific agent")
|
|
122
|
+
status_parser.add_argument("--full", action="store_true", help="Query agents for activity status")
|
|
118
123
|
status_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
|
|
119
124
|
|
|
120
125
|
# -- read subcommand ---------------------------------------------------
|
|
@@ -128,10 +133,28 @@ def main() -> None:
|
|
|
128
133
|
who_parser.add_argument("channel", help="Channel or nick target")
|
|
129
134
|
who_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
|
|
130
135
|
|
|
136
|
+
# -- send subcommand ---------------------------------------------------
|
|
137
|
+
send_parser = sub.add_parser("send", help="Send a message to a channel or agent")
|
|
138
|
+
send_parser.add_argument("target", help="Channel (e.g. #general) or agent nick")
|
|
139
|
+
send_parser.add_argument("message", help="Message text to send")
|
|
140
|
+
send_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
|
|
141
|
+
|
|
131
142
|
# -- channels subcommand -----------------------------------------------
|
|
132
143
|
channels_parser = sub.add_parser("channels", help="List active channels")
|
|
133
144
|
channels_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
|
|
134
145
|
|
|
146
|
+
# -- sleep subcommand --------------------------------------------------
|
|
147
|
+
sleep_parser = sub.add_parser("sleep", help="Pause agent(s) — stay connected but idle")
|
|
148
|
+
sleep_parser.add_argument("nick", nargs="?", help="Agent nick to pause")
|
|
149
|
+
sleep_parser.add_argument("--all", action="store_true", help="Pause all agents")
|
|
150
|
+
sleep_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
|
|
151
|
+
|
|
152
|
+
# -- wake subcommand ---------------------------------------------------
|
|
153
|
+
wake_parser = sub.add_parser("wake", help="Resume paused agent(s)")
|
|
154
|
+
wake_parser.add_argument("nick", nargs="?", help="Agent nick to resume")
|
|
155
|
+
wake_parser.add_argument("--all", action="store_true", help="Resume all agents")
|
|
156
|
+
wake_parser.add_argument("--config", default=DEFAULT_CONFIG, help="Config file path")
|
|
157
|
+
|
|
135
158
|
# -- skills subcommand -------------------------------------------------
|
|
136
159
|
skills_parser = sub.add_parser("skills", help="Install IRC skills for AI agents")
|
|
137
160
|
skills_sub = skills_parser.add_subparsers(dest="skills_command")
|
|
@@ -159,9 +182,12 @@ def main() -> None:
|
|
|
159
182
|
"start": _cmd_start,
|
|
160
183
|
"stop": _cmd_stop,
|
|
161
184
|
"status": _cmd_status,
|
|
185
|
+
"send": _cmd_send,
|
|
162
186
|
"read": _cmd_read,
|
|
163
187
|
"who": _cmd_who,
|
|
164
188
|
"channels": _cmd_channels,
|
|
189
|
+
"sleep": _cmd_sleep,
|
|
190
|
+
"wake": _cmd_wake,
|
|
165
191
|
"skills": _cmd_skills,
|
|
166
192
|
}
|
|
167
193
|
handler = dispatch.get(args.command)
|
|
@@ -652,21 +678,33 @@ def _stop_agent(nick: str) -> None:
|
|
|
652
678
|
print(f"Agent '{nick}' killed")
|
|
653
679
|
|
|
654
680
|
|
|
655
|
-
async def
|
|
656
|
-
"""Send
|
|
681
|
+
async def _ipc_request(socket_path: str, msg_type: str, **kwargs) -> dict | None:
|
|
682
|
+
"""Send an IPC request via Unix socket and return the response."""
|
|
657
683
|
from agentirc.clients.claude.ipc import decode_message, encode_message, make_request
|
|
658
684
|
|
|
659
|
-
reader, writer = await asyncio.wait_for(
|
|
660
|
-
asyncio.open_unix_connection(socket_path),
|
|
661
|
-
timeout=3.0,
|
|
662
|
-
)
|
|
663
685
|
try:
|
|
664
|
-
|
|
686
|
+
reader, writer = await asyncio.wait_for(
|
|
687
|
+
asyncio.open_unix_connection(socket_path),
|
|
688
|
+
timeout=3.0,
|
|
689
|
+
)
|
|
690
|
+
except (ConnectionRefusedError, FileNotFoundError, OSError):
|
|
691
|
+
return None
|
|
692
|
+
try:
|
|
693
|
+
req = make_request(msg_type, **kwargs)
|
|
665
694
|
writer.write(encode_message(req))
|
|
666
695
|
await writer.drain()
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
696
|
+
# Read lines until we get a response (skip whispers)
|
|
697
|
+
deadline = asyncio.get_event_loop().time() + 3.0
|
|
698
|
+
while True:
|
|
699
|
+
remaining = deadline - asyncio.get_event_loop().time()
|
|
700
|
+
if remaining <= 0:
|
|
701
|
+
return None
|
|
702
|
+
data = await asyncio.wait_for(reader.readline(), timeout=remaining)
|
|
703
|
+
msg = decode_message(data)
|
|
704
|
+
if msg and msg.get("type") == "response":
|
|
705
|
+
return msg
|
|
706
|
+
except (asyncio.TimeoutError, ConnectionError, BrokenPipeError, OSError):
|
|
707
|
+
return None
|
|
670
708
|
finally:
|
|
671
709
|
writer.close()
|
|
672
710
|
try:
|
|
@@ -675,10 +713,37 @@ async def _ipc_shutdown(socket_path: str) -> bool:
|
|
|
675
713
|
pass
|
|
676
714
|
|
|
677
715
|
|
|
716
|
+
async def _ipc_shutdown(socket_path: str) -> bool:
|
|
717
|
+
"""Send a shutdown command via Unix socket IPC."""
|
|
718
|
+
resp = await _ipc_request(socket_path, "shutdown")
|
|
719
|
+
return resp is not None and resp.get("ok", False)
|
|
720
|
+
|
|
721
|
+
|
|
678
722
|
# -----------------------------------------------------------------------
|
|
679
723
|
# Agent status
|
|
680
724
|
# -----------------------------------------------------------------------
|
|
681
725
|
|
|
726
|
+
def _agent_socket_path(nick: str) -> str:
|
|
727
|
+
return os.path.join(
|
|
728
|
+
os.environ.get("XDG_RUNTIME_DIR", "/tmp"),
|
|
729
|
+
f"agentirc-{nick}.sock",
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
def _agent_process_status(agent) -> tuple[str, int | None]:
|
|
734
|
+
"""Return (status_str, pid_or_none) for an agent."""
|
|
735
|
+
pid_name = f"agent-{agent.nick}"
|
|
736
|
+
pid = read_pid(pid_name)
|
|
737
|
+
if pid and is_process_alive(pid):
|
|
738
|
+
socket_path = _agent_socket_path(agent.nick)
|
|
739
|
+
if os.path.exists(socket_path):
|
|
740
|
+
return "running", pid
|
|
741
|
+
return "starting", pid
|
|
742
|
+
if pid:
|
|
743
|
+
remove_pid(pid_name)
|
|
744
|
+
return "stopped", None
|
|
745
|
+
|
|
746
|
+
|
|
682
747
|
def _cmd_status(args: argparse.Namespace) -> None:
|
|
683
748
|
config = load_config_or_default(args.config)
|
|
684
749
|
|
|
@@ -686,30 +751,64 @@ def _cmd_status(args: argparse.Namespace) -> None:
|
|
|
686
751
|
print("No agents configured")
|
|
687
752
|
return
|
|
688
753
|
|
|
689
|
-
|
|
690
|
-
|
|
754
|
+
# Single agent detailed view
|
|
755
|
+
if args.nick:
|
|
756
|
+
agent = None
|
|
757
|
+
for a in config.agents:
|
|
758
|
+
if a.nick == args.nick:
|
|
759
|
+
agent = a
|
|
760
|
+
break
|
|
761
|
+
if not agent:
|
|
762
|
+
print(f"Agent '{args.nick}' not found in config", file=sys.stderr)
|
|
763
|
+
sys.exit(1)
|
|
764
|
+
|
|
765
|
+
status, pid = _agent_process_status(agent)
|
|
766
|
+
print(agent.nick)
|
|
767
|
+
print(f" Status: {status}")
|
|
768
|
+
print(f" PID: {pid or '-'}")
|
|
769
|
+
|
|
770
|
+
# Query IPC for activity if running
|
|
771
|
+
if status == "running":
|
|
772
|
+
resp = asyncio.run(_ipc_request(_agent_socket_path(agent.nick), "status"))
|
|
773
|
+
if resp and resp.get("ok"):
|
|
774
|
+
data = resp.get("data", {})
|
|
775
|
+
print(f" Activity: {data.get('activity', 'unknown')}")
|
|
776
|
+
print(f" Turns: {data.get('turn_count', 0)}")
|
|
777
|
+
print(f" Paused: {'yes' if data.get('paused') else 'no'}")
|
|
778
|
+
else:
|
|
779
|
+
print(f" Activity: -")
|
|
780
|
+
|
|
781
|
+
channels = agent.channels if isinstance(agent.channels, list) else []
|
|
782
|
+
print(f" Directory: {agent.directory}")
|
|
783
|
+
print(f" Backend: {agent.agent}")
|
|
784
|
+
print(f" Channels: {', '.join(channels)}")
|
|
785
|
+
print(f" Model: {agent.model}")
|
|
786
|
+
print(f" Config: {args.config}")
|
|
787
|
+
return
|
|
788
|
+
|
|
789
|
+
# All agents view
|
|
790
|
+
show_activity = args.full
|
|
791
|
+
|
|
792
|
+
if show_activity:
|
|
793
|
+
print(f"{'NICK':<30} {'STATUS':<12} {'PID':<10} {'ACTIVITY':<10}")
|
|
794
|
+
print("-" * 62)
|
|
795
|
+
else:
|
|
796
|
+
print(f"{'NICK':<30} {'STATUS':<12} {'PID':<10}")
|
|
797
|
+
print("-" * 52)
|
|
691
798
|
|
|
692
799
|
for agent in config.agents:
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
)
|
|
703
|
-
if os.path.exists(socket_path):
|
|
704
|
-
status = "running"
|
|
705
|
-
else:
|
|
706
|
-
status = "starting"
|
|
707
|
-
print(f"{agent.nick:<30} {status:<12} {pid:<10}")
|
|
708
|
-
elif pid:
|
|
709
|
-
remove_pid(pid_name)
|
|
710
|
-
print(f"{agent.nick:<30} {'stopped':<12} {'-':<10}")
|
|
800
|
+
status, pid = _agent_process_status(agent)
|
|
801
|
+
activity = "-"
|
|
802
|
+
|
|
803
|
+
if show_activity and status == "running":
|
|
804
|
+
resp = asyncio.run(_ipc_request(_agent_socket_path(agent.nick), "status"))
|
|
805
|
+
if resp and resp.get("ok"):
|
|
806
|
+
activity = resp.get("data", {}).get("activity", "unknown")
|
|
807
|
+
|
|
808
|
+
if show_activity:
|
|
809
|
+
print(f"{agent.nick:<30} {status:<12} {str(pid or '-'):<10} {activity:<10}")
|
|
711
810
|
else:
|
|
712
|
-
print(f"{agent.nick:<30} {
|
|
811
|
+
print(f"{agent.nick:<30} {status:<12} {str(pid or '-'):<10}")
|
|
713
812
|
|
|
714
813
|
|
|
715
814
|
# -----------------------------------------------------------------------
|
|
@@ -728,6 +827,52 @@ def _get_observer(config_path: str):
|
|
|
728
827
|
)
|
|
729
828
|
|
|
730
829
|
|
|
830
|
+
def _ipc_to_agents(args: argparse.Namespace, msg_type: str, action_verb: str) -> None:
|
|
831
|
+
"""Send an IPC message (pause/resume) to one or all agents."""
|
|
832
|
+
config = load_config_or_default(args.config)
|
|
833
|
+
|
|
834
|
+
if args.nick and args.all:
|
|
835
|
+
print(f"Cannot specify both nick and --all", file=sys.stderr)
|
|
836
|
+
sys.exit(1)
|
|
837
|
+
|
|
838
|
+
if not args.nick and not args.all:
|
|
839
|
+
print(f"Usage: agentirc {action_verb} <nick> or --all", file=sys.stderr)
|
|
840
|
+
sys.exit(1)
|
|
841
|
+
|
|
842
|
+
targets = config.agents if args.all else []
|
|
843
|
+
if args.nick:
|
|
844
|
+
for a in config.agents:
|
|
845
|
+
if a.nick == args.nick:
|
|
846
|
+
targets = [a]
|
|
847
|
+
break
|
|
848
|
+
else:
|
|
849
|
+
print(f"Agent '{args.nick}' not found in config", file=sys.stderr)
|
|
850
|
+
sys.exit(1)
|
|
851
|
+
|
|
852
|
+
for agent in targets:
|
|
853
|
+
socket_path = _agent_socket_path(agent.nick)
|
|
854
|
+
resp = asyncio.run(_ipc_request(socket_path, msg_type))
|
|
855
|
+
if resp and resp.get("ok"):
|
|
856
|
+
print(f"{agent.nick}: {action_verb}")
|
|
857
|
+
else:
|
|
858
|
+
print(f"{agent.nick}: failed (not running?)", file=sys.stderr)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def _cmd_sleep(args: argparse.Namespace) -> None:
|
|
862
|
+
_ipc_to_agents(args, "pause", "paused")
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def _cmd_wake(args: argparse.Namespace) -> None:
|
|
866
|
+
_ipc_to_agents(args, "resume", "resumed")
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def _cmd_send(args: argparse.Namespace) -> None:
|
|
870
|
+
observer = _get_observer(args.config)
|
|
871
|
+
target = args.target if args.target.startswith("#") else args.target
|
|
872
|
+
asyncio.run(observer.send_message(target, args.message))
|
|
873
|
+
print(f"Sent to {target}")
|
|
874
|
+
|
|
875
|
+
|
|
731
876
|
def _cmd_read(args: argparse.Namespace) -> None:
|
|
732
877
|
observer = _get_observer(args.config)
|
|
733
878
|
channel = args.channel if args.channel.startswith("#") else f"#{args.channel}"
|
|
@@ -56,6 +56,8 @@ class DaemonConfig:
|
|
|
56
56
|
supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
|
|
57
57
|
webhooks: WebhookConfig = field(default_factory=WebhookConfig)
|
|
58
58
|
buffer_size: int = 500
|
|
59
|
+
sleep_start: str = "23:00"
|
|
60
|
+
sleep_end: str = "08:00"
|
|
59
61
|
agents: list[AgentConfig] = field(default_factory=list)
|
|
60
62
|
|
|
61
63
|
def get_agent(self, nick: str) -> AgentConfig | None:
|
|
@@ -84,6 +86,8 @@ def load_config(path: str | Path) -> DaemonConfig:
|
|
|
84
86
|
supervisor=supervisor,
|
|
85
87
|
webhooks=webhooks,
|
|
86
88
|
buffer_size=raw.get("buffer_size", 500),
|
|
89
|
+
sleep_start=raw.get("sleep_start", "23:00"),
|
|
90
|
+
sleep_end=raw.get("sleep_end", "08:00"),
|
|
87
91
|
agents=agents,
|
|
88
92
|
)
|
|
89
93
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import datetime
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
import time
|
|
@@ -53,6 +54,10 @@ class AgentDaemon:
|
|
|
53
54
|
self._crash_times: list[float] = []
|
|
54
55
|
self._circuit_open = False
|
|
55
56
|
|
|
57
|
+
# Pause/sleep state
|
|
58
|
+
self._paused: bool = False
|
|
59
|
+
self._last_activation: float | None = None
|
|
60
|
+
|
|
56
61
|
# Graceful shutdown
|
|
57
62
|
self._stop_event: asyncio.Event | None = None
|
|
58
63
|
self._pid_name: str = ""
|
|
@@ -112,12 +117,23 @@ class AgentDaemon:
|
|
|
112
117
|
if not self.skip_claude:
|
|
113
118
|
await self._start_agent_runner()
|
|
114
119
|
|
|
120
|
+
# 7. Sleep scheduler background task
|
|
121
|
+
self._sleep_task = asyncio.create_task(self._sleep_scheduler())
|
|
122
|
+
|
|
115
123
|
logger.info(
|
|
116
124
|
"AgentDaemon started for %s (socket=%s)", self.agent.nick, self._socket_path
|
|
117
125
|
)
|
|
118
126
|
|
|
119
127
|
async def stop(self) -> None:
|
|
120
128
|
"""Cleanly shut down all components."""
|
|
129
|
+
if hasattr(self, "_sleep_task") and self._sleep_task:
|
|
130
|
+
self._sleep_task.cancel()
|
|
131
|
+
try:
|
|
132
|
+
await self._sleep_task
|
|
133
|
+
except asyncio.CancelledError:
|
|
134
|
+
pass
|
|
135
|
+
self._sleep_task = None
|
|
136
|
+
|
|
121
137
|
if self._agent_runner is not None:
|
|
122
138
|
await self._agent_runner.stop()
|
|
123
139
|
self._agent_runner = None
|
|
@@ -136,6 +152,54 @@ class AgentDaemon:
|
|
|
136
152
|
|
|
137
153
|
logger.info("AgentDaemon stopped for %s", self.agent.nick)
|
|
138
154
|
|
|
155
|
+
def _parse_sleep_schedule(self) -> tuple[int, int] | None:
|
|
156
|
+
"""Parse sleep_start/sleep_end into minutes. Returns None if invalid."""
|
|
157
|
+
try:
|
|
158
|
+
sh, sm = (int(x) for x in self.config.sleep_start.split(":"))
|
|
159
|
+
wh, wm = (int(x) for x in self.config.sleep_end.split(":"))
|
|
160
|
+
if not (0 <= sh <= 23 and 0 <= sm <= 59 and 0 <= wh <= 23 and 0 <= wm <= 59):
|
|
161
|
+
raise ValueError("hours/minutes out of range")
|
|
162
|
+
return (sh * 60 + sm, wh * 60 + wm)
|
|
163
|
+
except (ValueError, AttributeError):
|
|
164
|
+
logger.warning(
|
|
165
|
+
"Invalid sleep schedule '%s'-'%s' for %s — scheduler disabled",
|
|
166
|
+
getattr(self.config, "sleep_start", None),
|
|
167
|
+
getattr(self.config, "sleep_end", None),
|
|
168
|
+
self.agent.nick,
|
|
169
|
+
)
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
async def _sleep_scheduler(self) -> None:
|
|
173
|
+
"""Background task that auto-pauses/resumes based on sleep schedule."""
|
|
174
|
+
schedule = self._parse_sleep_schedule()
|
|
175
|
+
if schedule is None:
|
|
176
|
+
return
|
|
177
|
+
sleep_minutes, wake_minutes = schedule
|
|
178
|
+
|
|
179
|
+
while True:
|
|
180
|
+
try:
|
|
181
|
+
await asyncio.sleep(60) # Check every minute
|
|
182
|
+
now = datetime.datetime.now()
|
|
183
|
+
current_minutes = now.hour * 60 + now.minute
|
|
184
|
+
|
|
185
|
+
if sleep_minutes > wake_minutes:
|
|
186
|
+
# Overnight: e.g., 23:00-08:00
|
|
187
|
+
should_sleep = current_minutes >= sleep_minutes or current_minutes < wake_minutes
|
|
188
|
+
else:
|
|
189
|
+
# Same day: e.g., 13:00-14:00
|
|
190
|
+
should_sleep = sleep_minutes <= current_minutes < wake_minutes
|
|
191
|
+
|
|
192
|
+
if should_sleep and not self._paused:
|
|
193
|
+
self._paused = True
|
|
194
|
+
logger.info("Sleep schedule: pausing %s", self.agent.nick)
|
|
195
|
+
elif not should_sleep and self._paused:
|
|
196
|
+
self._paused = False
|
|
197
|
+
logger.info("Sleep schedule: resuming %s", self.agent.nick)
|
|
198
|
+
except asyncio.CancelledError:
|
|
199
|
+
return
|
|
200
|
+
except Exception:
|
|
201
|
+
logger.exception("Sleep scheduler error")
|
|
202
|
+
|
|
139
203
|
async def _graceful_shutdown(self) -> None:
|
|
140
204
|
"""Trigger a graceful shutdown, signaling any waiting stop event."""
|
|
141
205
|
logger.info("Graceful shutdown requested for %s", self.agent.nick)
|
|
@@ -169,7 +233,10 @@ class AgentDaemon:
|
|
|
169
233
|
|
|
170
234
|
Formats a prompt and enqueues it so the SDK session picks it up.
|
|
171
235
|
"""
|
|
236
|
+
if self._paused:
|
|
237
|
+
return
|
|
172
238
|
if self._agent_runner and self._agent_runner.is_running():
|
|
239
|
+
self._last_activation = time.time()
|
|
173
240
|
if target.startswith("#"):
|
|
174
241
|
prompt = f"[IRC @mention in {target}] <{sender}> {text}"
|
|
175
242
|
else:
|
|
@@ -305,6 +372,15 @@ class AgentDaemon:
|
|
|
305
372
|
elif msg_type == "clear":
|
|
306
373
|
return await self._ipc_clear(req_id)
|
|
307
374
|
|
|
375
|
+
elif msg_type == "status":
|
|
376
|
+
return self._ipc_status(req_id)
|
|
377
|
+
|
|
378
|
+
elif msg_type == "pause":
|
|
379
|
+
return await self._ipc_pause(req_id)
|
|
380
|
+
|
|
381
|
+
elif msg_type == "resume":
|
|
382
|
+
return await self._ipc_resume(req_id)
|
|
383
|
+
|
|
308
384
|
elif msg_type == "shutdown":
|
|
309
385
|
asyncio.create_task(self._graceful_shutdown())
|
|
310
386
|
return make_response(req_id, ok=True)
|
|
@@ -320,6 +396,30 @@ class AgentDaemon:
|
|
|
320
396
|
# IPC sub-handlers
|
|
321
397
|
# ------------------------------------------------------------------
|
|
322
398
|
|
|
399
|
+
async def _ipc_pause(self, req_id: str) -> dict:
|
|
400
|
+
self._paused = True
|
|
401
|
+
logger.info("Agent %s paused", self.agent.nick)
|
|
402
|
+
return make_response(req_id, ok=True)
|
|
403
|
+
|
|
404
|
+
async def _ipc_resume(self, req_id: str) -> dict:
|
|
405
|
+
self._paused = False
|
|
406
|
+
logger.info("Agent %s resumed", self.agent.nick)
|
|
407
|
+
# NOTE: Catch-up on missed messages is not yet implemented.
|
|
408
|
+
# IRCTransport does not process HISTORY responses into the buffer.
|
|
409
|
+
# The agent resumes and will see new messages going forward.
|
|
410
|
+
return make_response(req_id, ok=True)
|
|
411
|
+
|
|
412
|
+
def _ipc_status(self, req_id: str) -> dict:
|
|
413
|
+
running = self._agent_runner is not None and self._agent_runner.is_running()
|
|
414
|
+
turn_count = self._supervisor._turn_count if self._supervisor else 0
|
|
415
|
+
return make_response(req_id, ok=True, data={
|
|
416
|
+
"running": running,
|
|
417
|
+
"paused": self._paused,
|
|
418
|
+
"turn_count": turn_count,
|
|
419
|
+
"last_activation": self._last_activation,
|
|
420
|
+
"activity": "paused" if self._paused else ("working" if running else "idle"),
|
|
421
|
+
})
|
|
422
|
+
|
|
323
423
|
async def _ipc_irc_send(self, req_id: str, msg: dict) -> dict:
|
|
324
424
|
channel = msg.get("channel", "")
|
|
325
425
|
text = msg.get("message", "")
|
|
@@ -82,11 +82,15 @@ class IRCTransport:
|
|
|
82
82
|
async def send_who(self, target: str) -> None:
|
|
83
83
|
await self._send_raw(f"WHO {target}")
|
|
84
84
|
|
|
85
|
-
async def
|
|
85
|
+
async def send_raw(self, line: str) -> None:
|
|
86
|
+
"""Send a raw IRC line. Public for commands like HISTORY."""
|
|
86
87
|
if self._writer:
|
|
87
88
|
self._writer.write(f"{line}\r\n".encode())
|
|
88
89
|
await self._writer.drain()
|
|
89
90
|
|
|
91
|
+
async def _send_raw(self, line: str) -> None:
|
|
92
|
+
await self.send_raw(line)
|
|
93
|
+
|
|
90
94
|
async def _read_loop(self) -> None:
|
|
91
95
|
buf = ""
|
|
92
96
|
try:
|
|
@@ -188,6 +188,29 @@ class IRCObserver:
|
|
|
188
188
|
finally:
|
|
189
189
|
await self._disconnect(writer)
|
|
190
190
|
|
|
191
|
+
async def send_message(self, target: str, text: str) -> None:
|
|
192
|
+
"""Send a PRIVMSG to a channel or nick, then disconnect.
|
|
193
|
+
|
|
194
|
+
Uses the same ephemeral connection pattern as the read commands.
|
|
195
|
+
"""
|
|
196
|
+
# Sanitize CR/LF to prevent IRC command injection
|
|
197
|
+
target = target.replace("\r", "").replace("\n", "")
|
|
198
|
+
text = text.replace("\r", "").replace("\n", " ")
|
|
199
|
+
|
|
200
|
+
reader, writer, nick = await self._connect_and_register()
|
|
201
|
+
try:
|
|
202
|
+
# If sending to a channel, join it first so the server accepts the PRIVMSG
|
|
203
|
+
if target.startswith("#"):
|
|
204
|
+
writer.write(f"JOIN {target}\r\n".encode())
|
|
205
|
+
await writer.drain()
|
|
206
|
+
# Drain join responses
|
|
207
|
+
await self._recv_lines(reader, timeout=1.0)
|
|
208
|
+
|
|
209
|
+
writer.write(f"PRIVMSG {target} :{text}\r\n".encode())
|
|
210
|
+
await writer.drain()
|
|
211
|
+
finally:
|
|
212
|
+
await self._disconnect(writer)
|
|
213
|
+
|
|
191
214
|
async def list_channels(self) -> list[str]:
|
|
192
215
|
"""List active channels using the LIST command.
|
|
193
216
|
|
|
@@ -107,13 +107,58 @@ Sends shutdown via IPC socket, falls back to PID file + SIGTERM.
|
|
|
107
107
|
List all configured agents and their running state.
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
|
-
agentirc status
|
|
110
|
+
agentirc status # quick view (nick, status, PID)
|
|
111
|
+
agentirc status --full # query running agents for activity
|
|
112
|
+
agentirc status spark-agentirc # detailed view for one agent
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Flag | Description |
|
|
116
|
+
|------|-------------|
|
|
117
|
+
| `--full` | Query each running agent via IPC for activity status |
|
|
118
|
+
| `nick` | Show detailed single-agent view (directory, backend, model, etc.) |
|
|
119
|
+
|
|
120
|
+
### `agentirc sleep`
|
|
121
|
+
|
|
122
|
+
Pause agent(s) — daemon stays connected to IRC but ignores @mentions.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
agentirc sleep spark-agentirc # pause specific agent
|
|
126
|
+
agentirc sleep --all # pause all agents
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Agents auto-pause at `sleep_start` (default `23:00`) and auto-resume at `sleep_end` (default `08:00`). Configure in `agents.yaml`:
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
sleep_start: "23:00"
|
|
133
|
+
sleep_end: "08:00"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `agentirc wake`
|
|
137
|
+
|
|
138
|
+
Resume paused agent(s).
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
agentirc wake spark-agentirc # resume specific agent
|
|
142
|
+
agentirc wake --all # resume all agents
|
|
111
143
|
```
|
|
112
144
|
|
|
113
145
|
## Observation
|
|
114
146
|
|
|
115
147
|
Read-only commands for peeking at the network. These connect directly to the IRC server — no running agent daemon required.
|
|
116
148
|
|
|
149
|
+
### `agentirc send`
|
|
150
|
+
|
|
151
|
+
Send a message to a channel or agent.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
agentirc send "#general" "hello from the CLI"
|
|
155
|
+
agentirc send spark-agentirc "what are you working on?"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Uses an ephemeral IRC connection — no daemon required.
|
|
159
|
+
|
|
160
|
+
Read-only commands for peeking at the network. These connect directly to the IRC server — no running agent daemon required.
|
|
161
|
+
|
|
117
162
|
### `agentirc read`
|
|
118
163
|
|
|
119
164
|
Read recent channel messages.
|