agentp 0.7.0 → 0.9.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.
- package/AGENTS.md +45 -16
- package/CHANGELOG.md +10 -0
- package/README.md +65 -10
- package/bin/agentp +142 -28
- package/bin/ocmux +24 -6
- package/bin/tgagentp +364 -125
- package/debugging_tg_integration.md +5594 -0
- package/lib/opencode.js +55 -12
- package/package.json +1 -1
- package/tgagentp.log +2156 -0
- package/status_report.md +0 -85
package/AGENTS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Three binaries, all registered
|
|
4
4
|
|
|
5
|
-
- `bin/agentp` (Node.js) — registered in `package.json` `"bin"` — pipes stdin → opencode
|
|
5
|
+
- `bin/agentp` (Node.js) — registered in `package.json` `"bin"` — pipes stdin → opencode session
|
|
6
6
|
- `bin/ocmux` (Node.js) — registered in `package.json` `"bin"` — manages opencode tmux sessions
|
|
7
7
|
- `bin/tgagentp` (Node.js) — registered in `package.json` `"bin"` — bridges Telegram bot ↔ opencode TUI
|
|
8
8
|
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
- **`"type": "commonjs"`**, requires Node >= 18.
|
|
15
15
|
- **Linux and macOS compatible.** All tools use portable Node.js APIs only.
|
|
16
16
|
|
|
17
|
+
## Logging conventions (tgagentp)
|
|
18
|
+
|
|
19
|
+
- **stdout** — informational messages (startup, discovery, session switches)
|
|
20
|
+
- **stderr** — errors (always shown) + trace/debug (only with `--verbose` flag)
|
|
21
|
+
- Default usage for clean console: `tgagentp 2>/dev/null`
|
|
22
|
+
- The `log` helper object at the top of `bin/tgagentp` provides `log.info()`, `log.error()`, and `log.debug()`.
|
|
23
|
+
|
|
17
24
|
## `.ocmux.json`
|
|
18
25
|
|
|
19
26
|
Runtime state file created by `bin/ocmux` (contains URL, log path, and window index). **Not in `.gitignore`** — avoid committing it.
|
|
@@ -30,11 +37,14 @@ Extracted from `bin/agentp`; used by both `agentp` and `tgagentp`:
|
|
|
30
37
|
- `listenForFinalAnswer(server, onText?, cancelRef?)` — SSE event stream listener; `cancelRef` allows true server-side abort via `req.destroy()`
|
|
31
38
|
- `listSessions(server, directory?)` — `GET /session`; returns parsed JSON array; optional `?directory=` filter
|
|
32
39
|
- `createSession(server, title?)` — `POST /session`; returns the created `Session` object; optional `title`
|
|
33
|
-
- `updateSession(server, sessionId, title)` — `PATCH /session/:id`; returns the updated `Session` object
|
|
34
|
-
- `sendToSession(server, sessionId, text, agent?)` — `POST /session/:id/message`; returns concatenated text parts from response; optional `agent`
|
|
40
|
+
- `updateSession(server, sessionId, title, agent?)` — `PATCH /session/:id`; returns the updated `Session` object; optional `agent` to set the session's agent
|
|
41
|
+
- `sendToSession(server, sessionId, text, agent?, cancelRef?)` — `POST /session/:id/message`; returns concatenated text parts from response; optional `agent`, optional `cancelRef` for aborting the request
|
|
42
|
+
- `sendToSessionAsync(server, sessionId, text, agent?)` — `POST /session/:id/prompt_async`; returns 204; non-blocking, answer delivered via SSE
|
|
43
|
+
- `respondToPermission(server, sessionId, permissionId, response)` — `POST /session/:id/permissions/:id`
|
|
35
44
|
- `selectSession(server, sessionId)` — `POST /session/:id/select`; tells the TUI to navigate to the given session (silently ignores 404 if the endpoint isn't available in older opencode versions)
|
|
36
45
|
- `listAgents(server)` — `GET /agent`; returns parsed JSON array of agent objects
|
|
37
46
|
- `listProviders(server)` — `GET /provider`; returns parsed JSON array of provider objects
|
|
47
|
+
- `listenForSessionEvents(server, sessionId, callbacks, cancelRef?)` — SSE listener for session events (text parts, permissions, thinking, session.idle)
|
|
38
48
|
|
|
39
49
|
## Shared `lib/ocmux.js`
|
|
40
50
|
|
|
@@ -60,17 +70,28 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
60
70
|
- `GET /event` (SSE stream)
|
|
61
71
|
- `GET /session` — list sessions (optional `?directory=` filter)
|
|
62
72
|
- `POST /session` — create a new session (optional `{ title? }` body)
|
|
63
|
-
- `PATCH /session/:id` — update session properties (`{ title }`)
|
|
73
|
+
- `PATCH /session/:id` — update session properties (`{ title }`, optional `{ agent }`)
|
|
64
74
|
- `POST /session/:id/select` — tell the TUI to navigate to a session (only with TUI attached)
|
|
65
75
|
- `POST /session/:id/message` — send message to session (returns parts synchronously; optional `agent` field)
|
|
76
|
+
- `POST /session/:id/prompt_async` — send message to session (returns 204; answer via SSE)
|
|
66
77
|
- `GET /agent` — list available agents
|
|
67
78
|
- `GET /provider` — list connected providers/models
|
|
68
79
|
|
|
80
|
+
## Agentp gateway (tgagentp ↔ agentp interop)
|
|
81
|
+
|
|
82
|
+
- tgagentp starts a tiny HTTP server (POST /send) on `127.0.0.1`, port from `TGAGENTP_PORT` (default 0 = random).
|
|
83
|
+
- Port is written to `/tmp/tgagentp-port` for agentp discovery.
|
|
84
|
+
- Validates `Authorization` header against `OPENCODE_SERVER_PASSWORD`.
|
|
85
|
+
- agentp `--tg` reads the port file and POSTs `{ text, server }` to the gateway.
|
|
86
|
+
- Messages for the active server are delivered immediately; others are queued per-server.
|
|
87
|
+
- Debounced Telegram notification (configurable via `TGAGENTP_DEBOUNCE_MS`, default 5000ms).
|
|
88
|
+
- Queued messages are flushed on `/servers switch`.
|
|
89
|
+
|
|
69
90
|
## OpenCode tmux session model (ocmux)
|
|
70
91
|
|
|
71
92
|
- Single tmux session named `"Opencode"`
|
|
72
93
|
- Windows named by full project directory path
|
|
73
|
-
- Server pane runs `opencode serve --port 0` (via send-keys → interactive shell for env var support), TUI pane runs `opencode attach --continue`
|
|
94
|
+
- Server pane runs `opencode serve --port 0` (via send-keys → interactive shell for env var support), TUI pane runs `opencode attach --continue`; `ocmux serve --print-logs` adds `--print-logs` to the serve command
|
|
74
95
|
- Log file per project: `/tmp/opencode-serve-<hashDir(dir)>.log` — captured via `tee`, read by ocmux polling loop
|
|
75
96
|
- Per-project `.ocmux.json` with upward directory search (git-like)
|
|
76
97
|
- Auto-restarts dead TUI panes on switch (respawn-pane or split-window)
|
|
@@ -82,10 +103,10 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
82
103
|
|---|---|---|
|
|
83
104
|
| `npm link` | local dev install |
|
|
84
105
|
| `npm install -g .` | alternative local install |
|
|
85
|
-
| `agentp [--qa] [port]` | pipe stdin → opencode
|
|
106
|
+
| `agentp [--qa] [--tg|--no-tg] [port]` | pipe stdin → opencode session, stream answer to stdout (uses session API); `--tg` forwards answer to Telegram |
|
|
86
107
|
| `tgagentp [port]` | bridge Telegram bot ↔ opencode TUI (needs `TELEGRAM_BOT_TOKEN`) |
|
|
87
108
|
| `tgagentp --dev` | enable `/shutdown` command for remote restart |
|
|
88
|
-
| `ocmux serve [dir]` | start opencode serve in a tmux window (primary verb) |
|
|
109
|
+
| `ocmux serve [--print-logs] [dir]` | start opencode serve in a tmux window (primary verb) |
|
|
89
110
|
| `ocmux new [dir]` | alias for `serve` (backwards compat, to be removed in 1.0) |
|
|
90
111
|
| `ocmux kill [dir]` | kill server + tmux window |
|
|
91
112
|
| `ocmux list [-l]` | list running servers |
|
|
@@ -95,23 +116,35 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
95
116
|
|
|
96
117
|
### Pending
|
|
97
118
|
|
|
98
|
-
-
|
|
99
|
-
- `--print-logs` flag for `ocmux serve`
|
|
119
|
+
- `agentp --getLast n` — retrieve last n answers from session history
|
|
100
120
|
|
|
101
121
|
### In Progress
|
|
102
122
|
|
|
103
|
-
|
|
123
|
+
*(none)*
|
|
104
124
|
|
|
105
125
|
### Done
|
|
106
126
|
|
|
107
|
-
-
|
|
127
|
+
- agentp `--qa` full context forwarding to Telegram (rulers, prompt, answer) — 0.9.0
|
|
128
|
+
- agentp resilience: 5s HTTP timeout, pre-send gate check, post-send warning (not hard error) for `--tg`; auto mode silently degrades — 0.9.0
|
|
129
|
+
- tgagentp: `lockedChatId` set at startup to fix race condition with agentp gateway — 0.9.0
|
|
130
|
+
- tgagentp: server error handler + try-catch in gateway handler (no crash on socket errors) — 0.9.0
|
|
131
|
+
- agentp `--tg`/`--no-tg` — gateway forwards answer to Telegram via tgagentp HTTP server; per-server queue with debounced notifications; auto-flush on server switch
|
|
132
|
+
- agentp session API migration — uses `POST /session/:id/message` instead of TUI endpoints
|
|
133
|
+
- ocmux `--print-logs` flag — pass-through to `opencode serve --print-logs` (prints server logs to stderr in the server pane)
|
|
134
|
+
- /agents lists only primary agents with ▶ active marker
|
|
135
|
+
- /agents switch calls `updateSession(agent)` to persist agent on session + `selectSession` to refresh TUI
|
|
136
|
+
- Logging rebalance: stdout for info, stderr for errors/debug/trace, `--verbose` flag, `2>/dev/null` default usage
|
|
137
|
+
- `/shutdown force` — refuses shutdown when busy unless `force` flag is given; goodbye message includes state
|
|
138
|
+
- `/think [on|off|switch]` — toggle real-time forwarding of model thinking messages to the chat; `--think` CLI flag to start enabled
|
|
139
|
+
- Event-driven permission handling (`POST /session/:id/prompt_async` + SSE listener + `/allow`, `/reject`, `/always` commands) — included in v0.7.0
|
|
140
|
+
- TUI navigation on `/sessions switch` and `/sessions new` — `POST /tui/select-session` now works in opencode 1.15.13 (was: tried second, never reached)
|
|
108
141
|
- /sessions new [name] — create a new session, TUI follows via /session/:id/select
|
|
109
142
|
- /sessions rename <name> — rename the current active session
|
|
110
143
|
- Merge /session into /sessions (keep /session as alias)
|
|
111
144
|
- Case-insensitive matching for all switch subcommands (/servers, /sessions, /agents)
|
|
112
145
|
- Fix: /sessions filter includes sessions with directory (TUI sessions) + active session exception
|
|
113
146
|
- Fix: /sessions now shows the active session even without agent set (exception in filter)
|
|
114
|
-
- Fix: /sessions switch and /sessions new call selectSession (TUI navigation
|
|
147
|
+
- Fix: /sessions switch and /sessions new call selectSession (TUI navigation didn't work in opencode < 1.15.x)
|
|
115
148
|
- Fix: active session highlighted with ▶ bullet in session list
|
|
116
149
|
- Fix: discover TUI session on startup and server switch via `discoverActiveSession()`
|
|
117
150
|
- Message splitting (>4096 chars, breaks at newlines)
|
|
@@ -121,10 +154,6 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
121
154
|
- /cancel with true server-side SSE abort via cancelRef
|
|
122
155
|
- /shutdown (--dev mode) with Telegram offset acknowledgment
|
|
123
156
|
|
|
124
|
-
### With Issues
|
|
125
|
-
|
|
126
|
-
- `/sessions switch` and `/sessions new` — `selectSession` tries `POST /session/:id/select` and `POST /tui/select-session` but the TUI pane doesn't navigate to the session. Message routing (via `activeSessionId` + `sendToSession`) works correctly in the background. This is a known limitation — the TUI must be navigated manually to see the conversation in the UI. The underlying opencode API may lack the needed TUI navigation endpoints.
|
|
127
|
-
|
|
128
157
|
### Verified
|
|
129
158
|
|
|
130
159
|
- Password auth support (OPENCODE_SERVER_PASSWORD) for agentp, ocmux, tgagentp
|
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,16 @@ All notable changes to this project will be documented in this file.
|
|
|
8
8
|
- Extract shared OpenCode client logic into `lib/opencode.js`.
|
|
9
9
|
- Add `tgagentp` — bridge a Telegram bot chat with an OpenCode TUI session.
|
|
10
10
|
|
|
11
|
+
## [0.8.0] - 2026-06-03
|
|
12
|
+
|
|
13
|
+
- Fix `/servers` crash: `serverBase is not defined` error.
|
|
14
|
+
- Logging rebalance: stdout for info, stderr for errors/debug with `--verbose` flag (`2>/dev/null` for clean console).
|
|
15
|
+
- `/shutdown force`: refuse shutdown when busy unless `force` flag given.
|
|
16
|
+
- `/think [on|off|switch]`: toggle real-time forwarding of model thinking messages; `--think` CLI flag.
|
|
17
|
+
- Reply quoting: when replying to a Telegram message, prepend quoted text with safe backtick fencing.
|
|
18
|
+
- Reply chaining: answers use `reply_to_message_id` to appear as replies.
|
|
19
|
+
- TUI navigation: `POST /tui/select-session` tried first (works in opencode 1.15.13).
|
|
20
|
+
|
|
11
21
|
## [0.1.0] - 2026-05-08
|
|
12
22
|
|
|
13
23
|
- Initial release.
|
package/README.md
CHANGED
|
@@ -53,9 +53,13 @@ agentp [options] [url]
|
|
|
53
53
|
Options:
|
|
54
54
|
|
|
55
55
|
- `--qa`: print the original prompt and answer with labels (useful when used as a filter)
|
|
56
|
+
- `--tg`: forward the answer to Telegram via tgagentp gateway (error if unreachable)
|
|
57
|
+
- `--no-tg`: do not forward to Telegram
|
|
56
58
|
- `--version`: show version
|
|
57
59
|
- `--help`: show help message
|
|
58
60
|
|
|
61
|
+
By default, `--qa` implies `--tg`; standalone mode implies `--no-tg`.
|
|
62
|
+
|
|
59
63
|
Arguments:
|
|
60
64
|
|
|
61
65
|
- `url`: OpenCode TUI server URL or port number (defaults to `4096`). Examples: `4096`, `http://localhost:4096`, `http://192.168.1.50:4096`
|
|
@@ -111,6 +115,15 @@ From Vim/Neovim, send the current visual selection and keep the prompt with the
|
|
|
111
115
|
:'<,'>!agentp --qa
|
|
112
116
|
```
|
|
113
117
|
|
|
118
|
+
From Vim/Neovim, forward the answer to your Telegram:
|
|
119
|
+
|
|
120
|
+
```vim
|
|
121
|
+
:'<,'>!agentp --qa --tg
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
(Works as long as `tgagentp` is running. The answer appears both in the editor
|
|
125
|
+
and in your Telegram chat.)
|
|
126
|
+
|
|
114
127
|
## ocmux
|
|
115
128
|
|
|
116
129
|
Manage OpenCode server + TUI in tmux for project directories.
|
|
@@ -123,9 +136,10 @@ Without arguments, searches upward from `<directory>` (default: `$PWD`) for `.oc
|
|
|
123
136
|
|
|
124
137
|
Subcommands:
|
|
125
138
|
|
|
126
|
-
- **`serve [--git|--GIT] [dir]`** — Create a server in `dir` (default: `$PWD`) and attach a TUI pane. Aliased as `new` for backwards compatibility (to be removed in 1.0). Errors if one already exists there. Warns if a parent directory already has a server.
|
|
139
|
+
- **`serve [--git|--GIT] [--print-logs] [dir]`** — Create a server in `dir` (default: `$PWD`) and attach a TUI pane. Aliased as `new` for backwards compatibility (to be removed in 1.0). Errors if one already exists there. Warns if a parent directory already has a server.
|
|
127
140
|
- `--git` resolves `dir` to the nearest parent with a `.git` entry (file or dir); errors if none is found.
|
|
128
141
|
- `--GIT` resolves `dir` to the nearest parent with a `.git` directory only; errors if none is found.
|
|
142
|
+
- `--print-logs` passes `--print-logs` to `opencode serve`, which prints server logs to stderr in the server tmux pane.
|
|
129
143
|
- **`kill [dir]`** — Kill the server found upward from `dir`. Removes its tmux window and state file.
|
|
130
144
|
- **`list`** — List all running servers with their directories, URLs, and status.
|
|
131
145
|
|
|
@@ -153,11 +167,15 @@ Notes:
|
|
|
153
167
|
|
|
154
168
|
## How agentp Works
|
|
155
169
|
|
|
156
|
-
1.
|
|
157
|
-
2.
|
|
158
|
-
3.
|
|
159
|
-
4.
|
|
160
|
-
5.
|
|
170
|
+
1. Reads all stdin into a single prompt string.
|
|
171
|
+
2. Finds the most recently updated session, or creates one named `agentp`.
|
|
172
|
+
3. Sends the prompt via the session API (`POST /session/:id/message`).
|
|
173
|
+
4. Prints the assistant answer to stdout.
|
|
174
|
+
5. With `--tg` (or by default when `--qa` is given), forwards the answer to Telegram
|
|
175
|
+
via the agentp gateway. The answer appears in your Telegram chat as if tgagentp
|
|
176
|
+
itself had processed the request.
|
|
177
|
+
|
|
178
|
+
The session API ensures the request is processed even when no TUI is attached, and returns the full answer in a single HTTP response.
|
|
161
179
|
|
|
162
180
|
Operational hint:
|
|
163
181
|
|
|
@@ -183,16 +201,21 @@ Non-text Telegram updates (photos, stickers, etc.) are silently ignored.
|
|
|
183
201
|
|
|
184
202
|
| Command | Action |
|
|
185
203
|
|---|---|
|
|
186
|
-
| `/help [topic]` | Show general help or help for a topic (`servers`, `sessions`, `agents`, `models`) |
|
|
204
|
+
| `/help [topic]` | Show general help or help for a topic (`servers`, `sessions`, `agents`, `models`, `allow`, `think`) |
|
|
187
205
|
| `/servers` | List all running ocmux-served projects with URL + status (✅ idle / ⏳ busy) |
|
|
188
206
|
| `/servers switch <name>` | Switch active server; matches by full path, basename, or substring |
|
|
189
207
|
| `/sessions` | List recent sessions for the current server (max 50, with date headings) |
|
|
190
208
|
| `/sessions switch <number\|name>` | Switch active session by position or partial name match |
|
|
191
|
-
| `/agents` | List
|
|
209
|
+
| `/agents` | List primary agents (▶ marker for the active one) |
|
|
210
|
+
| `/agents switch <name>` | Switch active agent — persists on session and refreshes TUI |
|
|
192
211
|
| `/models` | List connected providers with model counts, context limits, and costs |
|
|
193
212
|
| `/status` | Show current server path, URL, busy status, and active session |
|
|
194
213
|
| `/cancel` | Cancel the current AI response for the active server |
|
|
195
|
-
| `/
|
|
214
|
+
| `/think [on\|off\|switch]` | Toggle forwarding of model thinking messages to the chat |
|
|
215
|
+
| `/allow` | Approve a permission request once |
|
|
216
|
+
| `/reject` | Deny a permission request |
|
|
217
|
+
| `/always` | Approve and remember for the session |
|
|
218
|
+
| `/shutdown [force]` | (requires `--dev`) Stop tgagentp; refuses if busy unless `force` is given |
|
|
196
219
|
|
|
197
220
|
### Per-server state
|
|
198
221
|
|
|
@@ -212,8 +235,38 @@ tgagentp
|
|
|
212
235
|
|
|
213
236
|
- `--version`: show version
|
|
214
237
|
- `--help`: show help message
|
|
238
|
+
- `--verbose`: show detailed logs (errors, trace, debug) on stderr
|
|
239
|
+
- `--think`: start with thinking messages enabled (default: off)
|
|
215
240
|
- `--dev`: enable `/shutdown` command for remote restart (run via `while true; do tgagentp --dev; done`)
|
|
216
241
|
|
|
242
|
+
### Agentp gateway
|
|
243
|
+
|
|
244
|
+
tgagentp starts a tiny HTTP server on `127.0.0.1` that accepts `POST /send` requests from `agentp --tg`. This enables cross-tool interoperability: answers obtained through `agentp` (from scripts, editors, or pipes) are forwarded to your Telegram chat.
|
|
245
|
+
|
|
246
|
+
- Port is randomly assigned by default; overridable via `TGAGENTP_PORT`.
|
|
247
|
+
- Port is written to `/tmp/tgagentp-port` for agentp discovery.
|
|
248
|
+
- Authentication reuses `OPENCODE_SERVER_PASSWORD`.
|
|
249
|
+
- Messages for the active server are delivered immediately.
|
|
250
|
+
- Messages for non-active servers are queued and delivered on `/servers switch`.
|
|
251
|
+
- Debounced Telegram notification on queue (configurable via `TGAGENTP_DEBOUNCE_MS`).
|
|
252
|
+
|
|
253
|
+
### Logging
|
|
254
|
+
|
|
255
|
+
Informational messages (startup, discovery, session switches) go to **stdout**. Errors, warnings, and trace/debug messages go to **stderr**. With `--verbose`, detailed
|
|
256
|
+
trace/debug messages are also printed to stderr.
|
|
257
|
+
|
|
258
|
+
For a clean console with only essential info:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
tgagentp 2>/dev/null
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
To capture everything (info + errors) to a log file:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
tgagentp 2>/var/log/tgagentp.log
|
|
268
|
+
```
|
|
269
|
+
|
|
217
270
|
### Telemetry
|
|
218
271
|
|
|
219
272
|
A startup greeting is sent to the last known chat on boot — includes /status-style server info. The chat ID is persisted at `/tmp/tgagentp-startup-chat`.
|
|
@@ -223,9 +276,11 @@ A startup greeting is sent to the last known chat on boot — includes /status-s
|
|
|
223
276
|
| Variable | Description |
|
|
224
277
|
|---|---|
|
|
225
278
|
| `TELEGRAM_BOT_TOKEN` | **Required.** Telegram bot token from @BotFather. |
|
|
226
|
-
| `OPENCODE_SERVER_PASSWORD` | Optional. OpenCode server HTTP Basic Auth password. |
|
|
279
|
+
| `OPENCODE_SERVER_PASSWORD` | Optional. OpenCode server HTTP Basic Auth password (also used for agentp gateway auth). |
|
|
227
280
|
| `OPENCODE_SERVER_USERNAME` | Optional. OpenCode server username (default: `opencode`). |
|
|
228
281
|
| `TGAGENTP_ALLOWED_CHAT_IDS` | Optional. Comma-separated Telegram chat IDs that are allowed to use the bot. |
|
|
282
|
+
| `TGAGENTP_PORT` | Optional. Agentp gateway listen port (default: `0` = random). |
|
|
283
|
+
| `TGAGENTP_DEBOUNCE_MS` | Optional. Debounce interval for queued-message notifications (default: `5000`). |
|
|
229
284
|
|
|
230
285
|
## License
|
|
231
286
|
|
package/bin/agentp
CHANGED
|
@@ -1,39 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const fs = require('fs');
|
|
3
5
|
const readline = require('readline');
|
|
4
6
|
const { version } = require('../package.json');
|
|
5
7
|
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
listenForFinalAnswer,
|
|
8
|
+
sendToSession,
|
|
9
|
+
listSessions,
|
|
10
|
+
createSession,
|
|
10
11
|
} = require('../lib/opencode');
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
let promptText = '';
|
|
13
|
+
const TGAGENTP_PORT_FILE = '/tmp/tgagentp-port';
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
function notifyAgentpGateway(port, server, text) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const body = JSON.stringify({ text, server });
|
|
18
|
+
const options = {
|
|
19
|
+
hostname: '127.0.0.1',
|
|
20
|
+
port,
|
|
21
|
+
path: '/send',
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'Content-Length': Buffer.byteLength(body),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
29
|
+
if (password) {
|
|
30
|
+
const username = process.env.OPENCODE_SERVER_USERNAME || 'opencode';
|
|
31
|
+
options.headers['Authorization'] = 'Basic ' +
|
|
32
|
+
Buffer.from(`${username}:${password}`).toString('base64');
|
|
33
|
+
}
|
|
34
|
+
const req = http.request(options, (res) => {
|
|
35
|
+
let data = '';
|
|
36
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
37
|
+
res.on('end', () => {
|
|
38
|
+
if (res.statusCode === 200) resolve();
|
|
39
|
+
else reject(new Error(`status ${res.statusCode}: ${data}`));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
req.setTimeout(5000, () => {
|
|
43
|
+
req.destroy();
|
|
44
|
+
reject(new Error('Connection timed out'));
|
|
45
|
+
});
|
|
46
|
+
req.on('error', reject);
|
|
47
|
+
req.write(body);
|
|
48
|
+
req.end();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
16
51
|
|
|
52
|
+
async function readAllStdin() {
|
|
53
|
+
let text = '';
|
|
17
54
|
const rl = readline.createInterface({ input: process.stdin });
|
|
18
|
-
|
|
19
55
|
for await (const line of rl) {
|
|
20
|
-
|
|
21
|
-
promptText += promptLine;
|
|
22
|
-
await appendPrompt(server, promptLine);
|
|
56
|
+
text += line + '\n';
|
|
23
57
|
}
|
|
24
|
-
|
|
25
58
|
rl.close();
|
|
26
|
-
|
|
27
|
-
return promptText;
|
|
59
|
+
return text;
|
|
28
60
|
}
|
|
29
61
|
|
|
30
|
-
const HUMAN_ROW = '👤:
|
|
31
|
-
const AGENT_ROW = '🤖:
|
|
32
|
-
const FINAL_ROW = '
|
|
62
|
+
const HUMAN_ROW = '👤: —————————————————\n'
|
|
63
|
+
const AGENT_ROW = '🤖: —————————————————\n'
|
|
64
|
+
const FINAL_ROW = ' —————————————————\n';
|
|
33
65
|
|
|
34
66
|
async function main() {
|
|
35
67
|
let serverBase = 'http://localhost:4096';
|
|
36
68
|
let qaMode = false;
|
|
69
|
+
// tgMode: false = disabled, 'auto' = auto-detect, true = explicit --tg
|
|
70
|
+
let tgMode = null;
|
|
37
71
|
|
|
38
72
|
const args = process.argv.slice(2);
|
|
39
73
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -43,18 +77,23 @@ async function main() {
|
|
|
43
77
|
} else if (args[i] === '--help') {
|
|
44
78
|
console.error('Usage: agentp [options] [url]');
|
|
45
79
|
console.error('');
|
|
46
|
-
console.error('Pipe prompt text to an OpenCode
|
|
80
|
+
console.error('Pipe prompt text to an OpenCode session and get the assistant response on stdout.');
|
|
47
81
|
console.error('');
|
|
48
82
|
console.error('Options:');
|
|
49
83
|
console.error(' --version Show version');
|
|
50
84
|
console.error(' --qa Print the original prompt and answer with labels (useful as a filter)');
|
|
85
|
+
console.error(' --tg Forward the answer to Telegram via agentp gateway (error if unavailable)');
|
|
86
|
+
console.error(' --no-tg Do not forward to Telegram');
|
|
51
87
|
console.error(' --help Show this help message');
|
|
52
88
|
console.error('');
|
|
89
|
+
console.error('By default, --qa auto-detects tgagentp; standalone mode implies --no-tg.');
|
|
90
|
+
console.error('--tg: error if tgagentp is unavailable; auto mode: silently degrade.');
|
|
91
|
+
console.error('');
|
|
53
92
|
console.error('Arguments:');
|
|
54
|
-
console.error(' url OpenCode
|
|
93
|
+
console.error(' url OpenCode server URL or port number (default: 4096)');
|
|
55
94
|
console.error(' Examples: 4096, http://localhost:4096, http://192.168.1.50:4096');
|
|
56
95
|
console.error('');
|
|
57
|
-
console.error('See also: ocmux -- manage OpenCode tmux sessions (ocmux --help)');
|
|
96
|
+
console.error('See also: tgagentp -- Telegram bridge (tgagentp --help), ocmux -- manage OpenCode tmux sessions (ocmux --help)');
|
|
58
97
|
console.error('');
|
|
59
98
|
console.error('Examples:');
|
|
60
99
|
console.error(' printf "Summarize this file" | agentp');
|
|
@@ -64,6 +103,10 @@ async function main() {
|
|
|
64
103
|
process.exit(0);
|
|
65
104
|
} else if (args[i] === '--qa') {
|
|
66
105
|
qaMode = true;
|
|
106
|
+
} else if (args[i] === '--tg') {
|
|
107
|
+
tgMode = true;
|
|
108
|
+
} else if (args[i] === '--no-tg') {
|
|
109
|
+
tgMode = false;
|
|
67
110
|
} else if (args[i].includes('://')) {
|
|
68
111
|
serverBase = args[i].replace(/\/+$/, '');
|
|
69
112
|
} else if (/^\d+$/.test(args[i])) {
|
|
@@ -71,20 +114,91 @@ async function main() {
|
|
|
71
114
|
}
|
|
72
115
|
}
|
|
73
116
|
|
|
74
|
-
|
|
117
|
+
// Resolve default: --qa → auto-detect, standalone → disabled
|
|
118
|
+
if (tgMode === null) {
|
|
119
|
+
tgMode = qaMode ? 'auto' : false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Pre-send: probe tgagentp gateway if enabled
|
|
123
|
+
let tgPort = null;
|
|
124
|
+
if (tgMode) {
|
|
125
|
+
try {
|
|
126
|
+
tgPort = fs.readFileSync(TGAGENTP_PORT_FILE, 'utf8').trim();
|
|
127
|
+
if (!tgPort || !/^\d+$/.test(tgPort)) tgPort = null;
|
|
128
|
+
} catch {
|
|
129
|
+
tgPort = null;
|
|
130
|
+
}
|
|
131
|
+
if (!tgPort && tgMode === true) {
|
|
132
|
+
console.error('Error: tgagentp gateway not found. Is tgagentp running?');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
// auto mode: tgPort stays null → silently skip tgagentp
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const promptText = await readAllStdin();
|
|
75
139
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
140
|
+
// Find most recent session or create one
|
|
141
|
+
let sessions;
|
|
142
|
+
try {
|
|
143
|
+
sessions = await listSessions(serverBase);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error('Error connecting to server:', err.message);
|
|
146
|
+
process.exit(1);
|
|
80
147
|
}
|
|
81
148
|
|
|
82
|
-
|
|
83
|
-
|
|
149
|
+
let sessionId = null;
|
|
150
|
+
if (sessions && sessions.length > 0) {
|
|
151
|
+
const sorted = sessions
|
|
152
|
+
.filter(s => s.time && s.time.updated)
|
|
153
|
+
.sort((a, b) => b.time.updated - a.time.updated);
|
|
154
|
+
if (sorted.length > 0) sessionId = sorted[0].id;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!sessionId) {
|
|
158
|
+
try {
|
|
159
|
+
const session = await createSession(serverBase, 'agentp');
|
|
160
|
+
sessionId = session.id;
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error('Error creating session:', err.message);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let answer;
|
|
168
|
+
try {
|
|
169
|
+
answer = await sendToSession(serverBase, sessionId, promptText);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error('Error sending prompt:', err.message);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Build full --qa output and send to stdout
|
|
176
|
+
let tgText = answer;
|
|
84
177
|
if (qaMode) {
|
|
85
|
-
|
|
178
|
+
tgText = HUMAN_ROW + promptText + AGENT_ROW + answer;
|
|
179
|
+
if (!answer.endsWith('\n')) tgText += '\n';
|
|
180
|
+
tgText += FINAL_ROW;
|
|
181
|
+
process.stdout.write(tgText);
|
|
182
|
+
} else {
|
|
183
|
+
process.stdout.write(answer);
|
|
184
|
+
if (!answer.endsWith('\n')) process.stdout.write('\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Post-send: notify tgagentp if we have a port
|
|
188
|
+
let tgError = null;
|
|
189
|
+
if (tgPort) {
|
|
190
|
+
try {
|
|
191
|
+
await notifyAgentpGateway(tgPort, serverBase, tgText);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
tgError = err;
|
|
194
|
+
}
|
|
86
195
|
}
|
|
87
196
|
|
|
197
|
+
// Warnings at the end (stderr)
|
|
198
|
+
if (tgError && tgMode === true) {
|
|
199
|
+
process.stderr.write('\n');
|
|
200
|
+
process.stderr.write(`⚠️ Telegram notification failed: ${tgError.message}\n`);
|
|
201
|
+
}
|
|
88
202
|
if (!process.env.OPENCODE_SERVER_PASSWORD) {
|
|
89
203
|
process.stderr.write('\n');
|
|
90
204
|
process.stderr.write('🔓 ⚠️ Server is not password-protected! Set OPENCODE_SERVER_PASSWORD for better security.\n');
|
package/bin/ocmux
CHANGED
|
@@ -192,6 +192,7 @@ function printHelp() {
|
|
|
192
192
|
console.error(' --git Use the nearest parent with a .git entry (file or dir).');
|
|
193
193
|
console.error(' --GIT Use the nearest parent with a .git directory only.');
|
|
194
194
|
console.error(' Refuses to create above an existing .ocmux.json found while searching.');
|
|
195
|
+
console.error( ' --print-logs Pass --print-logs to opencode serve (prints server logs to stderr).');
|
|
195
196
|
console.error(' kill Kill the server found upward from <directory>.');
|
|
196
197
|
console.error(' Removes its tmux window and state file.');
|
|
197
198
|
console.error(' list List all running servers with their directories and URLs.');
|
|
@@ -201,6 +202,7 @@ function printHelp() {
|
|
|
201
202
|
console.error('');
|
|
202
203
|
console.error('Options:');
|
|
203
204
|
console.error(' -l Long output: append " → <dir>" after the URL (serve/default).');
|
|
205
|
+
console.error(' --print-logs Pass --print-logs to opencode serve (prints server logs to stderr).');
|
|
204
206
|
console.error(' --version Show version');
|
|
205
207
|
console.error(' -h Show this help');
|
|
206
208
|
console.error('');
|
|
@@ -290,7 +292,7 @@ function cmdList() {
|
|
|
290
292
|
// Subcommand: new
|
|
291
293
|
// ---------------------------------------------------------------------------
|
|
292
294
|
|
|
293
|
-
function cmdNew(targetDir, longMode) {
|
|
295
|
+
function cmdNew(targetDir, longMode, printLogs) {
|
|
294
296
|
const sf = path.join(targetDir, '.ocmux.json');
|
|
295
297
|
if (fs.existsSync(sf)) {
|
|
296
298
|
console.error(`Error: a server already exists in ${targetDir}.`);
|
|
@@ -311,14 +313,14 @@ function cmdNew(targetDir, longMode) {
|
|
|
311
313
|
console.error('Cancelled.');
|
|
312
314
|
process.exit(0);
|
|
313
315
|
}
|
|
314
|
-
doNew(targetDir, longMode);
|
|
316
|
+
doNew(targetDir, longMode, printLogs);
|
|
315
317
|
});
|
|
316
318
|
} else {
|
|
317
|
-
doNew(targetDir, longMode);
|
|
319
|
+
doNew(targetDir, longMode, printLogs);
|
|
318
320
|
}
|
|
319
321
|
}
|
|
320
322
|
|
|
321
|
-
function doNew(targetDir, longMode) {
|
|
323
|
+
function doNew(targetDir, longMode, printLogs) {
|
|
322
324
|
ensureSession();
|
|
323
325
|
const lf = logfileFor(targetDir);
|
|
324
326
|
|
|
@@ -343,8 +345,9 @@ function doNew(targetDir, longMode) {
|
|
|
343
345
|
try { fs.truncateSync(lf); } catch { /* may not exist yet */ }
|
|
344
346
|
|
|
345
347
|
// Send the command to the interactive shell (like typing it manually)
|
|
348
|
+
const serveFlags = printLogs ? '--port 0 --print-logs' : '--port 0';
|
|
346
349
|
tmux(['send-keys', '-t', `${SESSION}:${winIdx}.0`,
|
|
347
|
-
`opencode serve
|
|
350
|
+
`opencode serve ${serveFlags} 2>&1 | tee '${lf}'`, 'Enter']);
|
|
348
351
|
|
|
349
352
|
// Wait up to 10s for the URL in the logfile.
|
|
350
353
|
// Don't break on first match — keep polling so url ends up holding the
|
|
@@ -432,12 +435,15 @@ function cmdKill(statefile) {
|
|
|
432
435
|
function main() {
|
|
433
436
|
let longMode = false;
|
|
434
437
|
let gitMode = null;
|
|
438
|
+
let printLogs = false;
|
|
435
439
|
const positional = [];
|
|
436
440
|
let dirIsForced = false;
|
|
437
441
|
|
|
438
442
|
for (const arg of process.argv.slice(2)) {
|
|
439
443
|
if (arg === '-l') {
|
|
440
444
|
longMode = true;
|
|
445
|
+
} else if (arg === '--print-logs') {
|
|
446
|
+
printLogs = true;
|
|
441
447
|
} else if (arg === '--git') {
|
|
442
448
|
if (gitMode && gitMode !== 'nearest') {
|
|
443
449
|
console.error("Error: cannot combine '--git' with '--GIT'.");
|
|
@@ -506,6 +512,10 @@ function main() {
|
|
|
506
512
|
console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
|
|
507
513
|
process.exit(1);
|
|
508
514
|
}
|
|
515
|
+
if (printLogs) {
|
|
516
|
+
console.error("Error: '--print-logs' is only valid with 'serve'.");
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
509
519
|
if (targetDir !== process.cwd()) {
|
|
510
520
|
console.error("Error: 'list' does not accept a directory path.");
|
|
511
521
|
process.exit(1);
|
|
@@ -516,6 +526,10 @@ function main() {
|
|
|
516
526
|
console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
|
|
517
527
|
process.exit(1);
|
|
518
528
|
}
|
|
529
|
+
if (printLogs) {
|
|
530
|
+
console.error("Error: '--print-logs' is only valid with 'serve'.");
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
519
533
|
const sf = findParentStatefile(targetDir);
|
|
520
534
|
if (!sf) {
|
|
521
535
|
console.error(`Error: no opencode server found in ${targetDir} or any of its parents.`);
|
|
@@ -528,12 +542,16 @@ function main() {
|
|
|
528
542
|
const flagLabel = gitMode === 'root' ? '--GIT' : '--git';
|
|
529
543
|
targetDir = resolveGitDir(targetDir, gitMode === 'root', flagLabel);
|
|
530
544
|
}
|
|
531
|
-
cmdNew(targetDir, longMode);
|
|
545
|
+
cmdNew(targetDir, longMode, printLogs);
|
|
532
546
|
} else {
|
|
533
547
|
if (gitMode) {
|
|
534
548
|
console.error("Error: '--git' and '--GIT' are only valid with 'serve'.");
|
|
535
549
|
process.exit(1);
|
|
536
550
|
}
|
|
551
|
+
if (printLogs) {
|
|
552
|
+
console.error("Error: '--print-logs' is only valid with 'serve'.");
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
537
555
|
// Default mode
|
|
538
556
|
const sf = findParentStatefile(targetDir);
|
|
539
557
|
if (!sf) {
|