agentp 0.8.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 +31 -9
- package/README.md +44 -9
- package/bin/agentp +142 -28
- package/bin/ocmux +24 -6
- package/bin/tgagentp +219 -68
- package/debugging_tg_integration.md +5594 -0
- package/lib/opencode.js +45 -9
- package/package.json +1 -1
- package/tgagentp.log +2156 -0
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,28 @@ 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
|
+
|
|
76
90
|
## OpenCode tmux session model (ocmux)
|
|
77
91
|
|
|
78
92
|
- Single tmux session named `"Opencode"`
|
|
79
93
|
- 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`
|
|
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
|
|
81
95
|
- Log file per project: `/tmp/opencode-serve-<hashDir(dir)>.log` — captured via `tee`, read by ocmux polling loop
|
|
82
96
|
- Per-project `.ocmux.json` with upward directory search (git-like)
|
|
83
97
|
- Auto-restarts dead TUI panes on switch (respawn-pane or split-window)
|
|
@@ -89,10 +103,10 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
89
103
|
|---|---|---|
|
|
90
104
|
| `npm link` | local dev install |
|
|
91
105
|
| `npm install -g .` | alternative local install |
|
|
92
|
-
| `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 |
|
|
93
107
|
| `tgagentp [port]` | bridge Telegram bot ↔ opencode TUI (needs `TELEGRAM_BOT_TOKEN`) |
|
|
94
108
|
| `tgagentp --dev` | enable `/shutdown` command for remote restart |
|
|
95
|
-
| `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) |
|
|
96
110
|
| `ocmux new [dir]` | alias for `serve` (backwards compat, to be removed in 1.0) |
|
|
97
111
|
| `ocmux kill [dir]` | kill server + tmux window |
|
|
98
112
|
| `ocmux list [-l]` | list running servers |
|
|
@@ -102,7 +116,7 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
102
116
|
|
|
103
117
|
### Pending
|
|
104
118
|
|
|
105
|
-
-
|
|
119
|
+
- `agentp --getLast n` — retrieve last n answers from session history
|
|
106
120
|
|
|
107
121
|
### In Progress
|
|
108
122
|
|
|
@@ -110,12 +124,20 @@ These endpoints are consumed but not documented elsewhere in the repo:
|
|
|
110
124
|
|
|
111
125
|
### Done
|
|
112
126
|
|
|
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
|
|
113
136
|
- Logging rebalance: stdout for info, stderr for errors/debug/trace, `--verbose` flag, `2>/dev/null` default usage
|
|
114
137
|
- `/shutdown force` — refuses shutdown when busy unless `force` flag is given; goodbye message includes state
|
|
115
138
|
- `/think [on|off|switch]` — toggle real-time forwarding of model thinking messages to the chat; `--think` CLI flag to start enabled
|
|
116
139
|
- Event-driven permission handling (`POST /session/:id/prompt_async` + SSE listener + `/allow`, `/reject`, `/always` commands) — included in v0.7.0
|
|
117
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)
|
|
118
|
-
- /agents switch <name> — switch active agent on the active session
|
|
119
141
|
- /sessions new [name] — create a new session, TUI follows via /session/:id/select
|
|
120
142
|
- /sessions rename <name> — rename the current active session
|
|
121
143
|
- Merge /session into /sessions (keep /session as alias)
|
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,20 @@ 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 |
|
|
196
218
|
| `/shutdown [force]` | (requires `--dev`) Stop tgagentp; refuses if busy unless `force` is given |
|
|
197
219
|
|
|
198
220
|
### Per-server state
|
|
@@ -217,6 +239,17 @@ tgagentp
|
|
|
217
239
|
- `--think`: start with thinking messages enabled (default: off)
|
|
218
240
|
- `--dev`: enable `/shutdown` command for remote restart (run via `while true; do tgagentp --dev; done`)
|
|
219
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
|
+
|
|
220
253
|
### Logging
|
|
221
254
|
|
|
222
255
|
Informational messages (startup, discovery, session switches) go to **stdout**. Errors, warnings, and trace/debug messages go to **stderr**. With `--verbose`, detailed
|
|
@@ -243,9 +276,11 @@ A startup greeting is sent to the last known chat on boot — includes /status-s
|
|
|
243
276
|
| Variable | Description |
|
|
244
277
|
|---|---|
|
|
245
278
|
| `TELEGRAM_BOT_TOKEN` | **Required.** Telegram bot token from @BotFather. |
|
|
246
|
-
| `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). |
|
|
247
280
|
| `OPENCODE_SERVER_USERNAME` | Optional. OpenCode server username (default: `opencode`). |
|
|
248
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`). |
|
|
249
284
|
|
|
250
285
|
## License
|
|
251
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) {
|