agentp 1.14.0 → 2.0.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/CONTRIBUTING.md +80 -83
- package/README.md +135 -49
- package/bin/agentp +337 -75
- package/bin/ocmux +1028 -587
- package/docs/specification.md +12 -454
- package/docs/specification_v2.md +611 -0
- package/lib/ocmux.js +123 -197
- package/lib/opencode.js +363 -657
- package/lib/project-state.js +205 -0
- package/package.json +1 -1
package/docs/specification.md
CHANGED
|
@@ -1,458 +1,16 @@
|
|
|
1
|
-
# agentp — Specification
|
|
1
|
+
# agentp — Specification (superseded)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document described the **v1-era** architecture (one OpenCode server per
|
|
4
|
+
project, the `/tui/*` endpoints, `window_index`-based `.ocmux.json`, and the
|
|
5
|
+
`switch`/`model` subcommands). None of that is accurate any more.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
See **[specification_v2.md](./specification_v2.md)** for the current design:
|
|
6
8
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
9
|
+
- a single, **user-managed** OpenCode v2 server (no per-project servers);
|
|
10
|
+
- the **project = directory / target = session** model with a v2 `.ocmux.json`
|
|
11
|
+
(`directory`, `session`, `server`, `annotations`);
|
|
12
|
+
- `ocmux` project/TUI-window management and its interactive pickers;
|
|
13
|
+
- `agentp` resolution, deferred tickets (incl. `cancelled`) and `--qa` output;
|
|
14
|
+
- `lib/opencode.js` as the v2-only HTTP/SSE client.
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## File Reference
|
|
16
|
-
|
|
17
|
-
### `package.json`
|
|
18
|
-
|
|
19
|
-
**Version:** 0.11.4
|
|
20
|
-
|
|
21
|
-
Fields:
|
|
22
|
-
- `"bin"` — registers `agentp`, `ocmux`, `tgagentp`
|
|
23
|
-
- `"files"` — whitelist for npm publish: `bin/`, `lib/`, `README.md`
|
|
24
|
-
- `"type": "commonjs"`
|
|
25
|
-
- `"engines": { "node": ">=18" }`
|
|
26
|
-
|
|
27
|
-
### `bin/agentp` — Stdin-to-OpenCode pipe
|
|
28
|
-
|
|
29
|
-
Reads stdin, sends to the most recent or named OpenCode session, streams answer to stdout.
|
|
30
|
-
|
|
31
|
-
**Options:**
|
|
32
|
-
|
|
33
|
-
| Flag | Effect |
|
|
34
|
-
|---|---|---|
|
|
35
|
-
| `--qa` | Print prompt/answer with rulers; auto-detect tgagentp |
|
|
36
|
-
| `--defer [N]` | Deferred execution — submit a prompt and get a ticket immediately, or wait up to N seconds (default `0`) for the answer; piping the ticket back retrieves the result later |
|
|
37
|
-
| `--onlineTicket` | Print deferred tickets on a single line (default: pretty-printed JSON) |
|
|
38
|
-
| `--tg` | Forward answer via tgagentp gateway (error if unavailable) |
|
|
39
|
-
| `--no-tg` | Explicitly disable Telegram forwarding |
|
|
40
|
-
| `--flush` | Flush recorded buffer without prepending |
|
|
41
|
-
| `--getLast N` | Retrieve last N assistant answers from session history |
|
|
42
|
-
|
|
43
|
-
**Deferred tickets (`--defer`):**
|
|
44
|
-
|
|
45
|
-
Ticket format on stdout: `agentp_ticket` followed by a JSON object `{ ctime, path, server, sessionId, elapsed, defer }`, printed as pretty-printed (multi-line) JSON by default or on a single line with `--onlineTicket`. Both formats are accepted when piping a ticket back.
|
|
46
|
-
|
|
47
|
-
- First print omits `elapsed`; `defer` is included only when it was `> 0`.
|
|
48
|
-
- Re-prints (answer not ready yet) include `elapsed` computed from `ctime` (0 when `ctime` is missing/unparseable).
|
|
49
|
-
- A ticket piped back to `agentp --defer` ignores the CLI `--defer` value and uses the ticket's own `defer` (default 0): ready → print answer + delete temp file; not ready → re-print the ticket with updated `elapsed`; file missing → error, exit 1.
|
|
50
|
-
- If a ticket is followed by additional text and the answer is not ready, `agentp --defer` sends that text to the ticket's `server`/`sessionId` with `sendToSessionAsync()` (`POST /session/:id/prompt_async`), stores it in a ticket sidecar file, and re-prints the ticket without echoing the extra text. When the final answer is retrieved with `--qa`, stored follow-ups are injected into the prompt block under `📝` separators; without `--qa`, they are omitted from output. If the answer is ready, appended text is not sent and is printed after the returned answer. Older tickets without `server`/`sessionId` can retrieve results but cannot queue follow-up text.
|
|
51
|
-
- New run with `--defer N` (N > 0): wait up to N seconds polling the temp file; answer arrives in time → print + delete; else print the ticket and keep the background child alive.
|
|
52
|
-
|
|
53
|
-
**Protocol:**
|
|
54
|
-
|
|
55
|
-
1. Reads all stdin → string
|
|
56
|
-
2. Calls `listSessions(server)` → finds most recently updated session (or creates one named `agentp`)
|
|
57
|
-
3. Calls `sendToSession(server, sessionId, text, agent?, cancelRef?)` → returns concatenated text parts
|
|
58
|
-
4. Prints answer to stdout
|
|
59
|
-
5. If `--tg` or `--qa` (auto-detect):
|
|
60
|
-
- POSTs `{ text, server }` to tgagentp gateway at `http://localhost:<port>/send`
|
|
61
|
-
- Gateway response includes `{ buffered }` — recorded conversation messages
|
|
62
|
-
- `--qa` prepends buffered context to stdout; `--flush` skips prepending
|
|
63
|
-
|
|
64
|
-
**HTTP timeout:** 5 seconds. Pre-send gate check + post-send warning for `--tg`.
|
|
65
|
-
|
|
66
|
-
### `bin/ocmux` — Tmux server manager
|
|
67
|
-
|
|
68
|
-
Manages per-project OpenCode servers in a persistent `Opencode` tmux session.
|
|
69
|
-
|
|
70
|
-
**Subcommands:**
|
|
71
|
-
|
|
72
|
-
| Command | Description |
|
|
73
|
-
|---|---|
|
|
74
|
-
| `serve [dir]` | Create server + TUI pane in a new tmux window |
|
|
75
|
-
| `new [dir]` | Alias for `serve` (deprecated) |
|
|
76
|
-
| `kill [dir]` | Kill server, remove tmux window + `.ocmux.json` |
|
|
77
|
-
| `resurrect [dir]` | Recover dead server: kill old window, remove state file, create fresh server + TUI |
|
|
78
|
-
| `model [ref]` | Switch the model of the newest session (interactive picker without `ref`; prints the server URL on success so `agentp $(ocmux model <ref>)` composes) |
|
|
79
|
-
| `switch` | Interactive session picker (TTY required) |
|
|
80
|
-
| `list [-l]` | List all running servers |
|
|
81
|
-
| _(no arg)_ | Switch to existing server (searches upward for `.ocmux.json`) |
|
|
82
|
-
|
|
83
|
-
**`model` behavior:** Targets the newest session on the server found upward from `$PWD`. With no argument it opens an interactive model picker (all models, arrow/`j`/`k`, Enter to switch, `q`/`Ctrl+C` to quit; requires a TTY). With a reference it switches directly; references may be full (`providerID/modelID`), partial (matched uniquely against label or bare id), and carry a variant suffix (`#variant`). On success it writes the server URL to stdout (plus a confirmation to stderr), so `agentp $(ocmux model <ref>)` switches the model before the next prompt is sent. Multiple matches open the picker on a TTY, otherwise error listing the candidates. Rejects `--git`, `--GIT`, `--print-logs`, and directory arguments.
|
|
84
|
-
|
|
85
|
-
**`switch` behavior:** Renders an interactive menu of all running servers on an alt screen with columns `dirname | status | url | full path`. `j`/`k` or arrow keys move the cursor; `Enter`/`Space` activates the selected server (switches its tmux window) and keeps the menu open; `q`/`Ctrl+C` exits and prints the URL of the last selected server (if any). The row matching the tmux-active window (queried live from tmux via `activeWindowIndex()` on every redraw) is highlighted across the full line width, so the highlight tracks both local activations and external tmux window switches. Errors (exit 1) when no TTY or no servers. Rejects `--git`, `--GIT`, `--print-logs`, and directory arguments.
|
|
86
|
-
|
|
87
|
-
**Default no-server behavior:** When no `.ocmux.json` is found for the current/target directory or any parent, default mode prints the primary `Error: no opencode server found...` line to stdout and the `Run 'ocmux serve'...` hint to stderr. This makes command substitution (`agentp $(ocmux)`) fail safely instead of passing no argument and falling back to `agentp`'s default server.
|
|
88
|
-
|
|
89
|
-
**Flags:** `--git`, `--GIT`, `--print-logs`, `-l`, `--version`
|
|
90
|
-
|
|
91
|
-
**Window layout:**
|
|
92
|
-
|
|
93
|
-
- Pane 0: server (`opencode serve --port 0 2>&1 | tee <logfile>`)
|
|
94
|
-
- Pane 1+: TUI (`opencode --server '<url>' --continue` on OpenCode v2; `opencode attach --continue '<url>'` on older versions — chosen automatically via `opencode --version`)
|
|
95
|
-
- Log: `/tmp/opencode-serve-<hashDir(dir)>.log`
|
|
96
|
-
- State: `<dir>/.ocmux.json` (contains `url`, `logfile`, `window_index`)
|
|
97
|
-
|
|
98
|
-
**State file discovery:** Upward from target directory, git-like.
|
|
99
|
-
|
|
100
|
-
### `bin/tgagentp` — Telegram bridge
|
|
101
|
-
|
|
102
|
-
Long-polling Telegram bot that routes messages to OpenCode servers.
|
|
103
|
-
|
|
104
|
-
**Options:**
|
|
105
|
-
|
|
106
|
-
| Flag | Effect |
|
|
107
|
-
|---|---|
|
|
108
|
-
| `--dev` | Enable `/shutdown` for remote restart, verbose logging, and structured message traffic log to `/tmp/tgagentp-msg.log` |
|
|
109
|
-
| `--think` | Start with thinking forwarding enabled |
|
|
110
|
-
| `--verbose` | Detailed logs on stderr |
|
|
111
|
-
|
|
112
|
-
**Environment variables:**
|
|
113
|
-
|
|
114
|
-
| Variable | Required | Default | Description |
|
|
115
|
-
|---|---|---|---|
|
|
116
|
-
| `TELEGRAM_BOT_TOKEN` | Yes | — | Bot token from @BotFather |
|
|
117
|
-
| `TGAGENTP_ALLOWED_CHAT_IDS` | No | all | Comma-separated allowed chat IDs |
|
|
118
|
-
| `TGAGENTP_PORT` | No | random | Agentp gateway HTTP port |
|
|
119
|
-
| `TGAGENTP_DEBOUNCE_MS` | No | 5000 | Debounce for queued-agentp Telegram notifications |
|
|
120
|
-
| `OPENCODE_SERVER_PASSWORD` | No | — | HTTP Basic Auth for OpenCode + gateway |
|
|
121
|
-
| `OPENCODE_SERVER_USERNAME` | No | opencode | HTTP Basic Auth username |
|
|
122
|
-
|
|
123
|
-
### `lib/opencode.js` — HTTP session API client
|
|
124
|
-
|
|
125
|
-
Shared by `bin/agentp` and `bin/tgagentp`. Functions:
|
|
126
|
-
|
|
127
|
-
| Function | HTTP | Description |
|
|
128
|
-
|---|---|---|
|
|
129
|
-
| `getAuthHeaders()` | — | Reads `OPENCODE_SERVER_PASSWORD/USERNAME` |
|
|
130
|
-
| `makeRequest(options, data)` | — | Thin `http.request` wrapper |
|
|
131
|
-
| `buildJsonRequest(url, method, body)` | — | Builds request options |
|
|
132
|
-
| `sendText(server, text)` | POST /tui/* | Convenience: clear + append + submit prompt |
|
|
133
|
-
| `listenForFinalAnswer(server, onText?, cancelRef?)` | GET /event | SSE listener; `cancelRef` enables abort |
|
|
134
|
-
| `listSessions(server, directory?)` | GET /session | Returns parsed JSON array |
|
|
135
|
-
| `createSession(server, title?)` | POST /session | Creates a session |
|
|
136
|
-
| `updateSession(server, id, title, agent?)` | PATCH /session/:id | Updates session properties |
|
|
137
|
-
| `sendToSession(server, id, text, agent?, cancelRef?)` | POST /session/:id/message | Synchronous message; optional abort |
|
|
138
|
-
| `sendToSessionAsync(server, id, text, agent?)` | POST /session/:id/prompt_async | Non-blocking (204); answer via SSE |
|
|
139
|
-
| `respondToPermission(server, id, permissionId, response)` | POST /session/:id/permissions/:id | Permission response |
|
|
140
|
-
| `respondToQuestion(server, id, questionId, answer)` | POST /session/:id/questions/:id | Question response |
|
|
141
|
-
| `selectSession(server, id)` | POST /session/:id/select | TUI navigation |
|
|
142
|
-
| `listAgents(server)` | GET /agent | Returns parsed array |
|
|
143
|
-
| `listProviders(server)` | GET /provider | Returns parsed array |
|
|
144
|
-
| `isServerAlive(url)` | GET /session (5s timeout) | Health check, resolves true/false |
|
|
145
|
-
| `listenForSessionEvents(server, id, callbacks, cancelRef?)` | GET /event | SSE with structured events |
|
|
146
|
-
|
|
147
|
-
### `lib/ocmux.js` — Tmux management library
|
|
148
|
-
|
|
149
|
-
Shared by `bin/ocmux` and `bin/tgagentp`. Functions:
|
|
150
|
-
|
|
151
|
-
| Function | Description |
|
|
152
|
-
|---|---|
|
|
153
|
-
| `readState(file)` | Parses `.ocmux.json`; returns `null` on error |
|
|
154
|
-
| `statefileFor(dir)` | `path.join(dir, '.ocmux.json')` |
|
|
155
|
-
| `tuiPaneId(windowIndex)` | Returns pane ID of TUI pane (pane index != 0); `null` if dead |
|
|
156
|
-
| `windowByDir(dir)` | Returns tmux window index matching directory name |
|
|
157
|
-
| `windowNameByIndex(idx)` | Returns window name at given index |
|
|
158
|
-
| `activeWindowIndex()` | Returns index of currently selected tmux window |
|
|
159
|
-
| `paneCount(windowIndex)` | Number of panes in a window |
|
|
160
|
-
| `listServers()` | Scans all tmux windows for `.ocmux.json`; returns `{ url, dir, index, status }` |
|
|
161
|
-
| `activateServer(dir, index, url)` | Pin window name, restart dead TUI pane, select window, zoom |
|
|
162
|
-
| `hashDir(dir)` | MD5 hash (first 12 chars) |
|
|
163
|
-
| `logfileFor(dir)` | `/tmp/opencode-serve-<hash>.log` |
|
|
164
|
-
| `sleep(seconds)` | `execSync sleep` |
|
|
165
|
-
| `ensureSession()` | Create `Opencode` tmux session if missing |
|
|
166
|
-
| `pinWindowName(windowIndex)` | Disable tmux auto-rename for the window |
|
|
167
|
-
| `resurrectServer(dir, printLogs)` | Kill old window, remove state file, create fresh server + TUI; returns `{ url, dir }` |
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## Persistence / State Files
|
|
172
|
-
|
|
173
|
-
### `{project_dir}/.ocmux.json`
|
|
174
|
-
|
|
175
|
-
Per-project state file created by `ocmux serve`:
|
|
176
|
-
|
|
177
|
-
```json
|
|
178
|
-
{
|
|
179
|
-
"url": "http://localhost:40999",
|
|
180
|
-
"logfile": "/tmp/opencode-serve-abc123.log",
|
|
181
|
-
"window_index": 5
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
- **url:** The server's listen URL (assigned by `--port 0` — changes on every restart)
|
|
186
|
-
- **logfile:** Path to the server's tee'd log output (used for URL polling during startup)
|
|
187
|
-
- **window_index:** The tmux window index within the `Opencode` session
|
|
188
|
-
|
|
189
|
-
Discovered by upward directory search from the current/target directory (git-like). Used by `ocmux`, `tgagentp`, and `agentp` for server discovery.
|
|
190
|
-
|
|
191
|
-
### `/tmp/tgagentp-port`
|
|
192
|
-
|
|
193
|
-
Created by tgagentp on startup. Contains the HTTP port number of the agentp gateway. Read by `agentp --tg` to discover the gateway.
|
|
194
|
-
|
|
195
|
-
```
|
|
196
|
-
49152
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### `/tmp/tgagentp-connections.json`
|
|
200
|
-
|
|
201
|
-
Created and maintained by tgagentp. Persists chat↔server directory mappings across restarts.
|
|
202
|
-
|
|
203
|
-
```json
|
|
204
|
-
{
|
|
205
|
-
"connections": [
|
|
206
|
-
{
|
|
207
|
-
"chatId": "123456789",
|
|
208
|
-
"threadId": null,
|
|
209
|
-
"dir": "/home/user/projects/myapp"
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
"chatId": "987654321",
|
|
213
|
-
"threadId": "42",
|
|
214
|
-
"dir": "/home/user/projects/other"
|
|
215
|
-
}
|
|
216
|
-
]
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
- **chatId:** Telegram chat ID (string)
|
|
221
|
-
- **threadId:** Telegram forum topic ID or `null`
|
|
222
|
-
- **dir:** Project directory (where `.ocmux.json` lives)
|
|
223
|
-
|
|
224
|
-
On restart, tgagentp reads this file, reads `.ocmux.json` from each `dir` to discover the fresh URL, and reconnects. If `.ocmux.json` is missing, the chat starts disconnected.
|
|
225
|
-
|
|
226
|
-
**Lifecycle:**
|
|
227
|
-
- Written on every `/servers switch` (via `setServerForChat`)
|
|
228
|
-
- Removed for displaced chats on force-takeover (via `removeConnection`)
|
|
229
|
-
- Removed for pruned chats on restart (secondary per URL)
|
|
230
|
-
- Cleared on `/shutdown clear` (via `clearConnections`)
|
|
231
|
-
- Updated on group migration (regular group → supergroup, e.g. enabling topics): all `chatId` references in the file are replaced with the new ID
|
|
232
|
-
|
|
233
|
-
### `/tmp/opencode-serve-<hashDir(dir)>.log`
|
|
234
|
-
|
|
235
|
-
Server log file. Continuously written by `tee` in the server pane. Polled by `doNew`/`resurrectServer` for URL extraction during startup:
|
|
236
|
-
|
|
237
|
-
```
|
|
238
|
-
opencode server listening on http://localhost:40999
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
OpenCode v2 prints `server listening on http://...` (without the `opencode ` prefix); both formats are matched for URL extraction.
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
## Inter-Process Communication
|
|
246
|
-
|
|
247
|
-
### Agentp Gateway (tgagentp ↔ agentp)
|
|
248
|
-
|
|
249
|
-
tgagentp starts an HTTP server on `127.0.0.1:<port>` (random by default, configurable via `TGAGENTP_PORT`). `agentp --tg` POSTs answers to this gateway.
|
|
250
|
-
|
|
251
|
-
**Endpoint:** `POST /send`
|
|
252
|
-
|
|
253
|
-
**Request:**
|
|
254
|
-
```json
|
|
255
|
-
{
|
|
256
|
-
"text": "Answer from OpenCode",
|
|
257
|
-
"server": "http://localhost:40999"
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
**Headers:**
|
|
262
|
-
- `Authorization: Basic <base64>` — verified against `OPENCODE_SERVER_PASSWORD`
|
|
263
|
-
|
|
264
|
-
**Response:**
|
|
265
|
-
```json
|
|
266
|
-
{
|
|
267
|
-
"buffered": [
|
|
268
|
-
{"role": "user", "text": "What is X?"},
|
|
269
|
-
{"role": "assistant", "text": "X is..."}
|
|
270
|
-
]
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**Flow:**
|
|
275
|
-
1. Gateway looks up `serverOwners` map to find which chat owns the target server
|
|
276
|
-
2. If found and it's the active server for that chat → forwards immediately via `sendLongMessage`
|
|
277
|
-
3. If not the active server → debounce-queues with notification after `TGAGENTP_DEBOUNCE_MS`
|
|
278
|
-
4. Returns recorded conversation buffer (if any) — `agentp --qa` prepends this to stdout
|
|
279
|
-
5. If no owner found (no chat connected to that server) → returns buffered data, drops the message
|
|
280
|
-
|
|
281
|
-
### Tmux Session Model (ocmux)
|
|
282
|
-
|
|
283
|
-
All servers live in a single tmux session named `Opencode`. Each project gets one window:
|
|
284
|
-
|
|
285
|
-
```
|
|
286
|
-
Session: Opencode
|
|
287
|
-
├── Window 3: /home/user/project-a
|
|
288
|
-
│ ├── Pane 0: opencode serve --port 0 ... (server)
|
|
289
|
-
│ └── Pane 1: TUI (opencode --server <url> --continue / attach, zoomed)
|
|
290
|
-
├── Window 4: /home/user/project-b
|
|
291
|
-
│ ├── Pane 0: opencode serve --port 0 ...
|
|
292
|
-
│ └── Pane 1: TUI (opencode --server <url> --continue / attach)
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
Window names are the full project directory path. Pane 0 is always the server; pane 1+ is the TUI. The TUI pane is zoomed on switch/create. Dead TUI panes are auto-restarted on switch.
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## Chat-Server Ownership Model (tgagentp)
|
|
300
|
-
|
|
301
|
-
### States
|
|
302
|
-
|
|
303
|
-
- **Disconnected:** `chatState.serverBase === null`. Only `/help`, `/servers`, `/start` work.
|
|
304
|
-
- **Connected:** `chatState.serverBase === <url>`. Server is owned by this chat.
|
|
305
|
-
- **Force-taken:** Previous owner gets `serverBase = null` and a Telegram notification.
|
|
306
|
-
|
|
307
|
-
### Data Structures
|
|
308
|
-
|
|
309
|
-
```javascript
|
|
310
|
-
serverOwners = {
|
|
311
|
-
"http://localhost:40999": { chatId: "123", threadId: null },
|
|
312
|
-
"http://localhost:41000": { chatId: "456", threadId: "42" },
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
Maps server URL → owning chat. Used by the agentp gateway to route forwarded messages. Updated on every `/servers switch` and on startup restoration.
|
|
317
|
-
|
|
318
|
-
```javascript
|
|
319
|
-
chatStates = {
|
|
320
|
-
"123": {
|
|
321
|
-
chatId: "123",
|
|
322
|
-
threadId: null,
|
|
323
|
-
serverBase: "http://localhost:40999",
|
|
324
|
-
recording: { active: false, paused: false, messages: [], bytes: 0 },
|
|
325
|
-
ring: { messages: [], bytes: 0 },
|
|
326
|
-
},
|
|
327
|
-
"456:42": {
|
|
328
|
-
chatId: "456",
|
|
329
|
-
threadId: 42,
|
|
330
|
-
serverBase: null,
|
|
331
|
-
...
|
|
332
|
-
},
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
Keyed by `convKey(chatId, threadId)` → `${chatId}:t${threadId}` (or just `chatId` for non-thread chats). Created on first message from each chat.
|
|
337
|
-
|
|
338
|
-
### Connection Flow
|
|
339
|
-
|
|
340
|
-
1. **First message** → `getChatState` creates state with `serverBase: null`
|
|
341
|
-
2. **`/servers switch <name>`** → `cmdServers`:
|
|
342
|
-
- Finds server directory by name match
|
|
343
|
-
- Checks `serverOwners` — warns if owned by different chat (unless `--force`)
|
|
344
|
-
- Calls `setServerForChat(chatState, url)`:
|
|
345
|
-
- Iterates all `chatStates`, sets `serverBase = null` for any other chat on same URL
|
|
346
|
-
- Sets `chatState.serverBase = url`
|
|
347
|
-
- Sets `serverOwners[url] = { chatId, threadId }`
|
|
348
|
-
- Saves connection to `/tmp/tgagentp-connections.json`
|
|
349
|
-
- Activates tmux window
|
|
350
|
-
- Flushes agentp queue for this server
|
|
351
|
-
3. **Restart** → reads `/tmp/tgagentp-connections.json`, restores each connection:
|
|
352
|
-
- Phase 1: restores all chat states (`cs.serverBase`) and populates `serverOwners`
|
|
353
|
-
- `serverOwners` picks non-thread (private chat) over topic threads as owner per URL
|
|
354
|
-
- Phase 2: prunes secondary connections — only the owner per URL survives, others have `serverBase` cleared and connection removed from file
|
|
355
|
-
- Phase 3: "Bot started" notification sent only to owner per URL (first-to-notify wins)
|
|
356
|
-
4. **`/force-switch <name>`** → same as `/servers switch --force <name>`: bypasses ownership check, takes over server, notifies previous owner
|
|
357
|
-
5. **`/disconnect`** → clears `serverBase`, removes ownership, deletes connection from file
|
|
358
|
-
|
|
359
|
-
### Disconnected Guard
|
|
360
|
-
|
|
361
|
-
Both command and non-command paths in the message loop check `cs.serverBase`:
|
|
362
|
-
|
|
363
|
-
- Commands: only `/help`, `/servers`, `/force-switch`, `/start`, `/comment`, `/shutdown` pass through without a server
|
|
364
|
-
- Non-commands: `🔌 Not connected. Use /servers to see available servers.`
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## `//command` TUI Passthrough
|
|
369
|
-
|
|
370
|
-
Messages starting with `//` are forwarded to the TUI as raw keystrokes (not AI text). The `//` prefix is stripped, a trailing space is appended to activate the TUI as-you-type menu, and `tmux send-keys` sends the keys to the TUI pane. An SSE listener connects before the Enter key to catch the AI response.
|
|
371
|
-
|
|
372
|
-
**Flow:**
|
|
373
|
-
1. Chat message: `//init`
|
|
374
|
-
2. Strips `//` → `init`
|
|
375
|
-
3. Prepends `/` → `/init`
|
|
376
|
-
4. Appends space → `/init ` (selects TUI menu item)
|
|
377
|
-
5. `tmux send-keys` to TUI pane: `C-u` (clear line), type `/init `, Enter
|
|
378
|
-
6. SSE listener starts before Enter (15s timeout)
|
|
379
|
-
7. If AI responds: forwards full answer to Telegram
|
|
380
|
-
8. If timeout: sends `✅ /init submitted.` confirmation
|
|
381
|
-
|
|
382
|
-
**Busy guard:** `//command` is blocked when `st.busy` is true (same "use /cancel" message as regular messages).
|
|
383
|
-
|
|
384
|
-
**Commands that trigger AI responses:** `//init`, `//clear`, `//history` (and any other TUI command that produces an AI answer). Quick commands like `/exit` timeout without an answer.
|
|
385
|
-
|
|
386
|
-
---
|
|
387
|
-
|
|
388
|
-
## Question Handling
|
|
389
|
-
|
|
390
|
-
When the AI asks a structured multiple-choice question (via `question.asked` SSE event), tgagentp forwards the question to Telegram with numbered options.
|
|
391
|
-
|
|
392
|
-
**Notification format:**
|
|
393
|
-
```
|
|
394
|
-
❓ **Question from the AI**
|
|
395
|
-
|
|
396
|
-
What would you like to test?
|
|
397
|
-
|
|
398
|
-
1. Option A
|
|
399
|
-
2. Option B
|
|
400
|
-
3. Option C
|
|
401
|
-
|
|
402
|
-
Use /answer <number> to respond.
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
**Response:**
|
|
406
|
-
- `/answer <number>` → `POST /session/:id/questions/:id` with the selected option's value
|
|
407
|
-
- Only one question can be pending at a time per server
|
|
408
|
-
- The question is stored per-server in `chatStates.pendingQuestion`
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
## Logging Conventions (tgagentp)
|
|
413
|
-
|
|
414
|
-
- **stdout** — informational messages (startup, discovery, session switches)
|
|
415
|
-
- **stderr** — errors (always shown) + trace/debug (only `--verbose` flag)
|
|
416
|
-
- Default usage: `tgagentp 2>/dev/null`
|
|
417
|
-
|
|
418
|
-
### Dev Mode Message Log
|
|
419
|
-
|
|
420
|
-
When `--dev` is active, tgagentp writes a structured JSON-lines message traffic log to `/tmp/tgagentp-msg.log`:
|
|
421
|
-
|
|
422
|
-
```
|
|
423
|
-
{"ts":"2026-06-09 06:25:01","type":"in","chatId":-1004291964025,"from":"user","text":"/status"}
|
|
424
|
-
{"ts":"2026-06-09 06:25:01","type":"out","chatId":-1004291964025,"from":"assistant","text":"**Server:** ..."}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
Each line contains a timestamp, direction (`in`/`out`), chat ID, sender, and text content.
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
## Error Handling
|
|
432
|
-
|
|
433
|
-
### Server Health (tgagentp)
|
|
434
|
-
|
|
435
|
-
```javascript
|
|
436
|
-
// Pre-send check
|
|
437
|
-
if (st.serverDead) {
|
|
438
|
-
const alive = await isServerAlive(url); // GET /session, 5s timeout
|
|
439
|
-
if (alive) { st.serverDead = false; }
|
|
440
|
-
}
|
|
441
|
-
if (st.serverDead) {
|
|
442
|
-
// Auto-queue the message
|
|
443
|
-
st.messageQueue.push({ text, replyTo });
|
|
444
|
-
// Notify user with options (ocmux serve, /servers switch, /flush)
|
|
445
|
-
}
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
Health check runs before every non-command message. Dead servers cause auto-queue with user notification. Connection errors in `processMessageAsync` mark `serverDead = true` and requeue.
|
|
449
|
-
|
|
450
|
-
### Busy Server
|
|
451
|
-
|
|
452
|
-
When `st.busy` is true, non-command messages are dropped with a "⏳ Busy" notice. Use `/queue <message>` to explicitly queue.
|
|
453
|
-
|
|
454
|
-
### Gateway Errors
|
|
455
|
-
|
|
456
|
-
- Missing owning chat → return buffered data, drop message
|
|
457
|
-
- Socket errors → caught by try-catch in gateway handler (no crash)
|
|
458
|
-
- HTTP timeout (5s) in agentp → post-send warning (not hard error)
|
|
16
|
+
For a quick start, see [../README.md](../README.md).
|