agents_control 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE +202 -0
- data/README.md +282 -0
- data/exe/agents_control +6 -0
- data/lib/agents_control/agents/base.rb +52 -0
- data/lib/agents_control/agents/claude_code.rb +243 -0
- data/lib/agents_control/anchors/scheduler.rb +161 -0
- data/lib/agents_control/channels/base.rb +27 -0
- data/lib/agents_control/channels/telegram/api.rb +179 -0
- data/lib/agents_control/channels/telegram/bot.rb +146 -0
- data/lib/agents_control/channels/telegram/channel.rb +251 -0
- data/lib/agents_control/channels/telegram/chunker.rb +64 -0
- data/lib/agents_control/channels/telegram/keyboards.rb +129 -0
- data/lib/agents_control/channels/telegram/markdown.rb +59 -0
- data/lib/agents_control/channels/telegram/router.rb +482 -0
- data/lib/agents_control/channels/telegram/settings_menu.rb +112 -0
- data/lib/agents_control/cli.rb +347 -0
- data/lib/agents_control/config.rb +178 -0
- data/lib/agents_control/console.rb +320 -0
- data/lib/agents_control/daemon.rb +251 -0
- data/lib/agents_control/dispatcher.rb +163 -0
- data/lib/agents_control/doctor.rb +234 -0
- data/lib/agents_control/event.rb +111 -0
- data/lib/agents_control/executor.rb +83 -0
- data/lib/agents_control/hooks/server.rb +197 -0
- data/lib/agents_control/keyboard.rb +90 -0
- data/lib/agents_control/menu.rb +100 -0
- data/lib/agents_control/pending.rb +70 -0
- data/lib/agents_control/process_probe.rb +136 -0
- data/lib/agents_control/prompt.rb +227 -0
- data/lib/agents_control/rate_limit_watcher.rb +202 -0
- data/lib/agents_control/registry.rb +115 -0
- data/lib/agents_control/reply.rb +41 -0
- data/lib/agents_control/screen_watcher.rb +158 -0
- data/lib/agents_control/secrets.rb +256 -0
- data/lib/agents_control/service.rb +165 -0
- data/lib/agents_control/session.rb +66 -0
- data/lib/agents_control/store.rb +132 -0
- data/lib/agents_control/terminals/base.rb +76 -0
- data/lib/agents_control/terminals/iterm2.rb +167 -0
- data/lib/agents_control/terminals/null.rb +27 -0
- data/lib/agents_control/terminals/tmux.rb +106 -0
- data/lib/agents_control/transcript.rb +123 -0
- data/lib/agents_control/version.rb +5 -0
- data/lib/agents_control/which.rb +59 -0
- data/lib/agents_control.rb +52 -0
- metadata +102 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7396050fbeeb69af3c8fd481608facce44bce68072dc043f03fa0bc34138de5b
|
|
4
|
+
data.tar.gz: '05597674bc05d4bb1142e1295c264f737caf7be5afee724616ec4e0673db3870'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: efe615074ece6b5898eecaaa66fdc882c46d2b6a74c715b18768b347ab3af3ef180774930dc46d5648461a006b96059bd265c67f676e45ed2eadcc0017bc6ee2
|
|
7
|
+
data.tar.gz: 6137a2654888b576860d1a8463e5aff520ace959c687974e6bd20e3425ba40f98673391517c30dc7d1c54a58bbbbad070aa2253891d6553458f774354c9c6543
|
data/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 Sapar Kurmanov
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
data/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# agents_control
|
|
2
|
+
|
|
3
|
+
A remote for iTerm2 and its AI agents (Claude Code, Codex) from Telegram:
|
|
4
|
+
tab list, commands, screen, new sessions. For Claude Code — also the
|
|
5
|
+
agent's questions and permission requests: it stops, buttons show up in
|
|
6
|
+
Telegram, you answer, the session unblocks.
|
|
7
|
+
|
|
8
|
+
Everyone runs their own bot — the token lives in the Keychain or
|
|
9
|
+
libsecret, never in files or in git. The daemon is invisible from
|
|
10
|
+
outside: the port only binds to 127.0.0.1, there are no incoming connections.
|
|
11
|
+
|
|
12
|
+
> **Status:** terminal control and Claude Code work in full. Codex
|
|
13
|
+
> sessions are visible and controllable through the terminal (like any
|
|
14
|
+
> tab), but without the question relay: its hooks don't give us
|
|
15
|
+
> anything to hook into for that yet.
|
|
16
|
+
|
|
17
|
+
## Platforms and requirements
|
|
18
|
+
|
|
19
|
+
- **macOS** — terminal backend: iTerm2 (via AppleScript) or tmux;
|
|
20
|
+
secrets: Keychain; autostart: launchd.
|
|
21
|
+
- **Linux** — terminal backend: tmux; secrets: libsecret; autostart: systemd.
|
|
22
|
+
- **Windows is not supported.**
|
|
23
|
+
- **Ruby 3.1+.** The only external dependency is `thor` (pure Ruby, no
|
|
24
|
+
C extensions) — installing it compiles nothing.
|
|
25
|
+
|
|
26
|
+
On macOS, terminal control (sending commands, reading the screen,
|
|
27
|
+
creating tabs) works through iTerm2 or tmux — Terminal.app isn't
|
|
28
|
+
supported. Notifications about questions and permissions aren't tied to
|
|
29
|
+
a terminal at all: their source is the agent's hooks, which work
|
|
30
|
+
everywhere, including sessions with no terminal (a VS Code session, for
|
|
31
|
+
instance), but for now only for Claude Code.
|
|
32
|
+
|
|
33
|
+
## Why
|
|
34
|
+
|
|
35
|
+
You need to run a command in a session, check the screen, or switch to
|
|
36
|
+
a tab, and you're not at the computer — now you can do that from
|
|
37
|
+
Telegram. If the session is Claude Code, there's a bonus too: the agent
|
|
38
|
+
stops and waits for an answer — the question and its buttons arrive in
|
|
39
|
+
Telegram, no need to go home just to say "continue."
|
|
40
|
+
|
|
41
|
+
## How it works
|
|
42
|
+
|
|
43
|
+
Hooks, not screen scraping. Claude Code itself calls agents_control
|
|
44
|
+
when it stops — the hook waits for an answer and passes the decision
|
|
45
|
+
back into the session. There's currently one adapter, for Claude Code;
|
|
46
|
+
a new agent needs a file with the same interface. Codex didn't fit:
|
|
47
|
+
its hooks only see shell commands and only understand `deny` — there's
|
|
48
|
+
no "agent stopped" event to hook into at all.
|
|
49
|
+
|
|
50
|
+
The terminal is iTerm2 or tmux, and each session picks its own backend.
|
|
51
|
+
Terminalless sessions (VS Code) are visible and answer hooks too — they
|
|
52
|
+
just have nothing to type into and nowhere to read a screen from.
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
git clone https://github.com/saparjohnick/agents_control
|
|
58
|
+
cd agents_control
|
|
59
|
+
bundle install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or straight from Claude Code, as a plugin — the repo doubles as a marketplace:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
/plugin marketplace add saparjohnick/agents_control
|
|
66
|
+
/plugin install agents-control@agents-control
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The plugin doesn't replace the install above — it's just a way to find
|
|
70
|
+
the tool and get install instructions without leaving Claude Code.
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
agents_control # opens the console and stays in the tab
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The tool lives in a tab: while it's open, it listens to Telegram and
|
|
79
|
+
receives agent events. Commands inside start with a slash, same as the bot's:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
> /sessions sessions with a live agent
|
|
83
|
+
> /tabs all terminal tabs
|
|
84
|
+
> /away intercept agent questions (before stepping out)
|
|
85
|
+
> /settings settings; /settings away — toggle
|
|
86
|
+
> /doctor check that everything is in place
|
|
87
|
+
> /quit quit
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
State icons: `⏳` working · `▸` at a shell prompt · `🖥` no terminal
|
|
91
|
+
(VS Code) · `·` everything else.
|
|
92
|
+
|
|
93
|
+
One-off commands exist too — `agents_control sessions`, `doctor`,
|
|
94
|
+
`daemon` — but the normal way to run it is an open console.
|
|
95
|
+
|
|
96
|
+
### Telegram
|
|
97
|
+
|
|
98
|
+
Create a bot with [@BotFather](https://t.me/BotFather) and run the wizard:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
agents_control setup # asks for the token, waits for your /start
|
|
102
|
+
agents_control # after that, just open the console
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The wizard catches your `chat_id` from your own message and adds it to
|
|
106
|
+
the allowed list — no need to type in a long number by hand.
|
|
107
|
+
|
|
108
|
+
Bot commands:
|
|
109
|
+
|
|
110
|
+
| Command | What it does |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `/agents` | sessions with a live agent |
|
|
113
|
+
| `/tabs` | all terminal tabs |
|
|
114
|
+
| `/screen N` | show a tab's screen |
|
|
115
|
+
| `/focus N` | switch to a tab |
|
|
116
|
+
| `/run N command` | run a command in a tab |
|
|
117
|
+
| `/new [directory]` | create a tab |
|
|
118
|
+
| `/away` | intercept agent questions |
|
|
119
|
+
| `/status` | current status |
|
|
120
|
+
|
|
121
|
+
The number `N` comes from the last list shown.
|
|
122
|
+
|
|
123
|
+
This list also populates Telegram's own `/` command menu automatically
|
|
124
|
+
— `setup` and every daemon start publish it via the Bot API, no manual
|
|
125
|
+
BotFather step needed. If the menu still shows only `/start` after
|
|
126
|
+
that, it's Telegram's client caching the old list, not a missing step
|
|
127
|
+
on your end: close and reopen the chat, or restart the Telegram app,
|
|
128
|
+
to force it to refresh.
|
|
129
|
+
|
|
130
|
+
### Two modes
|
|
131
|
+
|
|
132
|
+
While you're at the keyboard, intercepting agent questions is
|
|
133
|
+
counterproductive: you'll answer in the terminal faster than you can
|
|
134
|
+
reach for your phone, and a blocked hook keeps the dialog from ever
|
|
135
|
+
appearing on screen. So there are two modes:
|
|
136
|
+
|
|
137
|
+
- **present** (default) — questions are mirrored to Telegram but stay
|
|
138
|
+
in the terminal;
|
|
139
|
+
- **away** (`/away`) — a question arrives with buttons and waits for a
|
|
140
|
+
reply; the agent stands by until you answer or time runs out.
|
|
141
|
+
|
|
142
|
+
A question can also be answered by replying directly to the message —
|
|
143
|
+
it goes to the right session, even with several tabs open.
|
|
144
|
+
|
|
145
|
+
Silence is treated as a refusal. If nobody answered while you were
|
|
146
|
+
out, the action doesn't happen, and the session just keeps waiting in the terminal.
|
|
147
|
+
|
|
148
|
+
AskUserQuestion is the one exception to all of this: its answer never
|
|
149
|
+
travels back through the hook at all, so there's nothing to block on —
|
|
150
|
+
it always arrives with real buttons for each option, in both modes.
|
|
151
|
+
Tapping one types that choice straight into the terminal, the same
|
|
152
|
+
keystroke you'd type by hand. An open-ended option ("something else,"
|
|
153
|
+
"explain what you mean") has no button — just reply to the message
|
|
154
|
+
with your own words instead. With more than one question in a single
|
|
155
|
+
batch, or several tabs sharing the same directory so the target pane
|
|
156
|
+
is ambiguous, buttons are skipped in favor of a plain reply, since
|
|
157
|
+
guessing at the terminal's exact sequencing there risks typing into
|
|
158
|
+
the wrong place.
|
|
159
|
+
|
|
160
|
+
A "continue" reply is sent automatically, but tool permissions aren't.
|
|
161
|
+
These are two independent settings on purpose: merged into one, they'd
|
|
162
|
+
produce an agent that approves itself everything while nobody's
|
|
163
|
+
watching. A question that offers a choice ("rewrite it or leave it?")
|
|
164
|
+
is never answered automatically, even if it contains the word "continue."
|
|
165
|
+
|
|
166
|
+
### Hooks
|
|
167
|
+
|
|
168
|
+
The daemon connects hooks on start and removes them on stop — otherwise
|
|
169
|
+
the agent prints a warning about an unreachable address in every
|
|
170
|
+
session. If the daemon crashed and the hooks are still there:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
agents_control hooks # check status
|
|
174
|
+
agents_control hooks uninstall
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Entries in `~/.claude/settings.json` are tagged, and other settings
|
|
178
|
+
aren't touched: installing and removing return the file to exactly its original shape.
|
|
179
|
+
|
|
180
|
+
## Rate-limit anchors
|
|
181
|
+
|
|
182
|
+
A five-hour window starts at the minute of the first message and
|
|
183
|
+
expires exactly three hundred minutes later. An anchor doesn't add a
|
|
184
|
+
single extra token — it moves window boundaries to where they're
|
|
185
|
+
convenient: the difference between "the window reset at 2:37pm,
|
|
186
|
+
mid-work" and "windows at exactly 7am, noon, and 5pm."
|
|
187
|
+
|
|
188
|
+
The ping uses a **cheap model**, and that's not economizing for its own
|
|
189
|
+
sake. The five-hour window is shared across the account, but weekly
|
|
190
|
+
limits are tracked per model family: an anchor on opus would spend the
|
|
191
|
+
scarcest bucket for an effect haiku gives for free.
|
|
192
|
+
|
|
193
|
+
Turned on in `/settings`. If you were working recently and a window is
|
|
194
|
+
already open, the ping is skipped — the daemon sees every agent event
|
|
195
|
+
and knows this without polling anything.
|
|
196
|
+
|
|
197
|
+
On macOS, a 7am anchor won't fire if the laptop is asleep: `doctor`
|
|
198
|
+
catches this and suggests `pmset repeat wakeorpoweron`.
|
|
199
|
+
|
|
200
|
+
## Watchers
|
|
201
|
+
|
|
202
|
+
Hooks see the agent's own decisions, but not everything: the CLI's own
|
|
203
|
+
local menus (model switch, folder trust) and text on screen (a
|
|
204
|
+
rate-limit message) aren't covered by hooks at all — these events never
|
|
205
|
+
produce a single hook call. Two independent watchers handle them,
|
|
206
|
+
working over the screen rather than over Claude Code. Both go through
|
|
207
|
+
the full `Registry` — they see bare iTerm2 tabs and tmux panes alike.
|
|
208
|
+
|
|
209
|
+
**CLI menus** (`terminal.watch_menus`, on by default, polled every 20
|
|
210
|
+
seconds — `terminal.menu_poll_interval`). Notices the "❯ 1. … / 2. …"
|
|
211
|
+
pattern Claude Code uses to draw any choice, and sends it to Telegram
|
|
212
|
+
as buttons — pressing one types the option's number straight into the pane.
|
|
213
|
+
|
|
214
|
+
**Limit reset** (`answers.auto_resume_after_limit`, on by default,
|
|
215
|
+
polled once a minute — `terminal.rate_limit_poll_interval`). Notices a
|
|
216
|
+
message like "resets 3pm (UTC)" / "resets Oct 9, 10am" and types the
|
|
217
|
+
continuation itself once the time comes (with a minute of headroom).
|
|
218
|
+
|
|
219
|
+
## Checking and autostart
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
agents_control doctor # is everything in place
|
|
223
|
+
agents_control service install # autostart (launchd / systemd)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`doctor` checks **the environment the daemon will actually get**, not
|
|
227
|
+
the current one: an interactive shell can show a different Ruby and a
|
|
228
|
+
different PATH than the process a service manager launches separately
|
|
229
|
+
— for instance, if a Ruby version manager puts a broken shim on PATH
|
|
230
|
+
ahead of the working interpreter.
|
|
231
|
+
|
|
232
|
+
That's why the service always starts via an absolute path to the
|
|
233
|
+
interpreter, never through PATH.
|
|
234
|
+
|
|
235
|
+
## Why a tab's title can't be trusted
|
|
236
|
+
|
|
237
|
+
An agent sets a tab's title via an OSC sequence, and the title stays up
|
|
238
|
+
after it exits — a tab with an agent icon isn't necessarily still
|
|
239
|
+
running anything. That's why an agent's presence is confirmed by the
|
|
240
|
+
process tree, and the title is only ever used as a label.
|
|
241
|
+
|
|
242
|
+
## Security
|
|
243
|
+
|
|
244
|
+
This tool lets you run commands on your machine from Telegram. That comes with some rules:
|
|
245
|
+
|
|
246
|
+
- **There's no shared service.** Everyone sets up their own bot with
|
|
247
|
+
@BotFather and connects it with their own token — the daemon talks to
|
|
248
|
+
a bot only you own, not to any third-party infrastructure.
|
|
249
|
+
- **The daemon's port only listens on `127.0.0.1`.** Reaching it from
|
|
250
|
+
outside the machine isn't just blocked by a password, it's physically
|
|
251
|
+
impossible. The daemon talks to Telegram itself via long polling —
|
|
252
|
+
outgoing connections only; no incoming port is opened for this either.
|
|
253
|
+
- **An allowed `chat_id` list is required.** While it's empty, the bot
|
|
254
|
+
answers nobody, even if someone learns its name. The bot's token
|
|
255
|
+
isn't an access secret; the access secret is the chat_id filter.
|
|
256
|
+
- **A leaked bot token is equivalent to remote code execution.**
|
|
257
|
+
- The token is stored in the Keychain (macOS) or libsecret (Linux) —
|
|
258
|
+
the same place as website and Wi-Fi passwords — and never lands in
|
|
259
|
+
the config. There's deliberately no command-line argument for the
|
|
260
|
+
token: it would leak into `ps` and shell history.
|
|
261
|
+
- This protects against leaks through git, config files, `ps`, and
|
|
262
|
+
shell history — not against malicious code already running as the
|
|
263
|
+
same user: like any CLI tool without its own signed `.app`, the
|
|
264
|
+
Keychain entry trusts the `security` utility itself, not
|
|
265
|
+
agents_control specifically, so any process on the same account that
|
|
266
|
+
knows the service name can read the token.
|
|
267
|
+
- Auto-replying "continue" is on by default; automatic tool approval is
|
|
268
|
+
off. These are separate settings on purpose.
|
|
269
|
+
|
|
270
|
+
## Development
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
rake test
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Tests run on minitest, with external commands stubbed via
|
|
277
|
+
`FakeExecutor` — neither iTerm2 nor tmux is needed to run them.
|
|
278
|
+
Fixtures live in `test/fixtures.rb` — recorded output from real commands.
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
Apache 2.0.
|
data/exe/agents_control
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AgentsControl
|
|
4
|
+
module Agents
|
|
5
|
+
# The agent adapter contract.
|
|
6
|
+
#
|
|
7
|
+
# Deliberately tiny. The core is written for a weak agent — one with
|
|
8
|
+
# nothing but a process in a terminal. An agent with full hooks
|
|
9
|
+
# (Claude Code, Codex) just enriches the same events, without
|
|
10
|
+
# changing their shape.
|
|
11
|
+
#
|
|
12
|
+
# If adding a new agent requires changes outside its adapter, the
|
|
13
|
+
# registry, and the hook installer, the seam was drawn wrong.
|
|
14
|
+
class Base
|
|
15
|
+
class << self
|
|
16
|
+
# Name in the registry and in Session#agent.
|
|
17
|
+
def key = raise(NotImplementedError)
|
|
18
|
+
|
|
19
|
+
# Executable names, matched by process basename.
|
|
20
|
+
def binaries = []
|
|
21
|
+
|
|
22
|
+
# Whether this adapter can parse a hook payload of this shape.
|
|
23
|
+
def handles?(_payload) = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def key = self.class.key
|
|
27
|
+
|
|
28
|
+
# What the adapter can do. The core checks this list, not the
|
|
29
|
+
# agent's name.
|
|
30
|
+
#
|
|
31
|
+
# :push — pushes events itself, no polling needed
|
|
32
|
+
# :blocking_reply — can wait for an answer and feed it back to the session
|
|
33
|
+
# :structured_options — brings a ready-made list of answer choices
|
|
34
|
+
def capabilities = []
|
|
35
|
+
|
|
36
|
+
def supports?(capability) = capabilities.include?(capability)
|
|
37
|
+
|
|
38
|
+
# hook payload → Event, or nil if the event isn't interesting.
|
|
39
|
+
def to_event(_payload) = raise(NotImplementedError)
|
|
40
|
+
|
|
41
|
+
# Reply → the hook response body.
|
|
42
|
+
def to_response(_event, _reply) = raise(NotImplementedError)
|
|
43
|
+
|
|
44
|
+
# Register itself in the agent's config so events start arriving.
|
|
45
|
+
def install!(_url, secret: nil) = raise(NotImplementedError)
|
|
46
|
+
|
|
47
|
+
def uninstall! = raise(NotImplementedError)
|
|
48
|
+
|
|
49
|
+
def installed? = false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|