agentp 0.8.0 → 0.10.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 +36 -9
- package/CHANGELOG.md +16 -4
- package/README.md +73 -9
- package/bin/agentp +220 -27
- package/bin/ocmux +24 -6
- package/bin/tgagentp +332 -74
- package/devto-article.md +121 -0
- package/lib/opencode.js +73 -9
- package/package.json +1 -1
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
|
|
|
@@ -37,11 +37,14 @@ Extracted from `bin/agentp`; used by both `agentp` and `tgagentp`:
|
|
|
37
37
|
- `listenForFinalAnswer(server, onText?, cancelRef?)` — SSE event stream listener; `cancelRef` allows true server-side abort via `req.destroy()`
|
|
38
38
|
- `listSessions(server, directory?)` — `GET /session`; returns parsed JSON array; optional `?directory=` filter
|
|
39
39
|
- `createSession(server, title?)` — `POST /session`; returns the created `Session` object; optional `title`
|
|
40
|
-
- `updateSession(server, sessionId, title)` — `PATCH /session/:id`; returns the updated `Session` object
|
|
41
|
-
- `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`
|
|
42
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)
|
|
43
45
|
- `listAgents(server)` — `GET /agent`; returns parsed JSON array of agent objects
|
|
44
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)
|
|
45
48
|
|
|
46
49
|
## Shared `lib/ocmux.js`
|
|
47
50
|
|
|
@@ -67,17 +70,30 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
67
70
|
- `GET /event` (SSE stream)
|
|
68
71
|
- `GET /session` — list sessions (optional `?directory=` filter)
|
|
69
72
|
- `POST /session` — create a new session (optional `{ title? }` body)
|
|
70
|
-
- `PATCH /session/:id` — update session properties (`{ title }`)
|
|
73
|
+
- `PATCH /session/:id` — update session properties (`{ title }`, optional `{ agent }`)
|
|
71
74
|
- `POST /session/:id/select` — tell the TUI to navigate to a session (only with TUI attached)
|
|
72
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)
|
|
73
77
|
- `GET /agent` — list available agents
|
|
74
78
|
- `GET /provider` — list connected providers/models
|
|
75
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
|
+
- Gateway response includes `{ buffered: [...] }` — recorded conversation messages (see `/record` command) flushed on each POST.
|
|
90
|
+
- agentp `--qa` prepends the buffered context to stdout; `--flush` bypasses prepending but still triggers the flush.
|
|
91
|
+
|
|
76
92
|
## OpenCode tmux session model (ocmux)
|
|
77
93
|
|
|
78
94
|
- Single tmux session named `"Opencode"`
|
|
79
95
|
- Windows named by full project directory path
|
|
80
|
-
- Server pane runs `opencode serve --port 0` (via send-keys → interactive shell for env var support), TUI pane runs `opencode attach --continue`
|
|
96
|
+
- 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
|
|
81
97
|
- Log file per project: `/tmp/opencode-serve-<hashDir(dir)>.log` — captured via `tee`, read by ocmux polling loop
|
|
82
98
|
- Per-project `.ocmux.json` with upward directory search (git-like)
|
|
83
99
|
- Auto-restarts dead TUI panes on switch (respawn-pane or split-window)
|
|
@@ -89,10 +105,10 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
89
105
|
|---|---|---|
|
|
90
106
|
| `npm link` | local dev install |
|
|
91
107
|
| `npm install -g .` | alternative local install |
|
|
92
|
-
| `agentp [--qa] [port]` | pipe stdin → opencode
|
|
108
|
+
| `agentp [--qa] [--tg|--no-tg] [--flush] [--getLast n] [port]` | pipe stdin → opencode session, stream answer to stdout (uses session API); `--tg` forwards answer to Telegram; `--flush` clears recorded buffer; `--getLast n` retrieves last n answers |
|
|
93
109
|
| `tgagentp [port]` | bridge Telegram bot ↔ opencode TUI (needs `TELEGRAM_BOT_TOKEN`) |
|
|
94
110
|
| `tgagentp --dev` | enable `/shutdown` command for remote restart |
|
|
95
|
-
| `ocmux serve [dir]` | start opencode serve in a tmux window (primary verb) |
|
|
111
|
+
| `ocmux serve [--print-logs] [dir]` | start opencode serve in a tmux window (primary verb) |
|
|
96
112
|
| `ocmux new [dir]` | alias for `serve` (backwards compat, to be removed in 1.0) |
|
|
97
113
|
| `ocmux kill [dir]` | kill server + tmux window |
|
|
98
114
|
| `ocmux list [-l]` | list running servers |
|
|
@@ -102,7 +118,7 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
102
118
|
|
|
103
119
|
### Pending
|
|
104
120
|
|
|
105
|
-
|
|
121
|
+
*(none)*
|
|
106
122
|
|
|
107
123
|
### In Progress
|
|
108
124
|
|
|
@@ -110,12 +126,23 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
110
126
|
|
|
111
127
|
### Done
|
|
112
128
|
|
|
129
|
+
- `/queue` command: queue messages when server is busy, auto-sent after current task finishes preserves replyTo chain — 0.10.0
|
|
130
|
+
- `agentp --getLast n` — retrieve last n answers from session history — 0.10.0
|
|
131
|
+
- `/record` command with ring buffer (100 msgs / 100KB), gateway returns `{ buffered }`, agentp `--qa` prepends recorded context, `--flush` flushes without prepending — 0.10.0
|
|
132
|
+
- agentp `--qa` full context forwarding to Telegram (rulers, prompt, answer) — 0.9.0
|
|
133
|
+
- agentp resilience: 5s HTTP timeout, pre-send gate check, post-send warning (not hard error) for `--tg`; auto mode silently degrades — 0.9.0
|
|
134
|
+
- tgagentp: `lockedChatId` set at startup to fix race condition with agentp gateway — 0.9.0
|
|
135
|
+
- tgagentp: server error handler + try-catch in gateway handler (no crash on socket errors) — 0.9.0
|
|
136
|
+
- agentp `--tg`/`--no-tg` — gateway forwards answer to Telegram via tgagentp HTTP server; per-server queue with debounced notifications; auto-flush on server switch
|
|
137
|
+
- agentp session API migration — uses `POST /session/:id/message` instead of TUI endpoints
|
|
138
|
+
- ocmux `--print-logs` flag — pass-through to `opencode serve --print-logs` (prints server logs to stderr in the server pane)
|
|
139
|
+
- /agents lists only primary agents with ▶ active marker
|
|
140
|
+
- /agents switch calls `updateSession(agent)` to persist agent on session + `selectSession` to refresh TUI
|
|
113
141
|
- Logging rebalance: stdout for info, stderr for errors/debug/trace, `--verbose` flag, `2>/dev/null` default usage
|
|
114
142
|
- `/shutdown force` — refuses shutdown when busy unless `force` flag is given; goodbye message includes state
|
|
115
143
|
- `/think [on|off|switch]` — toggle real-time forwarding of model thinking messages to the chat; `--think` CLI flag to start enabled
|
|
116
144
|
- Event-driven permission handling (`POST /session/:id/prompt_async` + SSE listener + `/allow`, `/reject`, `/always` commands) — included in v0.7.0
|
|
117
145
|
- TUI navigation on `/sessions switch` and `/sessions new` — `POST /tui/select-session` now works in opencode 1.15.13 (was: tried second, never reached)
|
|
118
|
-
- /agents switch <name> — switch active agent on the active session
|
|
119
146
|
- /sessions new [name] — create a new session, TUI follows via /session/:id/select
|
|
120
147
|
- /sessions rename <name> — rename the current active session
|
|
121
148
|
- Merge /session into /sessions (keep /session as alias)
|
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.10.0] - 2026-06-04
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
7
|
+
- `/queue` command: queue messages when server is busy, auto-sent after current task finishes, preserves replyTo chain.
|
|
8
|
+
- `/record` command with ring buffer (100 msgs / 100KB), `/record stop` to clear; gateway returns `{ buffered }` JSON.
|
|
9
|
+
- agentp `--flush`: flush tgagentp's recorded buffer without prepending to stdout.
|
|
10
|
+
- agentp `--getLast n`: retrieve last n assistant answers from session history.
|
|
11
|
+
- agentp `--qa` prepends recorded Telegram context (with rulers) to stdout.
|
|
12
|
+
- `getSession()` in lib/opencode: `GET /session/:id` with fallback to session list.
|
|
13
|
+
- `makeRequest` optional timeout parameter (used only for getSession; all other calls wait indefinitely).
|
|
14
|
+
- Minimum 3-backtick code fence for reply quoting.
|
|
15
|
+
|
|
16
|
+
## [0.9.0] - 2026-06-04
|
|
17
|
+
|
|
18
|
+
- agentp `--tg`/`--no-tg` flags: gateway forwards answer to Telegram; auto mode silently degrades.
|
|
19
|
+
- Agentp resilience: 5s HTTP timeout, pre-send gate check, post-send warning.
|
|
20
|
+
- Full `--qa` output (rulers + prompt + answer) forwarded to Telegram.
|
|
21
|
+
- Rulers changed from `—` to `─`, shortened to 17 chars.
|
|
10
22
|
|
|
11
23
|
## [0.8.0] - 2026-06-03
|
|
12
24
|
|
package/README.md
CHANGED
|
@@ -53,9 +53,16 @@ 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
|
|
58
|
+
- `--flush`: flush tgagentp's recorded buffer without prepending it to output
|
|
59
|
+
- `--getLast <n>`: retrieve last n assistant answers from session history
|
|
56
60
|
- `--version`: show version
|
|
57
61
|
- `--help`: show help message
|
|
58
62
|
|
|
63
|
+
By default, `--qa` auto-detects tgagentp (silently degrades if unavailable); standalone mode implies `--no-tg`.
|
|
64
|
+
With `--tg`, errors if tgagentp is unavailable.
|
|
65
|
+
|
|
59
66
|
Arguments:
|
|
60
67
|
|
|
61
68
|
- `url`: OpenCode TUI server URL or port number (defaults to `4096`). Examples: `4096`, `http://localhost:4096`, `http://192.168.1.50:4096`
|
|
@@ -111,6 +118,34 @@ From Vim/Neovim, send the current visual selection and keep the prompt with the
|
|
|
111
118
|
:'<,'>!agentp --qa
|
|
112
119
|
```
|
|
113
120
|
|
|
121
|
+
From Vim/Neovim, forward the answer to your Telegram:
|
|
122
|
+
|
|
123
|
+
```vim
|
|
124
|
+
:'<,'>!agentp --qa --tg
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
(Works as long as `tgagentp` is running. The answer appears both in the editor
|
|
128
|
+
and in your Telegram chat.)
|
|
129
|
+
|
|
130
|
+
From Vim/Neovim, flush the recorded buffer without prepending context:
|
|
131
|
+
|
|
132
|
+
```vim
|
|
133
|
+
:'<,'>!agentp --qa --flush
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Useful when you've finished a conversation thread and want to reset the recorded
|
|
137
|
+
context for a new topic.
|
|
138
|
+
|
|
139
|
+
Retrieve the last 3 assistant answers from session history:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
agentp --getLast 3
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Useful to grab recent answers without sending a new prompt.
|
|
146
|
+
|
|
147
|
+
## ocmux
|
|
148
|
+
|
|
114
149
|
## ocmux
|
|
115
150
|
|
|
116
151
|
Manage OpenCode server + TUI in tmux for project directories.
|
|
@@ -123,9 +158,10 @@ Without arguments, searches upward from `<directory>` (default: `$PWD`) for `.oc
|
|
|
123
158
|
|
|
124
159
|
Subcommands:
|
|
125
160
|
|
|
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.
|
|
161
|
+
- **`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
162
|
- `--git` resolves `dir` to the nearest parent with a `.git` entry (file or dir); errors if none is found.
|
|
128
163
|
- `--GIT` resolves `dir` to the nearest parent with a `.git` directory only; errors if none is found.
|
|
164
|
+
- `--print-logs` passes `--print-logs` to `opencode serve`, which prints server logs to stderr in the server tmux pane.
|
|
129
165
|
- **`kill [dir]`** — Kill the server found upward from `dir`. Removes its tmux window and state file.
|
|
130
166
|
- **`list`** — List all running servers with their directories, URLs, and status.
|
|
131
167
|
|
|
@@ -153,11 +189,19 @@ Notes:
|
|
|
153
189
|
|
|
154
190
|
## How agentp Works
|
|
155
191
|
|
|
156
|
-
1.
|
|
157
|
-
2.
|
|
158
|
-
3.
|
|
159
|
-
4.
|
|
160
|
-
5.
|
|
192
|
+
1. Reads all stdin into a single prompt string.
|
|
193
|
+
2. Finds the most recently updated session, or creates one named `agentp`.
|
|
194
|
+
3. Sends the prompt via the session API (`POST /session/:id/message`).
|
|
195
|
+
4. Prints the assistant answer to stdout.
|
|
196
|
+
5. With `--tg` (or by default when `--qa` is given), forwards the answer to Telegram
|
|
197
|
+
via the agentp gateway. The answer appears in your Telegram chat as if tgagentp
|
|
198
|
+
itself had processed the request.
|
|
199
|
+
6. If tgagentp's [/record](#tgagentp) feature was active, the gateway response includes
|
|
200
|
+
the recorded conversation buffer. In `--qa` mode, agentp prepends this buffer (with
|
|
201
|
+
rulers) to its stdout so OpenCode receives the full Telegram context. Use `--flush`
|
|
202
|
+
to clear the buffer without prepending.
|
|
203
|
+
|
|
204
|
+
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
205
|
|
|
162
206
|
Operational hint:
|
|
163
207
|
|
|
@@ -183,16 +227,22 @@ Non-text Telegram updates (photos, stickers, etc.) are silently ignored.
|
|
|
183
227
|
|
|
184
228
|
| Command | Action |
|
|
185
229
|
|---|---|
|
|
186
|
-
| `/help [topic]` | Show general help or help for a topic (`servers`, `sessions`, `agents`, `models`) |
|
|
230
|
+
| `/help [topic]` | Show general help or help for a topic (`servers`, `sessions`, `agents`, `models`, `allow`, `think`, `record`, `queue`) |
|
|
187
231
|
| `/servers` | List all running ocmux-served projects with URL + status (✅ idle / ⏳ busy) |
|
|
188
232
|
| `/servers switch <name>` | Switch active server; matches by full path, basename, or substring |
|
|
189
233
|
| `/sessions` | List recent sessions for the current server (max 50, with date headings) |
|
|
190
234
|
| `/sessions switch <number\|name>` | Switch active session by position or partial name match |
|
|
191
|
-
| `/agents` | List
|
|
235
|
+
| `/agents` | List primary agents (▶ marker for the active one) |
|
|
236
|
+
| `/agents switch <name>` | Switch active agent — persists on session and refreshes TUI |
|
|
192
237
|
| `/models` | List connected providers with model counts, context limits, and costs |
|
|
193
238
|
| `/status` | Show current server path, URL, busy status, and active session |
|
|
194
239
|
| `/cancel` | Cancel the current AI response for the active server |
|
|
195
240
|
| `/think [on\|off\|switch]` | Toggle forwarding of model thinking messages to the chat |
|
|
241
|
+
| `/record [stop]` | Toggle recording of Telegram conversation for agentp context; `/record stop` clears and stops |
|
|
242
|
+
| `/queue <message>` | Queue a message when the server is busy; auto-sent when current task finishes |
|
|
243
|
+
| `/allow` | Approve a permission request once |
|
|
244
|
+
| `/reject` | Deny a permission request |
|
|
245
|
+
| `/always` | Approve and remember for the session |
|
|
196
246
|
| `/shutdown [force]` | (requires `--dev`) Stop tgagentp; refuses if busy unless `force` is given |
|
|
197
247
|
|
|
198
248
|
### Per-server state
|
|
@@ -217,6 +267,18 @@ tgagentp
|
|
|
217
267
|
- `--think`: start with thinking messages enabled (default: off)
|
|
218
268
|
- `--dev`: enable `/shutdown` command for remote restart (run via `while true; do tgagentp --dev; done`)
|
|
219
269
|
|
|
270
|
+
### Agentp gateway
|
|
271
|
+
|
|
272
|
+
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.
|
|
273
|
+
|
|
274
|
+
- Port is randomly assigned by default; overridable via `TGAGENTP_PORT`.
|
|
275
|
+
- Port is written to `/tmp/tgagentp-port` for agentp discovery.
|
|
276
|
+
- Authentication reuses `OPENCODE_SERVER_PASSWORD`.
|
|
277
|
+
- Messages for the active server are delivered immediately.
|
|
278
|
+
- Messages for non-active servers are queued and delivered on `/servers switch`.
|
|
279
|
+
- Debounced Telegram notification on queue (configurable via `TGAGENTP_DEBOUNCE_MS`).
|
|
280
|
+
- When [/record](#tgagentp) is active, the gateway response includes the recorded conversation buffer. `agentp --qa` prepends this buffer (with rulers) to its stdout so the full Telegram context is available to OpenCode. Use `agentp --qa --flush` to flush the buffer without prepending.
|
|
281
|
+
|
|
220
282
|
### Logging
|
|
221
283
|
|
|
222
284
|
Informational messages (startup, discovery, session switches) go to **stdout**. Errors, warnings, and trace/debug messages go to **stderr**. With `--verbose`, detailed
|
|
@@ -243,9 +305,11 @@ A startup greeting is sent to the last known chat on boot — includes /status-s
|
|
|
243
305
|
| Variable | Description |
|
|
244
306
|
|---|---|
|
|
245
307
|
| `TELEGRAM_BOT_TOKEN` | **Required.** Telegram bot token from @BotFather. |
|
|
246
|
-
| `OPENCODE_SERVER_PASSWORD` | Optional. OpenCode server HTTP Basic Auth password. |
|
|
308
|
+
| `OPENCODE_SERVER_PASSWORD` | Optional. OpenCode server HTTP Basic Auth password (also used for agentp gateway auth). |
|
|
247
309
|
| `OPENCODE_SERVER_USERNAME` | Optional. OpenCode server username (default: `opencode`). |
|
|
248
310
|
| `TGAGENTP_ALLOWED_CHAT_IDS` | Optional. Comma-separated Telegram chat IDs that are allowed to use the bot. |
|
|
311
|
+
| `TGAGENTP_PORT` | Optional. Agentp gateway listen port (default: `0` = random). |
|
|
312
|
+
| `TGAGENTP_DEBOUNCE_MS` | Optional. Debounce interval for queued-message notifications (default: `5000`). |
|
|
249
313
|
|
|
250
314
|
## License
|
|
251
315
|
|
package/bin/agentp
CHANGED
|
@@ -1,39 +1,83 @@
|
|
|
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
|
-
|
|
8
|
+
sendToSession,
|
|
9
|
+
listSessions,
|
|
10
|
+
createSession,
|
|
11
|
+
getSession,
|
|
10
12
|
} = require('../lib/opencode');
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
let promptText = '';
|
|
14
|
+
const TGAGENTP_PORT_FILE = '/tmp/tgagentp-port';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
function notifyAgentpGateway(port, server, text) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const body = JSON.stringify({ text, server });
|
|
19
|
+
const options = {
|
|
20
|
+
hostname: '127.0.0.1',
|
|
21
|
+
port,
|
|
22
|
+
path: '/send',
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'Content-Length': Buffer.byteLength(body),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
30
|
+
if (password) {
|
|
31
|
+
const username = process.env.OPENCODE_SERVER_USERNAME || 'opencode';
|
|
32
|
+
options.headers['Authorization'] = 'Basic ' +
|
|
33
|
+
Buffer.from(`${username}:${password}`).toString('base64');
|
|
34
|
+
}
|
|
35
|
+
const req = http.request(options, (res) => {
|
|
36
|
+
let data = '';
|
|
37
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
38
|
+
res.on('end', () => {
|
|
39
|
+
if (res.statusCode === 200) {
|
|
40
|
+
try {
|
|
41
|
+
const result = JSON.parse(data);
|
|
42
|
+
resolve(result.buffered || []);
|
|
43
|
+
} catch {
|
|
44
|
+
resolve([]);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
reject(new Error(`status ${res.statusCode}: ${data}`));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
req.setTimeout(5000, () => {
|
|
52
|
+
req.destroy();
|
|
53
|
+
reject(new Error('Connection timed out'));
|
|
54
|
+
});
|
|
55
|
+
req.on('error', reject);
|
|
56
|
+
req.write(body);
|
|
57
|
+
req.end();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
16
60
|
|
|
61
|
+
async function readAllStdin() {
|
|
62
|
+
let text = '';
|
|
17
63
|
const rl = readline.createInterface({ input: process.stdin });
|
|
18
|
-
|
|
19
64
|
for await (const line of rl) {
|
|
20
|
-
|
|
21
|
-
promptText += promptLine;
|
|
22
|
-
await appendPrompt(server, promptLine);
|
|
65
|
+
text += line + '\n';
|
|
23
66
|
}
|
|
24
|
-
|
|
25
67
|
rl.close();
|
|
26
|
-
|
|
27
|
-
return promptText;
|
|
68
|
+
return text;
|
|
28
69
|
}
|
|
29
70
|
|
|
30
|
-
const HUMAN_ROW = '👤:
|
|
31
|
-
const AGENT_ROW = '🤖:
|
|
32
|
-
const FINAL_ROW = '
|
|
71
|
+
const HUMAN_ROW = '👤: —————————————————\n'
|
|
72
|
+
const AGENT_ROW = '🤖: —————————————————\n'
|
|
73
|
+
const FINAL_ROW = ' —————————————————\n';
|
|
33
74
|
|
|
34
75
|
async function main() {
|
|
35
76
|
let serverBase = 'http://localhost:4096';
|
|
36
77
|
let qaMode = false;
|
|
78
|
+
let flushMode = false;
|
|
79
|
+
// tgMode: false = disabled, 'auto' = auto-detect, true = explicit --tg
|
|
80
|
+
let tgMode = null;
|
|
37
81
|
|
|
38
82
|
const args = process.argv.slice(2);
|
|
39
83
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -43,27 +87,94 @@ async function main() {
|
|
|
43
87
|
} else if (args[i] === '--help') {
|
|
44
88
|
console.error('Usage: agentp [options] [url]');
|
|
45
89
|
console.error('');
|
|
46
|
-
console.error('Pipe prompt text to an OpenCode
|
|
90
|
+
console.error('Pipe prompt text to an OpenCode session and get the assistant response on stdout.');
|
|
47
91
|
console.error('');
|
|
48
92
|
console.error('Options:');
|
|
49
93
|
console.error(' --version Show version');
|
|
50
94
|
console.error(' --qa Print the original prompt and answer with labels (useful as a filter)');
|
|
95
|
+
console.error(' --tg Forward the answer to Telegram via agentp gateway (error if unavailable)');
|
|
96
|
+
console.error(' --no-tg Do not forward to Telegram');
|
|
97
|
+
console.error(' --flush Flush tgagentp\'s recorded buffer without prepending it to output');
|
|
98
|
+
console.error(' --getLast Retrieve last N assistant answers from session history');
|
|
51
99
|
console.error(' --help Show this help message');
|
|
52
100
|
console.error('');
|
|
101
|
+
console.error('By default, --qa auto-detects tgagentp; standalone mode implies --no-tg.');
|
|
102
|
+
console.error('--tg: error if tgagentp is unavailable; auto mode: silently degrade.');
|
|
103
|
+
console.error('');
|
|
53
104
|
console.error('Arguments:');
|
|
54
|
-
console.error(' url OpenCode
|
|
105
|
+
console.error(' url OpenCode server URL or port number (default: 4096)');
|
|
55
106
|
console.error(' Examples: 4096, http://localhost:4096, http://192.168.1.50:4096');
|
|
56
107
|
console.error('');
|
|
57
|
-
console.error('See also: ocmux -- manage OpenCode tmux sessions (ocmux --help)');
|
|
108
|
+
console.error('See also: tgagentp -- Telegram bridge (tgagentp --help), ocmux -- manage OpenCode tmux sessions (ocmux --help)');
|
|
58
109
|
console.error('');
|
|
59
110
|
console.error('Examples:');
|
|
60
111
|
console.error(' printf "Summarize this file" | agentp');
|
|
61
112
|
console.error(' cat prompt.txt | agentp 4096');
|
|
62
113
|
console.error(' # Vim/Neovim filter, preserving prompt + answer:');
|
|
63
114
|
console.error(" :'<,'>!agentp --qa");
|
|
115
|
+
console.error(' # Retrieve last 3 assistant answers:');
|
|
116
|
+
console.error(' agentp --getLast 3');
|
|
117
|
+
console.error(' agentp --getLast 1 4096');
|
|
64
118
|
process.exit(0);
|
|
65
119
|
} else if (args[i] === '--qa') {
|
|
66
120
|
qaMode = true;
|
|
121
|
+
} else if (args[i] === '--flush') {
|
|
122
|
+
flushMode = true;
|
|
123
|
+
} else if (args[i] === '--getLast') {
|
|
124
|
+
i++;
|
|
125
|
+
const nStr = args[i];
|
|
126
|
+
if (!nStr || !/^\d+$/.test(nStr)) {
|
|
127
|
+
console.error('Error: --getLast requires a number (e.g. --getLast 5)');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
const n = parseInt(nStr, 10);
|
|
131
|
+
try {
|
|
132
|
+
const sessions = await listSessions(serverBase);
|
|
133
|
+
if (!sessions || sessions.length === 0) {
|
|
134
|
+
console.error('No sessions found.');
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const sorted = sessions
|
|
138
|
+
.filter(s => s.time && s.time.updated)
|
|
139
|
+
.sort((a, b) => b.time.updated - a.time.updated);
|
|
140
|
+
// Try to extract messages; getSession may return data with
|
|
141
|
+
// messages, or the sessions list may already contain them
|
|
142
|
+
let rawMessages = [];
|
|
143
|
+
try {
|
|
144
|
+
const session = await getSession(serverBase, sorted[0].id);
|
|
145
|
+
rawMessages = session.messages || session.history || session.conversation || [];
|
|
146
|
+
} catch {
|
|
147
|
+
// If the dedicated endpoint fails, the list data may have messages
|
|
148
|
+
rawMessages = sorted[0].messages || sorted[0].history || sorted[0].conversation || [];
|
|
149
|
+
}
|
|
150
|
+
if (rawMessages.length === 0) {
|
|
151
|
+
console.error('No message history available for the most recent session.');
|
|
152
|
+
console.error('The OpenCode server may not expose message history via the HTTP API.');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
const assistantTexts = rawMessages
|
|
156
|
+
.filter(m => m.role === 'assistant')
|
|
157
|
+
.map(m => {
|
|
158
|
+
if (m.parts) return m.parts.filter(p => p.type === 'text').map(p => p.text).join('');
|
|
159
|
+
return m.content || m.text || '';
|
|
160
|
+
})
|
|
161
|
+
.filter(t => t.trim());
|
|
162
|
+
if (assistantTexts.length === 0) {
|
|
163
|
+
console.error('No assistant answers found in session history.');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
const lastN = assistantTexts.slice(-n);
|
|
167
|
+
process.stdout.write(lastN.join('\n'));
|
|
168
|
+
if (lastN.length > 0 && !lastN[lastN.length - 1].endsWith('\n')) process.stdout.write('\n');
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error('Error retrieving session history:', err.message);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
} else if (args[i] === '--tg') {
|
|
175
|
+
tgMode = true;
|
|
176
|
+
} else if (args[i] === '--no-tg') {
|
|
177
|
+
tgMode = false;
|
|
67
178
|
} else if (args[i].includes('://')) {
|
|
68
179
|
serverBase = args[i].replace(/\/+$/, '');
|
|
69
180
|
} else if (/^\d+$/.test(args[i])) {
|
|
@@ -71,20 +182,102 @@ async function main() {
|
|
|
71
182
|
}
|
|
72
183
|
}
|
|
73
184
|
|
|
74
|
-
|
|
185
|
+
// Resolve default: --qa → auto-detect, standalone → disabled
|
|
186
|
+
if (tgMode === null) {
|
|
187
|
+
tgMode = qaMode ? 'auto' : false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Pre-send: probe tgagentp gateway if enabled
|
|
191
|
+
let tgPort = null;
|
|
192
|
+
if (tgMode) {
|
|
193
|
+
try {
|
|
194
|
+
tgPort = fs.readFileSync(TGAGENTP_PORT_FILE, 'utf8').trim();
|
|
195
|
+
if (!tgPort || !/^\d+$/.test(tgPort)) tgPort = null;
|
|
196
|
+
} catch {
|
|
197
|
+
tgPort = null;
|
|
198
|
+
}
|
|
199
|
+
if (!tgPort && tgMode === true) {
|
|
200
|
+
console.error('Error: tgagentp gateway not found. Is tgagentp running?');
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
// auto mode: tgPort stays null → silently skip tgagentp
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const promptText = await readAllStdin();
|
|
207
|
+
|
|
208
|
+
// Find most recent session or create one
|
|
209
|
+
let sessions;
|
|
210
|
+
try {
|
|
211
|
+
sessions = await listSessions(serverBase);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error('Error connecting to server:', err.message);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let sessionId = null;
|
|
218
|
+
if (sessions && sessions.length > 0) {
|
|
219
|
+
const sorted = sessions
|
|
220
|
+
.filter(s => s.time && s.time.updated)
|
|
221
|
+
.sort((a, b) => b.time.updated - a.time.updated);
|
|
222
|
+
if (sorted.length > 0) sessionId = sorted[0].id;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!sessionId) {
|
|
226
|
+
try {
|
|
227
|
+
const session = await createSession(serverBase, 'agentp');
|
|
228
|
+
sessionId = session.id;
|
|
229
|
+
} catch (err) {
|
|
230
|
+
console.error('Error creating session:', err.message);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let answer;
|
|
236
|
+
try {
|
|
237
|
+
answer = await sendToSession(serverBase, sessionId, promptText);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error('Error sending prompt:', err.message);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
75
242
|
|
|
243
|
+
// Build the message to send to Telegram (QA pair with rulers)
|
|
244
|
+
let tgMessage = answer;
|
|
76
245
|
if (qaMode) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
246
|
+
tgMessage = HUMAN_ROW + promptText + AGENT_ROW + answer;
|
|
247
|
+
if (!answer.endsWith('\n')) tgMessage += '\n';
|
|
248
|
+
tgMessage += FINAL_ROW;
|
|
80
249
|
}
|
|
81
250
|
|
|
82
|
-
|
|
83
|
-
|
|
251
|
+
// Post-send: notify tgagentp, get recorded context back
|
|
252
|
+
let buffered = [];
|
|
253
|
+
let tgError = null;
|
|
254
|
+
if (tgPort) {
|
|
255
|
+
try {
|
|
256
|
+
buffered = await notifyAgentpGateway(tgPort, serverBase, tgMessage);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
tgError = err;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Build stdout output (prepend recorded context if any, unless --flush)
|
|
84
263
|
if (qaMode) {
|
|
85
|
-
|
|
264
|
+
if (!flushMode && buffered.length > 0) {
|
|
265
|
+
for (const msg of buffered) {
|
|
266
|
+
const row = msg.role === 'user' ? HUMAN_ROW : AGENT_ROW;
|
|
267
|
+
process.stdout.write(row + msg.text + '\n');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
process.stdout.write(tgMessage);
|
|
271
|
+
} else {
|
|
272
|
+
process.stdout.write(tgMessage);
|
|
273
|
+
if (!tgMessage.endsWith('\n')) process.stdout.write('\n');
|
|
86
274
|
}
|
|
87
275
|
|
|
276
|
+
// Warnings at the end (stderr)
|
|
277
|
+
if (tgError && tgMode === true) {
|
|
278
|
+
process.stderr.write('\n');
|
|
279
|
+
process.stderr.write(`⚠️ Telegram notification failed: ${tgError.message}\n`);
|
|
280
|
+
}
|
|
88
281
|
if (!process.env.OPENCODE_SERVER_PASSWORD) {
|
|
89
282
|
process.stderr.write('\n');
|
|
90
283
|
process.stderr.write('🔓 ⚠️ Server is not password-protected! Set OPENCODE_SERVER_PASSWORD for better security.\n');
|