agentirc-cli 0.15.0__tar.gz → 0.15.1__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.15.0 → agentirc_cli-0.15.1}/CHANGELOG.md +8 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/PKG-INFO +1 -1
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/overview/renderer_web.py +69 -3
- agentirc_cli-0.15.1/agentirc/pidfile.py +84 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/overview.md +14 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/pyproject.toml +1 -1
- agentirc_cli-0.15.1/tests/test_overview_web.py +165 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/uv.lock +1 -1
- agentirc_cli-0.15.0/agentirc/pidfile.py +0 -49
- agentirc_cli-0.15.0/tests/test_overview_web.py +0 -58
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/.claude/skills/pr-review/SKILL.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/.github/workflows/pages.yml +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/.github/workflows/publish.yml +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/.github/workflows/tests.yml +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/.gitignore +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/.markdownlint-cli2.yaml +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/.pr_agent.toml +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/CLAUDE.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/CNAME +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/Gemfile +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/Gemfile.lock +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/LICENSE +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/README.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/_config.yml +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/_sass/color_schemes/anthropic.scss +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/_sass/custom/custom.scss +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/__main__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/cli.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/__main__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/agent_runner.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/config.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/ipc.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/irc_transport.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/message_buffer.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/skill/SKILL.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/skill/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/skill/irc_client.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/socket_server.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/supervisor.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/claude/webhook.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/agent_runner.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/config.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/ipc.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/irc_transport.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/message_buffer.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/skill/SKILL.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/skill/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/skill/irc_client.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/socket_server.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/supervisor.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/codex/webhook.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/agent_runner.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/config.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/ipc.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/irc_transport.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/message_buffer.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/skill/SKILL.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/skill/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/skill/irc_client.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/socket_server.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/supervisor.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/copilot/webhook.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/agent_runner.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/config.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/ipc.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/irc_transport.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/message_buffer.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/skill/SKILL.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/skill/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/skill/irc_client.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/socket_server.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/supervisor.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/clients/opencode/webhook.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/learn_prompt.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/observer.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/overview/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/overview/collector.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/overview/model.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/overview/renderer_text.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/overview/web/style.css +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/commands.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/extensions/federation.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/extensions/history.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/extensions/rooms.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/extensions/tags.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/message.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/protocol-index.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/protocol/replies.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/__main__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/channel.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/client.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/config.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/ircd.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/remote_client.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/room_store.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/rooms_util.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/server_link.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/skill.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/skills/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/skills/history.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/agentirc/server/skills/rooms.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/agent-client.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/agent-harness-spec.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/ci.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/cli.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/claude/configuration.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/claude/context-management.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/claude/irc-tools.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/claude/overview.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/claude/setup.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/claude/supervisor.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/claude/webhooks.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/codex/configuration.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/codex/context-management.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/codex/irc-tools.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/codex/overview.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/codex/setup.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/codex/supervisor.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/codex/webhooks.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/copilot/configuration.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/copilot/context-management.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/copilot/irc-tools.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/copilot/overview.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/copilot/setup.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/copilot/supervisor.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/copilot/webhooks.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/opencode/configuration.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/opencode/context-management.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/opencode/irc-tools.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/opencode/overview.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/opencode/setup.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/opencode/supervisor.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/clients/opencode/webhooks.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/codex-backend.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/copilot-backend.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/design.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/docs-site.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/getting-started.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/grow-your-agent.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/layer1-core-irc.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/layer2-attention.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/layer3-skills.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/layer4-federation.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/layer5-agent-harness.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/opencode-backend.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/publishing.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/resources/github-copilot-sdk-instructions.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/rooms.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/server-architecture.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/01-pair-programming.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/02-code-review-ensemble.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/03-cross-server-delegation.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/04-knowledge-propagation.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/05-the-observer.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/06-cross-server-ops.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/07-supervisor-intervention.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/08-apps-as-agents.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/09-research-swarm.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases/10-grow-your-agent.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/docs/use-cases-index.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/index.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/README.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/config.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/ipc.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/irc_transport.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/message_buffer.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/skill/SKILL.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/skill/irc_client.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/socket_server.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/packages/agent-harness/webhook.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/plugins/claude-code/skills/irc/SKILL.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/plugins/codex/skills/agentirc-irc/SKILL.md +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/__init__.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/conftest.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_agent_runner.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_channel.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_codex_daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_connection.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_copilot_daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_daemon_config.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_daemon_ipc.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_discovery.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_federation.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_history.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_integration_layer5.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_ipc.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_irc_transport.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_mentions.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_message.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_message_buffer.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_messaging.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_modes.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_opencode_daemon.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_overview_cli.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_overview_collector.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_overview_model.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_overview_renderer.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_room_persistence.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_rooms.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_rooms_federation.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_rooms_integration.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_skill_client.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_skills.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_socket_server.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_supervisor.py +0 -0
- {agentirc_cli-0.15.0 → agentirc_cli-0.15.1}/tests/test_webhook.py +0 -0
|
@@ -4,6 +4,14 @@ 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.15.1] - 2026-03-30
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Overview serve: flush stdout so port URL is visible when backgrounded
|
|
13
|
+
- Overview serve: auto-kill previous instance for same server via PID/port files
|
|
14
|
+
|
|
7
15
|
## [0.15.0] - 2026-03-30
|
|
8
16
|
|
|
9
17
|
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"""Render mesh overview as HTML and serve via HTTP."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import atexit
|
|
4
5
|
import asyncio
|
|
5
6
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
|
7
|
+
import os
|
|
6
8
|
from pathlib import Path
|
|
9
|
+
import signal
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
7
12
|
|
|
8
13
|
import re
|
|
9
14
|
|
|
10
15
|
import mistune
|
|
11
16
|
|
|
17
|
+
from agentirc.pidfile import (
|
|
18
|
+
is_process_alive, read_pid, read_port,
|
|
19
|
+
remove_pid, remove_port, write_pid, write_port,
|
|
20
|
+
)
|
|
12
21
|
from .collector import collect_mesh_state
|
|
13
22
|
from .model import MeshState
|
|
14
23
|
from .renderer_text import render_text
|
|
@@ -72,6 +81,45 @@ def render_html(
|
|
|
72
81
|
</html>"""
|
|
73
82
|
|
|
74
83
|
|
|
84
|
+
def _stop_existing_overview(pid_name: str) -> None:
|
|
85
|
+
"""Kill a previous overview instance if running, clean up its files."""
|
|
86
|
+
existing_pid = read_pid(pid_name)
|
|
87
|
+
if existing_pid and is_process_alive(existing_pid):
|
|
88
|
+
existing_port = read_port(pid_name)
|
|
89
|
+
try:
|
|
90
|
+
os.kill(existing_pid, signal.SIGTERM)
|
|
91
|
+
except (PermissionError, ProcessLookupError) as exc:
|
|
92
|
+
remove_pid(pid_name)
|
|
93
|
+
remove_port(pid_name)
|
|
94
|
+
port_msg = f", port {existing_port}" if existing_port else ""
|
|
95
|
+
print(
|
|
96
|
+
f"Warning: could not stop previous overview (PID {existing_pid}"
|
|
97
|
+
f"{port_msg}): {exc}",
|
|
98
|
+
flush=True,
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
for _ in range(20):
|
|
102
|
+
if not is_process_alive(existing_pid):
|
|
103
|
+
break
|
|
104
|
+
time.sleep(0.1)
|
|
105
|
+
else:
|
|
106
|
+
try:
|
|
107
|
+
os.kill(existing_pid, signal.SIGKILL)
|
|
108
|
+
except (PermissionError, ProcessLookupError):
|
|
109
|
+
pass
|
|
110
|
+
remove_pid(pid_name)
|
|
111
|
+
remove_port(pid_name)
|
|
112
|
+
port_msg = f", port {existing_port}" if existing_port else ""
|
|
113
|
+
print(
|
|
114
|
+
f"Stopped previous overview for '{pid_name.removeprefix('overview-')}'"
|
|
115
|
+
f" (PID {existing_pid}{port_msg})",
|
|
116
|
+
flush=True,
|
|
117
|
+
)
|
|
118
|
+
elif existing_pid:
|
|
119
|
+
remove_pid(pid_name)
|
|
120
|
+
remove_port(pid_name)
|
|
121
|
+
|
|
122
|
+
|
|
75
123
|
def serve_web(
|
|
76
124
|
host: str,
|
|
77
125
|
port: int,
|
|
@@ -84,6 +132,8 @@ def serve_web(
|
|
|
84
132
|
serve_port: int = 0,
|
|
85
133
|
) -> None:
|
|
86
134
|
"""Start a local HTTP server serving the live overview."""
|
|
135
|
+
pid_name = f"overview-{server_name}"
|
|
136
|
+
_stop_existing_overview(pid_name)
|
|
87
137
|
|
|
88
138
|
class OverviewHandler(SimpleHTTPRequestHandler):
|
|
89
139
|
def do_GET(self):
|
|
@@ -110,11 +160,27 @@ def serve_web(
|
|
|
110
160
|
|
|
111
161
|
httpd = HTTPServer(("127.0.0.1", serve_port), OverviewHandler)
|
|
112
162
|
actual_port = httpd.server_address[1]
|
|
113
|
-
|
|
114
|
-
|
|
163
|
+
|
|
164
|
+
write_pid(pid_name, os.getpid())
|
|
165
|
+
write_port(pid_name, actual_port)
|
|
166
|
+
|
|
167
|
+
def _cleanup():
|
|
168
|
+
remove_pid(pid_name)
|
|
169
|
+
remove_port(pid_name)
|
|
170
|
+
|
|
171
|
+
atexit.register(_cleanup)
|
|
172
|
+
if threading.current_thread() is threading.main_thread():
|
|
173
|
+
def _handle_term(_sig, _frame):
|
|
174
|
+
threading.Thread(target=httpd.shutdown, daemon=True).start()
|
|
175
|
+
signal.signal(signal.SIGTERM, _handle_term)
|
|
176
|
+
|
|
177
|
+
print(f"Overview dashboard: http://localhost:{actual_port}", flush=True)
|
|
178
|
+
print("Press Ctrl+C to stop.", flush=True)
|
|
115
179
|
try:
|
|
116
180
|
httpd.serve_forever()
|
|
117
181
|
except KeyboardInterrupt:
|
|
118
|
-
print("\nStopped.")
|
|
182
|
+
print("\nStopped.", flush=True)
|
|
119
183
|
finally:
|
|
120
184
|
httpd.server_close()
|
|
185
|
+
_cleanup()
|
|
186
|
+
atexit.unregister(_cleanup)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""PID file management for agentirc daemon instances."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
PID_DIR = os.path.expanduser("~/.agentirc/pids")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _safe_name(name: str) -> str:
|
|
13
|
+
"""Sanitize a daemon name to prevent path traversal."""
|
|
14
|
+
return re.sub(r"[^a-zA-Z0-9._-]", "_", Path(name).name)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def write_pid(name: str, pid: int) -> Path:
|
|
18
|
+
"""Write a PID file for the named daemon. Creates the directory if needed."""
|
|
19
|
+
pid_dir = Path(PID_DIR)
|
|
20
|
+
pid_dir.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
pid_path = pid_dir / f"{_safe_name(name)}.pid"
|
|
22
|
+
pid_path.write_text(str(pid))
|
|
23
|
+
return pid_path
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def read_pid(name: str) -> int | None:
|
|
27
|
+
"""Read the PID for the named daemon. Returns None if file is missing."""
|
|
28
|
+
pid_path = Path(PID_DIR) / f"{_safe_name(name)}.pid"
|
|
29
|
+
if not pid_path.exists():
|
|
30
|
+
return None
|
|
31
|
+
try:
|
|
32
|
+
return int(pid_path.read_text().strip())
|
|
33
|
+
except (ValueError, OSError):
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def remove_pid(name: str) -> None:
|
|
38
|
+
"""Remove the PID file for the named daemon if it exists."""
|
|
39
|
+
pid_path = Path(PID_DIR) / f"{_safe_name(name)}.pid"
|
|
40
|
+
try:
|
|
41
|
+
pid_path.unlink()
|
|
42
|
+
except FileNotFoundError:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_port(name: str, port: int) -> Path:
|
|
47
|
+
"""Write a port file for the named daemon. Creates the directory if needed."""
|
|
48
|
+
pid_dir = Path(PID_DIR)
|
|
49
|
+
pid_dir.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
port_path = pid_dir / f"{_safe_name(name)}.port"
|
|
51
|
+
port_path.write_text(str(port))
|
|
52
|
+
return port_path
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def read_port(name: str) -> int | None:
|
|
56
|
+
"""Read the port for the named daemon. Returns None if file is missing."""
|
|
57
|
+
port_path = Path(PID_DIR) / f"{_safe_name(name)}.port"
|
|
58
|
+
if not port_path.exists():
|
|
59
|
+
return None
|
|
60
|
+
try:
|
|
61
|
+
return int(port_path.read_text().strip())
|
|
62
|
+
except (ValueError, OSError):
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def remove_port(name: str) -> None:
|
|
67
|
+
"""Remove the port file for the named daemon if it exists."""
|
|
68
|
+
port_path = Path(PID_DIR) / f"{_safe_name(name)}.port"
|
|
69
|
+
try:
|
|
70
|
+
port_path.unlink()
|
|
71
|
+
except FileNotFoundError:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def is_process_alive(pid: int) -> bool:
|
|
76
|
+
"""Check whether a process with the given PID is alive."""
|
|
77
|
+
try:
|
|
78
|
+
os.kill(pid, 0)
|
|
79
|
+
return True
|
|
80
|
+
except ProcessLookupError:
|
|
81
|
+
return False
|
|
82
|
+
except PermissionError:
|
|
83
|
+
# Process exists but we don't have permission to signal it
|
|
84
|
+
return True
|
|
@@ -48,6 +48,20 @@ memberships with roles, and cross-channel recent activity.
|
|
|
48
48
|
styled HTML with the anthropic cream theme. Auto-refreshes at the interval
|
|
49
49
|
set by `--refresh` (default: 5 seconds).
|
|
50
50
|
|
|
51
|
+
### Instance Management
|
|
52
|
+
|
|
53
|
+
Each overview server registers itself with a PID and port file in
|
|
54
|
+
`~/.agentirc/pids/` (e.g., `overview-spark.pid`, `overview-spark.port`).
|
|
55
|
+
|
|
56
|
+
- **One per server**: Starting a new overview for the same IRC server
|
|
57
|
+
auto-kills the previous instance via SIGTERM (with SIGKILL fallback).
|
|
58
|
+
- **Multiple servers**: Different IRC servers can each have their own
|
|
59
|
+
overview site running simultaneously (keyed by `server_name`).
|
|
60
|
+
- **Graceful shutdown**: SIGTERM and Ctrl+C both trigger clean shutdown
|
|
61
|
+
with PID/port file removal.
|
|
62
|
+
- **Background visibility**: The dashboard URL is flushed to stdout
|
|
63
|
+
immediately, so it appears even when the process runs in the background.
|
|
64
|
+
|
|
51
65
|
## Data Sources
|
|
52
66
|
|
|
53
67
|
- **IRC Observer**: Ephemeral connection queries LIST, NAMES, WHO, HISTORY
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Tests for overview web renderer."""
|
|
2
|
+
import os
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from agentirc.overview.model import Agent, Message, MeshState, Room
|
|
7
|
+
from agentirc.overview.renderer_web import render_html, serve_web, _stop_existing_overview
|
|
8
|
+
from agentirc import pidfile
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _make_fixture() -> MeshState:
|
|
12
|
+
now = time.time()
|
|
13
|
+
agent = Agent(
|
|
14
|
+
nick="spark-claude", status="active", activity="working on: tests",
|
|
15
|
+
channels=["#general"], server="spark",
|
|
16
|
+
backend="claude", model="claude-opus-4-6",
|
|
17
|
+
)
|
|
18
|
+
msg = Message(nick="spark-claude", text="hello", timestamp=now - 60, channel="#general")
|
|
19
|
+
room = Room(
|
|
20
|
+
name="#general", topic="Testing",
|
|
21
|
+
members=[agent], operators=["spark-claude"],
|
|
22
|
+
federation_servers=[], messages=[msg],
|
|
23
|
+
)
|
|
24
|
+
return MeshState(server_name="spark", rooms=[room], agents=[agent], federation_links=[])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_render_html_produces_valid_html():
|
|
28
|
+
mesh = _make_fixture()
|
|
29
|
+
html = render_html(mesh, message_limit=4)
|
|
30
|
+
assert "<!DOCTYPE html>" in html
|
|
31
|
+
assert "<html" in html
|
|
32
|
+
assert "</html>" in html
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_render_html_contains_content():
|
|
36
|
+
mesh = _make_fixture()
|
|
37
|
+
html = render_html(mesh, message_limit=4)
|
|
38
|
+
assert "spark mesh" in html
|
|
39
|
+
assert "#general" in html
|
|
40
|
+
assert "spark-claude" in html
|
|
41
|
+
assert "hello" in html
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_render_html_has_cream_styles():
|
|
45
|
+
mesh = _make_fixture()
|
|
46
|
+
html = render_html(mesh, message_limit=4)
|
|
47
|
+
assert "#faf7f2" in html or "faf7f2" in html
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_render_html_has_auto_refresh():
|
|
51
|
+
mesh = _make_fixture()
|
|
52
|
+
html = render_html(mesh, message_limit=4, refresh_interval=5)
|
|
53
|
+
assert 'http-equiv="refresh"' in html or "refresh" in html.lower()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_render_html_has_table_elements():
|
|
57
|
+
mesh = _make_fixture()
|
|
58
|
+
html = render_html(mesh, message_limit=4)
|
|
59
|
+
assert "<table" in html
|
|
60
|
+
assert "<th>" in html or "<th " in html
|
|
61
|
+
assert "status-active" in html
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# --- serve_web PID/port management tests ---
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_serve_web_writes_and_cleans_pid_port(tmp_path, monkeypatch):
|
|
68
|
+
"""serve_web writes PID and port files, cleans up on shutdown."""
|
|
69
|
+
monkeypatch.setattr(pidfile, "PID_DIR", str(tmp_path))
|
|
70
|
+
|
|
71
|
+
pid_name = "overview-testserver"
|
|
72
|
+
pid_path = tmp_path / f"{pid_name}.pid"
|
|
73
|
+
port_path = tmp_path / f"{pid_name}.port"
|
|
74
|
+
|
|
75
|
+
# We need to stop the server after it starts. Patch HTTPServer to
|
|
76
|
+
# capture the instance so we can call shutdown() from a timer.
|
|
77
|
+
from http.server import HTTPServer
|
|
78
|
+
captured = {}
|
|
79
|
+
_orig_init = HTTPServer.__init__
|
|
80
|
+
|
|
81
|
+
def _patched_init(self, *args, **kwargs):
|
|
82
|
+
_orig_init(self, *args, **kwargs)
|
|
83
|
+
captured["httpd"] = self
|
|
84
|
+
|
|
85
|
+
monkeypatch.setattr(HTTPServer, "__init__", _patched_init)
|
|
86
|
+
|
|
87
|
+
def _run():
|
|
88
|
+
serve_web(
|
|
89
|
+
host="127.0.0.1", port=6667, server_name="testserver",
|
|
90
|
+
serve_port=0,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
t = threading.Thread(target=_run, daemon=True)
|
|
94
|
+
t.start()
|
|
95
|
+
|
|
96
|
+
# Wait for PID file to appear
|
|
97
|
+
for _ in range(40):
|
|
98
|
+
if pid_path.exists() and port_path.exists():
|
|
99
|
+
break
|
|
100
|
+
time.sleep(0.05)
|
|
101
|
+
|
|
102
|
+
assert pid_path.exists(), "PID file should be created"
|
|
103
|
+
assert port_path.exists(), "Port file should be created"
|
|
104
|
+
|
|
105
|
+
stored_pid = int(pid_path.read_text().strip())
|
|
106
|
+
stored_port = int(port_path.read_text().strip())
|
|
107
|
+
assert stored_pid == os.getpid()
|
|
108
|
+
assert stored_port > 0
|
|
109
|
+
|
|
110
|
+
# Shut down the server gracefully
|
|
111
|
+
captured["httpd"].shutdown()
|
|
112
|
+
t.join(timeout=5)
|
|
113
|
+
|
|
114
|
+
assert not pid_path.exists(), "PID file should be cleaned up"
|
|
115
|
+
assert not port_path.exists(), "Port file should be cleaned up"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_stop_existing_overview_kills_previous(tmp_path, monkeypatch):
|
|
119
|
+
"""_stop_existing_overview sends SIGTERM to a live previous instance."""
|
|
120
|
+
monkeypatch.setattr(pidfile, "PID_DIR", str(tmp_path))
|
|
121
|
+
|
|
122
|
+
pid_name = "overview-testserver"
|
|
123
|
+
(tmp_path / f"{pid_name}.pid").write_text("99999")
|
|
124
|
+
(tmp_path / f"{pid_name}.port").write_text("12345")
|
|
125
|
+
|
|
126
|
+
# Simulate: process alive on first check, dead after SIGTERM
|
|
127
|
+
alive_calls = {"count": 0}
|
|
128
|
+
|
|
129
|
+
def _fake_alive(pid):
|
|
130
|
+
alive_calls["count"] += 1
|
|
131
|
+
return alive_calls["count"] == 1 # alive first, dead after
|
|
132
|
+
|
|
133
|
+
killed_pids = []
|
|
134
|
+
monkeypatch.setattr(os, "kill", lambda pid, sig: killed_pids.append((pid, sig)))
|
|
135
|
+
|
|
136
|
+
import agentirc.overview.renderer_web as rweb
|
|
137
|
+
monkeypatch.setattr(rweb, "is_process_alive", _fake_alive)
|
|
138
|
+
|
|
139
|
+
_stop_existing_overview(pid_name)
|
|
140
|
+
|
|
141
|
+
import signal
|
|
142
|
+
assert (99999, signal.SIGTERM) in killed_pids, "SIGTERM should be sent"
|
|
143
|
+
assert not (tmp_path / f"{pid_name}.pid").exists()
|
|
144
|
+
assert not (tmp_path / f"{pid_name}.port").exists()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_stop_existing_overview_cleans_stale_pid(tmp_path, monkeypatch):
|
|
148
|
+
"""Stale PID files (dead process) are cleaned without sending signals."""
|
|
149
|
+
monkeypatch.setattr(pidfile, "PID_DIR", str(tmp_path))
|
|
150
|
+
|
|
151
|
+
pid_name = "overview-stale"
|
|
152
|
+
(tmp_path / f"{pid_name}.pid").write_text("11111")
|
|
153
|
+
(tmp_path / f"{pid_name}.port").write_text("9999")
|
|
154
|
+
|
|
155
|
+
kills = []
|
|
156
|
+
monkeypatch.setattr(os, "kill", lambda pid, sig: kills.append((pid, sig)))
|
|
157
|
+
|
|
158
|
+
import agentirc.overview.renderer_web as rweb
|
|
159
|
+
monkeypatch.setattr(rweb, "is_process_alive", lambda pid: False)
|
|
160
|
+
|
|
161
|
+
_stop_existing_overview(pid_name)
|
|
162
|
+
|
|
163
|
+
assert not (tmp_path / f"{pid_name}.pid").exists()
|
|
164
|
+
assert not (tmp_path / f"{pid_name}.port").exists()
|
|
165
|
+
assert kills == [], "No signals should be sent for a dead process"
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"""PID file management for agentirc daemon instances."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
PID_DIR = os.path.expanduser("~/.agentirc/pids")
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def write_pid(name: str, pid: int) -> Path:
|
|
12
|
-
"""Write a PID file for the named daemon. Creates the directory if needed."""
|
|
13
|
-
pid_dir = Path(PID_DIR)
|
|
14
|
-
pid_dir.mkdir(parents=True, exist_ok=True)
|
|
15
|
-
pid_path = pid_dir / f"{name}.pid"
|
|
16
|
-
pid_path.write_text(str(pid))
|
|
17
|
-
return pid_path
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def read_pid(name: str) -> int | None:
|
|
21
|
-
"""Read the PID for the named daemon. Returns None if file is missing."""
|
|
22
|
-
pid_path = Path(PID_DIR) / f"{name}.pid"
|
|
23
|
-
if not pid_path.exists():
|
|
24
|
-
return None
|
|
25
|
-
try:
|
|
26
|
-
return int(pid_path.read_text().strip())
|
|
27
|
-
except (ValueError, OSError):
|
|
28
|
-
return None
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def remove_pid(name: str) -> None:
|
|
32
|
-
"""Remove the PID file for the named daemon if it exists."""
|
|
33
|
-
pid_path = Path(PID_DIR) / f"{name}.pid"
|
|
34
|
-
try:
|
|
35
|
-
pid_path.unlink()
|
|
36
|
-
except FileNotFoundError:
|
|
37
|
-
pass
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def is_process_alive(pid: int) -> bool:
|
|
41
|
-
"""Check whether a process with the given PID is alive."""
|
|
42
|
-
try:
|
|
43
|
-
os.kill(pid, 0)
|
|
44
|
-
return True
|
|
45
|
-
except ProcessLookupError:
|
|
46
|
-
return False
|
|
47
|
-
except PermissionError:
|
|
48
|
-
# Process exists but we don't have permission to signal it
|
|
49
|
-
return True
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"""Tests for overview web renderer."""
|
|
2
|
-
import time
|
|
3
|
-
|
|
4
|
-
from agentirc.overview.model import Agent, Message, MeshState, Room
|
|
5
|
-
from agentirc.overview.renderer_web import render_html
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _make_fixture() -> MeshState:
|
|
9
|
-
now = time.time()
|
|
10
|
-
agent = Agent(
|
|
11
|
-
nick="spark-claude", status="active", activity="working on: tests",
|
|
12
|
-
channels=["#general"], server="spark",
|
|
13
|
-
backend="claude", model="claude-opus-4-6",
|
|
14
|
-
)
|
|
15
|
-
msg = Message(nick="spark-claude", text="hello", timestamp=now - 60, channel="#general")
|
|
16
|
-
room = Room(
|
|
17
|
-
name="#general", topic="Testing",
|
|
18
|
-
members=[agent], operators=["spark-claude"],
|
|
19
|
-
federation_servers=[], messages=[msg],
|
|
20
|
-
)
|
|
21
|
-
return MeshState(server_name="spark", rooms=[room], agents=[agent], federation_links=[])
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def test_render_html_produces_valid_html():
|
|
25
|
-
mesh = _make_fixture()
|
|
26
|
-
html = render_html(mesh, message_limit=4)
|
|
27
|
-
assert "<!DOCTYPE html>" in html
|
|
28
|
-
assert "<html" in html
|
|
29
|
-
assert "</html>" in html
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def test_render_html_contains_content():
|
|
33
|
-
mesh = _make_fixture()
|
|
34
|
-
html = render_html(mesh, message_limit=4)
|
|
35
|
-
assert "spark mesh" in html
|
|
36
|
-
assert "#general" in html
|
|
37
|
-
assert "spark-claude" in html
|
|
38
|
-
assert "hello" in html
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_render_html_has_cream_styles():
|
|
42
|
-
mesh = _make_fixture()
|
|
43
|
-
html = render_html(mesh, message_limit=4)
|
|
44
|
-
assert "#faf7f2" in html or "faf7f2" in html
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def test_render_html_has_auto_refresh():
|
|
48
|
-
mesh = _make_fixture()
|
|
49
|
-
html = render_html(mesh, message_limit=4, refresh_interval=5)
|
|
50
|
-
assert 'http-equiv="refresh"' in html or "refresh" in html.lower()
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_render_html_has_table_elements():
|
|
54
|
-
mesh = _make_fixture()
|
|
55
|
-
html = render_html(mesh, message_limit=4)
|
|
56
|
-
assert "<table" in html
|
|
57
|
-
assert "<th>" in html or "<th " in html
|
|
58
|
-
assert "status-active" in html
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|