agent-tether 0.2.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.
- agent_tether-0.2.0/.github/workflows/publish.yml +31 -0
- agent_tether-0.2.0/.github/workflows/test.yml +33 -0
- agent_tether-0.2.0/.gitignore +15 -0
- agent_tether-0.2.0/CHANGELOG.md +42 -0
- agent_tether-0.2.0/LICENSE +21 -0
- agent_tether-0.2.0/PKG-INFO +178 -0
- agent_tether-0.2.0/README.md +131 -0
- agent_tether-0.2.0/pyproject.toml +76 -0
- agent_tether-0.2.0/src/agent_tether/__init__.py +64 -0
- agent_tether-0.2.0/src/agent_tether/approval.py +142 -0
- agent_tether-0.2.0/src/agent_tether/batching.py +62 -0
- agent_tether-0.2.0/src/agent_tether/debounce.py +40 -0
- agent_tether-0.2.0/src/agent_tether/formatting.py +176 -0
- agent_tether-0.2.0/src/agent_tether/models.py +108 -0
- agent_tether-0.2.0/src/agent_tether/platforms/__init__.py +0 -0
- agent_tether-0.2.0/src/agent_tether/platforms/base.py +598 -0
- agent_tether-0.2.0/src/agent_tether/platforms/discord/__init__.py +0 -0
- agent_tether-0.2.0/src/agent_tether/platforms/discord/bridge.py +403 -0
- agent_tether-0.2.0/src/agent_tether/platforms/discord/pairing.py +90 -0
- agent_tether-0.2.0/src/agent_tether/platforms/slack/__init__.py +0 -0
- agent_tether-0.2.0/src/agent_tether/platforms/slack/bridge.py +287 -0
- agent_tether-0.2.0/src/agent_tether/platforms/telegram/__init__.py +0 -0
- agent_tether-0.2.0/src/agent_tether/platforms/telegram/bridge.py +619 -0
- agent_tether-0.2.0/src/agent_tether/platforms/telegram/formatting.py +197 -0
- agent_tether-0.2.0/src/agent_tether/py.typed +0 -0
- agent_tether-0.2.0/src/agent_tether/router.py +55 -0
- agent_tether-0.2.0/src/agent_tether/runner/__init__.py +14 -0
- agent_tether-0.2.0/src/agent_tether/runner/adapters/__init__.py +18 -0
- agent_tether-0.2.0/src/agent_tether/runner/protocol.py +192 -0
- agent_tether-0.2.0/src/agent_tether/runner/registry.py +81 -0
- agent_tether-0.2.0/src/agent_tether/state.py +105 -0
- agent_tether-0.2.0/src/agent_tether/subscriber.py +205 -0
- agent_tether-0.2.0/tests/__init__.py +0 -0
- agent_tether-0.2.0/tests/test_approval.py +86 -0
- agent_tether-0.2.0/tests/test_base.py +197 -0
- agent_tether-0.2.0/tests/test_formatting.py +61 -0
- agent_tether-0.2.0/tests/test_runner.py +351 -0
- agent_tether-0.2.0/tests/test_subscriber.py +177 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
pypi-publish:
|
|
12
|
+
name: Upload release to PyPI
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: pypi
|
|
15
|
+
permissions:
|
|
16
|
+
id-token: write # REQUIRED for trusted publishing
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.11"
|
|
23
|
+
|
|
24
|
+
- name: Install build tools
|
|
25
|
+
run: python -m pip install --upgrade build
|
|
26
|
+
|
|
27
|
+
- name: Build package
|
|
28
|
+
run: python -m build
|
|
29
|
+
|
|
30
|
+
- name: Publish to PyPI
|
|
31
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install -e ".[dev,all]"
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: pytest tests/ -v
|
|
31
|
+
|
|
32
|
+
- name: Check code formatting
|
|
33
|
+
run: black --check src/ tests/
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to agent-tether will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-02-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Runner protocol and registry framework
|
|
12
|
+
- `Runner` protocol for agent backends
|
|
13
|
+
- `RunnerEvents` protocol with 10 event callbacks
|
|
14
|
+
- `RunnerRegistry` for factory-based runner creation
|
|
15
|
+
- `RunnerUnavailableError` exception
|
|
16
|
+
- Comprehensive test suite (41 tests)
|
|
17
|
+
- Full documentation in README.md
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Simplified package scope to bridges + runner protocol only
|
|
21
|
+
- Updated documentation to reflect focused scope
|
|
22
|
+
- Improved example code in README
|
|
23
|
+
|
|
24
|
+
## [0.1.0] - 2026-02-11
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- Initial release with chat platform bridges
|
|
28
|
+
- Telegram bridge with forum topics and inline keyboards
|
|
29
|
+
- Slack bridge with socket mode and threaded conversations
|
|
30
|
+
- Discord bridge with pairing system and thread management
|
|
31
|
+
- Auto-approve engine with per-thread, per-tool, and per-directory timers
|
|
32
|
+
- Command handling with built-in commands and custom registry
|
|
33
|
+
- Message formatting utilities (markdown, chunking, tool inputs)
|
|
34
|
+
- Thread state management with JSON persistence
|
|
35
|
+
- Notification batching and error debouncing
|
|
36
|
+
- Event subscriber system for bridge events
|
|
37
|
+
- Comprehensive test coverage
|
|
38
|
+
- MIT License
|
|
39
|
+
- Full documentation
|
|
40
|
+
|
|
41
|
+
[0.2.0]: https://github.com/xithing/agent-tether/releases/tag/v0.2.0
|
|
42
|
+
[0.1.0]: https://github.com/xithing/agent-tether/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xithing
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-tether
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Tether your AI agents to human oversight through Telegram, Slack, and Discord
|
|
5
|
+
Project-URL: Homepage, https://github.com/xithing/agent-tether
|
|
6
|
+
Project-URL: Repository, https://github.com/xithing/agent-tether
|
|
7
|
+
Project-URL: Issues, https://github.com/xithing/agent-tether/issues
|
|
8
|
+
Author: xithing
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,ai,approval,discord,human-in-the-loop,slack,telegram
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Communications :: Chat
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: pydantic>=2.0
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: discord-py>=2.0; extra == 'all'
|
|
26
|
+
Requires-Dist: python-telegram-bot>=21.0; extra == 'all'
|
|
27
|
+
Requires-Dist: slack-bolt>=1.0; extra == 'all'
|
|
28
|
+
Requires-Dist: slack-sdk>=3.0; extra == 'all'
|
|
29
|
+
Provides-Extra: bridges
|
|
30
|
+
Requires-Dist: discord-py>=2.0; extra == 'bridges'
|
|
31
|
+
Requires-Dist: python-telegram-bot>=21.0; extra == 'bridges'
|
|
32
|
+
Requires-Dist: slack-bolt>=1.0; extra == 'bridges'
|
|
33
|
+
Requires-Dist: slack-sdk>=3.0; extra == 'bridges'
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: black; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
39
|
+
Provides-Extra: discord
|
|
40
|
+
Requires-Dist: discord-py>=2.0; extra == 'discord'
|
|
41
|
+
Provides-Extra: slack
|
|
42
|
+
Requires-Dist: slack-bolt>=1.0; extra == 'slack'
|
|
43
|
+
Requires-Dist: slack-sdk>=3.0; extra == 'slack'
|
|
44
|
+
Provides-Extra: telegram
|
|
45
|
+
Requires-Dist: python-telegram-bot>=21.0; extra == 'telegram'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# agent-tether
|
|
49
|
+
|
|
50
|
+
Connect to your AI coding agents through Telegram, Slack, and Discord. Control them from your phone while they work on your laptop.
|
|
51
|
+
|
|
52
|
+
A Python library that handles the chat-platform integration for AI agent supervision: thread management, approval flows with inline buttons, auto-approve timers, message formatting, and command handling. You provide callbacks for your application logic.
|
|
53
|
+
|
|
54
|
+
**Use Cases:**
|
|
55
|
+
- Monitor Claude/Codex/Aider from your phone while agents run locally
|
|
56
|
+
- Get approval requests as Telegram notifications with one-tap approve/deny
|
|
57
|
+
- Set auto-approve timers for trusted operations
|
|
58
|
+
- Send additional input or stop agents remotely
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install agent-tether[telegram] # Telegram support
|
|
64
|
+
pip install agent-tether[slack] # Slack support
|
|
65
|
+
pip install agent-tether[discord] # Discord support
|
|
66
|
+
pip install agent-tether[all] # All platforms
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import asyncio
|
|
73
|
+
from agent_tether import TelegramBridge, Handlers
|
|
74
|
+
|
|
75
|
+
async def on_input(thread_id: str, text: str, username: str | None):
|
|
76
|
+
print(f"[{thread_id}] {username}: {text}")
|
|
77
|
+
|
|
78
|
+
async def on_approval_response(thread_id: str, request_id: str, approved: bool, **kwargs):
|
|
79
|
+
print(f"[{thread_id}] {'Approved' if approved else 'Denied'} {request_id}")
|
|
80
|
+
|
|
81
|
+
bridge = TelegramBridge(
|
|
82
|
+
token="BOT_TOKEN",
|
|
83
|
+
forum_group_id=123456,
|
|
84
|
+
handlers=Handlers(
|
|
85
|
+
on_input=on_input,
|
|
86
|
+
on_approval_response=on_approval_response,
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async def main():
|
|
91
|
+
await bridge.start()
|
|
92
|
+
|
|
93
|
+
thread_id = await bridge.create_thread("My Agent Task")
|
|
94
|
+
await bridge.send_output(thread_id, "Starting work on your request...")
|
|
95
|
+
|
|
96
|
+
await bridge.send_approval_request(
|
|
97
|
+
thread_id,
|
|
98
|
+
request_id="req_123",
|
|
99
|
+
tool_name="Bash",
|
|
100
|
+
description='{"command": "rm -rf /tmp/cache"}',
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
await bridge.wait_until_stopped()
|
|
104
|
+
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Runner Protocol Example
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from agent_tether.runner import Runner, RunnerEvents, RunnerRegistry
|
|
112
|
+
|
|
113
|
+
# Implement event callbacks
|
|
114
|
+
class MyEventHandler:
|
|
115
|
+
async def on_output(self, session_id, stream, text, **kwargs):
|
|
116
|
+
print(f"[{session_id}] {text}", end="")
|
|
117
|
+
|
|
118
|
+
async def on_error(self, session_id, code, message):
|
|
119
|
+
print(f"ERROR: {message}")
|
|
120
|
+
|
|
121
|
+
async def on_exit(self, session_id, exit_code):
|
|
122
|
+
print(f"Session {session_id} exited with code {exit_code}")
|
|
123
|
+
|
|
124
|
+
async def on_permission_request(self, session_id, request_id, tool_name, tool_input, **kwargs):
|
|
125
|
+
print(f"Permission requested for {tool_name}")
|
|
126
|
+
|
|
127
|
+
# ... other event callbacks
|
|
128
|
+
|
|
129
|
+
# Register runners
|
|
130
|
+
registry = RunnerRegistry()
|
|
131
|
+
|
|
132
|
+
def my_runner_factory(events, config):
|
|
133
|
+
# Return a Runner implementation
|
|
134
|
+
return MyCustomRunner(events, **config)
|
|
135
|
+
|
|
136
|
+
registry.register("my-runner", my_runner_factory)
|
|
137
|
+
|
|
138
|
+
# Create and use runner
|
|
139
|
+
events = MyEventHandler()
|
|
140
|
+
runner = registry.create("my-runner", events, api_key="...", model="...")
|
|
141
|
+
|
|
142
|
+
await runner.start("sess_1", "Build a web app", approval_choice=1)
|
|
143
|
+
await runner.send_input("sess_1", "Add a login page")
|
|
144
|
+
await runner.stop("sess_1")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Features
|
|
148
|
+
|
|
149
|
+
### Chat Platform Bridges
|
|
150
|
+
- **Telegram** — Forum topics, inline keyboard approval buttons, typing indicators, HTML formatting
|
|
151
|
+
- **Slack** — Socket mode, threaded conversations, text-based approval commands
|
|
152
|
+
- **Discord** — Channel threads, pairing/authorization system, text-based approvals
|
|
153
|
+
- **Approval engine** — Auto-approve timers (per-thread, per-tool, per-directory), batched notifications
|
|
154
|
+
- **Commands** — Built-in `/help`, `/stop`, `/status`, `/usage` + custom command registry
|
|
155
|
+
- **Formatting** — Tool input JSON → readable text, markdown conversion, message chunking
|
|
156
|
+
|
|
157
|
+
### Runner Protocol
|
|
158
|
+
- **Protocol definitions** — `Runner` and `RunnerEvents` interfaces for agent backends
|
|
159
|
+
- **RunnerRegistry** — Factory pattern for discovering and creating runners
|
|
160
|
+
- **Pluggable adapters** — Clean protocol for implementing custom agent backends
|
|
161
|
+
- **Event-driven** — Runners report progress via callbacks (output, errors, permissions, etc.)
|
|
162
|
+
|
|
163
|
+
## Documentation
|
|
164
|
+
|
|
165
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — Version history and changes
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
Contributions welcome! Please feel free to submit a Pull Request.
|
|
170
|
+
|
|
171
|
+
## Related Projects
|
|
172
|
+
|
|
173
|
+
This library is extracted from [Tether](https://github.com/xithing/tether), a full-featured control plane for supervising AI coding agents.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT - see [LICENSE](LICENSE) for details
|
|
178
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# agent-tether
|
|
2
|
+
|
|
3
|
+
Connect to your AI coding agents through Telegram, Slack, and Discord. Control them from your phone while they work on your laptop.
|
|
4
|
+
|
|
5
|
+
A Python library that handles the chat-platform integration for AI agent supervision: thread management, approval flows with inline buttons, auto-approve timers, message formatting, and command handling. You provide callbacks for your application logic.
|
|
6
|
+
|
|
7
|
+
**Use Cases:**
|
|
8
|
+
- Monitor Claude/Codex/Aider from your phone while agents run locally
|
|
9
|
+
- Get approval requests as Telegram notifications with one-tap approve/deny
|
|
10
|
+
- Set auto-approve timers for trusted operations
|
|
11
|
+
- Send additional input or stop agents remotely
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install agent-tether[telegram] # Telegram support
|
|
17
|
+
pip install agent-tether[slack] # Slack support
|
|
18
|
+
pip install agent-tether[discord] # Discord support
|
|
19
|
+
pip install agent-tether[all] # All platforms
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import asyncio
|
|
26
|
+
from agent_tether import TelegramBridge, Handlers
|
|
27
|
+
|
|
28
|
+
async def on_input(thread_id: str, text: str, username: str | None):
|
|
29
|
+
print(f"[{thread_id}] {username}: {text}")
|
|
30
|
+
|
|
31
|
+
async def on_approval_response(thread_id: str, request_id: str, approved: bool, **kwargs):
|
|
32
|
+
print(f"[{thread_id}] {'Approved' if approved else 'Denied'} {request_id}")
|
|
33
|
+
|
|
34
|
+
bridge = TelegramBridge(
|
|
35
|
+
token="BOT_TOKEN",
|
|
36
|
+
forum_group_id=123456,
|
|
37
|
+
handlers=Handlers(
|
|
38
|
+
on_input=on_input,
|
|
39
|
+
on_approval_response=on_approval_response,
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
async def main():
|
|
44
|
+
await bridge.start()
|
|
45
|
+
|
|
46
|
+
thread_id = await bridge.create_thread("My Agent Task")
|
|
47
|
+
await bridge.send_output(thread_id, "Starting work on your request...")
|
|
48
|
+
|
|
49
|
+
await bridge.send_approval_request(
|
|
50
|
+
thread_id,
|
|
51
|
+
request_id="req_123",
|
|
52
|
+
tool_name="Bash",
|
|
53
|
+
description='{"command": "rm -rf /tmp/cache"}',
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
await bridge.wait_until_stopped()
|
|
57
|
+
|
|
58
|
+
asyncio.run(main())
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Runner Protocol Example
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from agent_tether.runner import Runner, RunnerEvents, RunnerRegistry
|
|
65
|
+
|
|
66
|
+
# Implement event callbacks
|
|
67
|
+
class MyEventHandler:
|
|
68
|
+
async def on_output(self, session_id, stream, text, **kwargs):
|
|
69
|
+
print(f"[{session_id}] {text}", end="")
|
|
70
|
+
|
|
71
|
+
async def on_error(self, session_id, code, message):
|
|
72
|
+
print(f"ERROR: {message}")
|
|
73
|
+
|
|
74
|
+
async def on_exit(self, session_id, exit_code):
|
|
75
|
+
print(f"Session {session_id} exited with code {exit_code}")
|
|
76
|
+
|
|
77
|
+
async def on_permission_request(self, session_id, request_id, tool_name, tool_input, **kwargs):
|
|
78
|
+
print(f"Permission requested for {tool_name}")
|
|
79
|
+
|
|
80
|
+
# ... other event callbacks
|
|
81
|
+
|
|
82
|
+
# Register runners
|
|
83
|
+
registry = RunnerRegistry()
|
|
84
|
+
|
|
85
|
+
def my_runner_factory(events, config):
|
|
86
|
+
# Return a Runner implementation
|
|
87
|
+
return MyCustomRunner(events, **config)
|
|
88
|
+
|
|
89
|
+
registry.register("my-runner", my_runner_factory)
|
|
90
|
+
|
|
91
|
+
# Create and use runner
|
|
92
|
+
events = MyEventHandler()
|
|
93
|
+
runner = registry.create("my-runner", events, api_key="...", model="...")
|
|
94
|
+
|
|
95
|
+
await runner.start("sess_1", "Build a web app", approval_choice=1)
|
|
96
|
+
await runner.send_input("sess_1", "Add a login page")
|
|
97
|
+
await runner.stop("sess_1")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Features
|
|
101
|
+
|
|
102
|
+
### Chat Platform Bridges
|
|
103
|
+
- **Telegram** — Forum topics, inline keyboard approval buttons, typing indicators, HTML formatting
|
|
104
|
+
- **Slack** — Socket mode, threaded conversations, text-based approval commands
|
|
105
|
+
- **Discord** — Channel threads, pairing/authorization system, text-based approvals
|
|
106
|
+
- **Approval engine** — Auto-approve timers (per-thread, per-tool, per-directory), batched notifications
|
|
107
|
+
- **Commands** — Built-in `/help`, `/stop`, `/status`, `/usage` + custom command registry
|
|
108
|
+
- **Formatting** — Tool input JSON → readable text, markdown conversion, message chunking
|
|
109
|
+
|
|
110
|
+
### Runner Protocol
|
|
111
|
+
- **Protocol definitions** — `Runner` and `RunnerEvents` interfaces for agent backends
|
|
112
|
+
- **RunnerRegistry** — Factory pattern for discovering and creating runners
|
|
113
|
+
- **Pluggable adapters** — Clean protocol for implementing custom agent backends
|
|
114
|
+
- **Event-driven** — Runners report progress via callbacks (output, errors, permissions, etc.)
|
|
115
|
+
|
|
116
|
+
## Documentation
|
|
117
|
+
|
|
118
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — Version history and changes
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
Contributions welcome! Please feel free to submit a Pull Request.
|
|
123
|
+
|
|
124
|
+
## Related Projects
|
|
125
|
+
|
|
126
|
+
This library is extracted from [Tether](https://github.com/xithing/tether), a full-featured control plane for supervising AI coding agents.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT - see [LICENSE](LICENSE) for details
|
|
131
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agent-tether"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Tether your AI agents to human oversight through Telegram, Slack, and Discord"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "xithing" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "agent", "telegram", "slack", "discord", "approval", "human-in-the-loop"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Communications :: Chat",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"pydantic>=2.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
# Bridges
|
|
34
|
+
telegram = ["python-telegram-bot>=21.0"]
|
|
35
|
+
slack = ["slack-sdk>=3.0", "slack-bolt>=1.0"]
|
|
36
|
+
discord = ["discord.py>=2.0"]
|
|
37
|
+
bridges = [
|
|
38
|
+
"python-telegram-bot>=21.0",
|
|
39
|
+
"slack-sdk>=3.0",
|
|
40
|
+
"slack-bolt>=1.0",
|
|
41
|
+
"discord.py>=2.0",
|
|
42
|
+
]
|
|
43
|
+
# All features (currently just bridges)
|
|
44
|
+
all = [
|
|
45
|
+
"python-telegram-bot>=21.0",
|
|
46
|
+
"slack-sdk>=3.0",
|
|
47
|
+
"slack-bolt>=1.0",
|
|
48
|
+
"discord.py>=2.0",
|
|
49
|
+
]
|
|
50
|
+
# Development
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=8.0",
|
|
53
|
+
"pytest-asyncio>=0.24",
|
|
54
|
+
"black",
|
|
55
|
+
"mypy",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[project.urls]
|
|
59
|
+
Homepage = "https://github.com/xithing/agent-tether"
|
|
60
|
+
Repository = "https://github.com/xithing/agent-tether"
|
|
61
|
+
Issues = "https://github.com/xithing/agent-tether/issues"
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.wheel]
|
|
64
|
+
packages = ["src/agent_tether"]
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
testpaths = ["tests"]
|
|
68
|
+
asyncio_mode = "auto"
|
|
69
|
+
|
|
70
|
+
[tool.black]
|
|
71
|
+
line-length = 99
|
|
72
|
+
target-version = ["py311"]
|
|
73
|
+
|
|
74
|
+
[tool.mypy]
|
|
75
|
+
python_version = "3.11"
|
|
76
|
+
strict = true
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""agent-tether: Tether your AI agents to human oversight through chat platforms."""
|
|
2
|
+
|
|
3
|
+
from agent_tether.formatting import format_tool_input, humanize_key, humanize_enum_value
|
|
4
|
+
from agent_tether.models import ApprovalRequest, CommandDef, Handlers
|
|
5
|
+
from agent_tether.router import BridgeRouter
|
|
6
|
+
from agent_tether.subscriber import EventSubscriber
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
# Core bridge components
|
|
10
|
+
"ApprovalRequest",
|
|
11
|
+
"CommandDef",
|
|
12
|
+
"EventSubscriber",
|
|
13
|
+
"Handlers",
|
|
14
|
+
"BridgeRouter",
|
|
15
|
+
# Platform bridges (lazy loaded)
|
|
16
|
+
"TelegramBridge",
|
|
17
|
+
"SlackBridge",
|
|
18
|
+
"DiscordBridge",
|
|
19
|
+
# Formatting utilities
|
|
20
|
+
"format_tool_input",
|
|
21
|
+
"humanize_key",
|
|
22
|
+
"humanize_enum_value",
|
|
23
|
+
# Runner module (lazy loaded)
|
|
24
|
+
"runner",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def __getattr__(name: str):
|
|
29
|
+
"""Lazy imports for platform bridges and session module — avoids requiring optional deps at import time."""
|
|
30
|
+
if name == "TelegramBridge":
|
|
31
|
+
try:
|
|
32
|
+
from agent_tether.platforms.telegram.bridge import TelegramBridge
|
|
33
|
+
|
|
34
|
+
return TelegramBridge
|
|
35
|
+
except ImportError:
|
|
36
|
+
raise ImportError(
|
|
37
|
+
"TelegramBridge requires python-telegram-bot. "
|
|
38
|
+
"Install with: pip install agent-tether[telegram]"
|
|
39
|
+
) from None
|
|
40
|
+
if name == "SlackBridge":
|
|
41
|
+
try:
|
|
42
|
+
from agent_tether.platforms.slack.bridge import SlackBridge
|
|
43
|
+
|
|
44
|
+
return SlackBridge
|
|
45
|
+
except ImportError:
|
|
46
|
+
raise ImportError(
|
|
47
|
+
"SlackBridge requires slack-sdk and slack-bolt. "
|
|
48
|
+
"Install with: pip install agent-tether[slack]"
|
|
49
|
+
) from None
|
|
50
|
+
if name == "DiscordBridge":
|
|
51
|
+
try:
|
|
52
|
+
from agent_tether.platforms.discord.bridge import DiscordBridge
|
|
53
|
+
|
|
54
|
+
return DiscordBridge
|
|
55
|
+
except ImportError:
|
|
56
|
+
raise ImportError(
|
|
57
|
+
"DiscordBridge requires discord.py. "
|
|
58
|
+
"Install with: pip install agent-tether[discord]"
|
|
59
|
+
) from None
|
|
60
|
+
if name == "runner":
|
|
61
|
+
from agent_tether import runner as runner_module
|
|
62
|
+
|
|
63
|
+
return runner_module
|
|
64
|
+
raise AttributeError(f"module 'agent_tether' has no attribute {name!r}")
|