agentp 2.0.1 → 2.1.1

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 CHANGED
@@ -6,7 +6,8 @@ agentp is a collection of three **zero-dependency** Node.js CLI tools that
6
6
  extend [OpenCode](https://opencode.ai) v2:
7
7
 
8
8
  - **`agentp`** — pipes prompt text into a running OpenCode session and streams the answer back to stdout.
9
- - **`ocmux`** — manages per-project TUI windows in tmux (session picker, project switcher, create/rename/delete/annotate sessions) on top of a single **user-managed** OpenCode server.
9
+ - **`ocmux`** — routes project sessions and optional user-placed dedicated/shared
10
+ TUI panes in tmux on top of user-managed OpenCode servers.
10
11
  - **`tgagentp`** — bridges a Telegram bot chat with OpenCode (multi-chat, multi-server, file sharing). *Experimental.*
11
12
 
12
13
  The project aims to stay **zero npm dependencies** — everything uses only the
@@ -42,12 +43,13 @@ npm install -g .
42
43
  agentp/
43
44
  ├── bin/
44
45
  │ ├── agentp — stdin-to-session pipe
45
- │ ├── ocmux — project/TUI window manager + interactive pickers
46
+ │ ├── ocmux — project/session router + interactive pickers
46
47
  │ └── tgagentp — Telegram bot bridge
47
48
  ├── lib/
48
49
  │ ├── opencode.js — OpenCode v2 HTTP/SSE client (shared by all three)
49
- │ ├── ocmux.js — tmux helpers (shared by ocmux + tgagentp)
50
+ │ ├── ocmux.js — registered-TUI routing helpers
50
51
  │ ├── project-state.js — `.ocmux.json` v2 schema + per-session reminders
52
+ │ ├── tui-registry.js — private runtime registry + tmux pane operations
51
53
  │ ├── tui-cmd.js — tmux send-keys passthrough (tgagentp)
52
54
  │ ├── file-share.js — telegram-shared directory + upload/download
53
55
  │ └── telegram-*.js — Telegram API + formatting helpers
@@ -72,8 +74,8 @@ agentp/
72
74
  ### Conventions
73
75
 
74
76
  - **HTTP:** use `lib/opencode.js` helpers — never raw `http.request`.
75
- - **tmux:** use `lib/ocmux.js` helpers (`_tmux` / exported wrappers) — never raw
76
- `spawnSync`.
77
+ - **tmux TUI registrations:** use `lib/tui-registry.js`; keep socket, pane,
78
+ token, and PID data out of `.ocmux.json`.
77
79
  - **State:** `.ocmux.json` I/O goes through `lib/project-state.js`
78
80
  (`readProjectState`, `writeProjectState`, `readAnnotations`, `writeAnnotation`,
79
81
  atomic writes). Never hand-roll reads/writes.
package/README.md CHANGED
@@ -8,7 +8,9 @@
8
8
  This package provides three CLI tools:
9
9
 
10
10
  - **`agentp`** — pipes prompt text into a running OpenCode server and streams the assistant final answer back to stdout
11
- - **`ocmux`** — manages project TUI windows in tmux on top of a single user-managed OpenCode server (session picker, project switcher, create/rename/delete/annotate sessions)
11
+ - **`ocmux`** — routes project sessions and optionally drives user-placed,
12
+ registered OpenCode TUI panes in tmux (session picker, project switcher,
13
+ create/rename/delete/annotate sessions)
12
14
  - **`tgagentp`** — bridges a Telegram bot chat with all running OpenCode servers (receives messages from Telegram, routes them to the active server, sends answers back). Supports slash commands for multi-server management, session switching, agent/model listing, including file sharing from the chat.
13
15
 
14
16
  It is designed for prompt-driven workflows where you want to do things like:
@@ -42,7 +44,7 @@ npm link
42
44
 
43
45
  - Node.js 18+
44
46
  - **OpenCode v2** (`opencode serve`) — the server is user-managed; these tools only check it is reachable and complain otherwise.
45
- - [tmux](https://github.com/tmux/tmux) when using `ocmux` (project TUI windows).
47
+ - [tmux](https://github.com/tmux/tmux) when using registered `ocmux` TUIs.
46
48
 
47
49
  ## Servers
48
50
 
@@ -323,18 +325,21 @@ Useful to grab recent answers without sending a new prompt.
323
325
 
324
326
  ## ocmux
325
327
 
326
- Manage **project TUI windows** in tmux on top of a single user-managed OpenCode
327
- server. A project is a directory holding a `.ocmux.json` state file recording
328
- the target session; an `Opencode` tmux session holds one window per project
329
- (TUI only, pane 0).
328
+ Manage project/session routing on top of a user-managed OpenCode server. A
329
+ project is a directory holding a `.ocmux.json` state file recording its target
330
+ session. TUIs are optional: `ocmux tui` can register any tmux pane as a
331
+ project-dedicated display, while `ocmux tui --shared` registers one fallback
332
+ display that follows session switches from every project.
330
333
 
331
334
  ```bash
332
- ocmux [-l] [<subcommand>] [<directory>]
335
+ ocmux [-l] [--all-projects] [<subcommand>] [<directory>]
333
336
  ```
334
337
 
335
338
  Without arguments (and with a TTY), opens an **interactive session picker** for
336
339
  the project found upward from `<directory>` (default: `$PWD`):
337
340
 
341
+ - the title bar names the project being worked on — `ocmux — <project> sessions`
342
+ — so the list is never ambiguous
338
343
  - sessions are listed most-recently-viewed first
339
344
  - `/` starts an **incremental search** of the list (matches title/id,
340
345
  case-insensitive, space-separated tokens are ANDed). While the search line is
@@ -347,16 +352,39 @@ the project found upward from `<directory>` (default: `$PWD`):
347
352
  and switches normally; `ESC`/`q` cancels back to the session list and returns
348
353
  the TUI to the stored session; deselecting down to a single session selects
349
354
  that remaining session. New selections open in the TUI for inspection.
350
- Broadcast mode also has `h` help, `d` delete, and `m` to change the model for
351
- every selected session.
355
+ Broadcast mode also has `h` help, `d` delete, `D` (Shift+d) to delete every
356
+ selected session at once (confirmation in the status bar), and `m` to change
357
+ the model for every selected session. The `Broadcast to sessions: …` line
358
+ caps itself to one row — up to 480 characters of names (or the terminal
359
+ width, whichever is smaller); past that the selection is truncated from the
360
+ beginning with a leading `...`, so the footer never wraps.
352
361
  - `n` create (name input) · `r` rename (edit in place) · `R` set a reminder ·
353
362
  `d` delete (confirm) · `a` switch agent · `m` switch model · `p` project
354
- switcher · `h` help · `q` quit
363
+ switcher (inspect-only by default) · `h` help · `q` quit. The `n`/`r`/`R`
364
+ prompts and the `d` confirmation take over the **status bar**, with their
365
+ essential keys on the right (`Enter: create/rename/save · Esc: cancel`,
366
+ `y: delete · n/Esc: cancel`). While a question is active the bar flips from
367
+ brown to the same **light yellow as the list pointer**, so the change of
368
+ state is obvious at a glance. Deleting the current session adopts the session
369
+ under the cursor as the new current (recorded, refreshed in the TUI, and
370
+ highlighted)
371
+ - `m` opens the model list **sorted by provider** with the cursor already on the
372
+ session's **current model** — also in broadcast mode, where it starts from the
373
+ cursor session's model, so a bulk change begins where you are
355
374
  - **`q` only quits the session picker.** In every other menu (model/agent
356
375
  pickers, project switcher, help/input prompts) `q`/`ESC` just closes that menu
357
376
  and returns to the previous one. **`Ctrl+C` fully exits** `ocmux` from any menu.
358
- - switching updates `.ocmux.json` and relaunches the TUI on the chosen session
359
- (`opencode --server <url> --session <id>`); silent on success
377
+ - switching updates `.ocmux.json` and refreshes the project's dedicated TUI, or
378
+ the shared TUI when no live dedicated one exists; silent on success. The file
379
+ updated is always the one of the project being listed — your own project
380
+ unless ocmux was started with `--all-projects`. Opening the picker in a
381
+ project also routes that project's TUI to its stored session (a TUI already
382
+ showing it is left untouched), so running `ocmux` from anywhere switches the
383
+ display even without picking a row
384
+ - when `.ocmux.json`'s server is unreachable but the default server
385
+ (`$OCMUX_SERVER` or `http://localhost:4096`) answers, `ocmux` offers (on a TTY)
386
+ to repoint the project to the default and rewrites `server` in `.ocmux.json`
387
+ if you confirm
360
388
  - new sessions inherit the model of the previously selected session (v2
361
389
  sessions created via the API have no model and won't run a prompt until set)
362
390
 
@@ -370,32 +398,98 @@ reported individually with session name, id, error, and timestamp.
370
398
 
371
399
  Subcommands:
372
400
 
373
- - **`serve [--server <url>] [--git|--GIT] [dir]`** — create a project TUI window
401
+ - **`serve [--server <url>] [--git|--GIT] [dir]`** — initialize project state
374
402
  (checks the server is reachable first). Aliased as `new` for backwards
375
403
  compatibility. `--git`/`--GIT` resolve `dir` to the nearest parent with a
376
404
  `.git` entry / directory.
405
+ - **`tui [--shared] [--server <url>] [dir]`** — register the current tmux pane and run OpenCode
406
+ in it. Without `--shared`, the pane follows only that project. With
407
+ `--shared`, it becomes the one fallback TUI for all projects and can reconnect
408
+ across server URLs as selections change; `--shared` also starts without a
409
+ `.ocmux.json`, defaulting to the default server. Re-registering a slot stops
410
+ its old wrapper without destroying the old pane. Closing the TUI unregisters
411
+ it.
412
+ `tui --list` lists live registrations; `tui [--shared] --status` inspects a
413
+ dedicated/shared slot; `tui [--shared] --detach` unregisters that slot without
414
+ closing the pane or its current TUI.
377
415
  - **`session <id|title> [dir]`** — non-interactive session switch.
378
- - **`list [-l]`** — list project windows (with `-l`, their server URL).
379
- - **`model [ref]`** — switch the model of the selected session.
380
- - **`kill [dir]`** — close the project's TUI window. **Keeps `.ocmux.json`**
381
- (session memory; marks it `stopped`).
382
- - **`resurrect [dir]`** — recreate the project window from its state file.
416
+ - **`list [-l]`** — list configured projects and whether their route is
417
+ `project`, `shared`, or `headless` (with `-l`, their server URL).
383
418
  - **`migrate`** — rewrite legacy (v1-style) `.ocmux.json` files to the v2 schema.
384
419
 
385
420
  The old `switch` subcommand is gone: press **`p`** inside the session picker to
386
- open the (read-only) project switcher instead. `ocmux` never starts or stops the
421
+ open the project switcher instead. The switcher is a **foldable tree**: `▸`/`▾`
422
+ marks a project as folded/unfolded, `Space` folds/unfolds a project's sessions
423
+ (fetched once per project, most recent first), and `/` searches projects *and*
424
+ their unfolded sessions (a matching session keeps its project header visible).
425
+
426
+ By default the switcher is an **inspector**: `Enter` on a project row routes its
427
+ current session to the applicable registered TUI, `Enter`/`Space` on a session
428
+ row shows that session, and you **always come back to your own project's
429
+ list** when you leave it (`q`). No `.ocmux.json` is ever written from there, so
430
+ `agentp` — which reads the state file of the directory it runs in — keeps
431
+ prompting your own project's session.
432
+
433
+ Pass **`--all-projects`** to turn it into a real switcher: selecting a project
434
+ then moves the session picker over to that project, and picking a session there
435
+ updates *that* project's `.ocmux.json` (which is what `agentp` reads when it
436
+ runs in that directory). The title bar always names the project being listed, so
437
+ either way you can tell where you are. `ocmux` never starts or stops the
387
438
  OpenCode server — run `opencode serve` yourself (see [Versioning](#versioning)
388
439
  for the pairing policy).
389
440
 
390
- Options: `-l` · `--version` · `-h` · `--` (treat the next argument as a directory).
441
+ Options: `-l` · `--all-projects` · `--shared` (only with `tui`) · `--version` ·
442
+ `-h` · `--` (treat the next argument as a directory). There is no `--global`
443
+ alias.
391
444
 
392
445
  Notes:
393
446
 
394
- - If `<directory>` is not a valid path, `ocmux` matches it against the basenames
395
- of existing project windows (exact unique match).
396
447
  - If the server is password-protected (`OPENCODE_SERVER_PASSWORD`), both
397
448
  `agentp` and `ocmux` send the required HTTP Basic Auth credentials.
398
449
 
450
+ ### TUI runtime registry
451
+
452
+ TUI placement is ephemeral and is never written to `.ocmux.json`. Registrations
453
+ live in `$XDG_RUNTIME_DIR/agentp/ocmux-tuis.json` (falling back to a private
454
+ `/tmp/agentp-<uid>/` directory); `OCMUX_RUNTIME_DIR` overrides that location.
455
+ The directory is mode `0700`, registry files are mode `0600`, and updates use a
456
+ lock plus atomic rename.
457
+
458
+ A registration stores the tmux socket, pane ID, diagnostic wrapper PID, and a
459
+ random pane verification token. Matching ID/token options are also written to
460
+ the pane before it can be respawned. This prevents stale entries from targeting
461
+ an unrelated pane and lets a pane move between windows or tmux sessions on the
462
+ same socket without re-registering. Dead registrations are pruned when queried.
463
+
464
+ Closing OpenCode makes its foreground `ocmux tui` wrapper unregister itself if
465
+ it still owns the token. Replacing a dedicated/shared registration interrupts
466
+ the previous wrapper but preserves its pane. `--detach` only unregisters and
467
+ clears the pane markers; it does not close the pane or the OpenCode process.
468
+
469
+ Registry commands:
470
+
471
+ ```bash
472
+ ocmux tui # dedicated TUI for the current project
473
+ ocmux tui /path/to/project # dedicated TUI for another project
474
+ ocmux tui --shared # the single cross-project fallback TUI
475
+ ocmux tui --list # all live dedicated/shared registrations
476
+ ocmux tui --status # current project's dedicated slot
477
+ ocmux tui --status --shared # shared slot
478
+ ocmux tui --detach # unregister current project's slot
479
+ ocmux tui --detach --shared # unregister shared slot
480
+ ```
481
+
482
+ Routing precedence is:
483
+
484
+ 1. a live TUI dedicated to the selected project;
485
+ 2. the single live shared TUI;
486
+ 3. headless operation (the session still switches successfully).
487
+
488
+ Switching respawns the registered pane with `ocmux tui`, which reconnects
489
+ OpenCode to the selected server, directory, and session. Server-side work keeps
490
+ running, but client-local TUI state such as a draft prompt or scroll position is
491
+ lost during the refresh.
492
+
399
493
  ### State file
400
494
 
401
495
  `.ocmux.json` (in the project directory) stores `version`, `directory`,
@@ -408,8 +502,8 @@ gitignored — never commit it.
408
502
  2. Resolves the target from the nearest `.ocmux.json`: server URL, project
409
503
  directory, and the stored session id (falls back to the most recently viewed
410
504
  session in that directory, or creates one pinned to the directory).
411
- 3. Focuses the project's TUI window, prepends the session annotation (if any),
412
- and sends the prompt via the v2 session API (`POST /api/session/:id/prompt`,
505
+ 3. Prepends the session annotation (if any) and sends the prompt via the v2
506
+ session API (`POST /api/session/:id/prompt`,
413
507
  delivering to that session regardless of what the TUI shows).
414
508
  4. Attaches to the SSE stream first so no events are missed, and streams text
415
509
  until the session is quiescent after its terminal signal.
@@ -451,7 +545,7 @@ Non-text Telegram updates (photos, stickers, etc.) are silently ignored.
451
545
  | `/servers` | List all ocmux-served projects (▶ active, 🔌 disconnected, 💀 dead) |
452
546
  | `/server <name>` | Switch active server; matches by full path, basename, or substring |
453
547
  | `/server --force <name>` | Take over a server from another chat |
454
- | `/resurrect [path]` | Restart a crashed server from its `.ocmux.json`; accepts optional directory path |
548
+ | `/resurrect [path]` | Check the configured user-managed server and explain how to restart it externally |
455
549
  | `/sessions` | List sessions (numbered, newest first) |
456
550
  | `/session <name-or-number>` | Switch to a session by name or position |
457
551
  | `/session new [name]` | Create a new session |
package/bin/agentp CHANGED
@@ -21,7 +21,6 @@ const {
21
21
  getActiveSessions,
22
22
  } = require('../lib/opencode');
23
23
  const projectState = require('../lib/project-state');
24
- const ocmuxLib = require('../lib/ocmux');
25
24
 
26
25
  const TGAGENTP_PORT_FILE = '/tmp/tgagentp-port';
27
26
 
@@ -1151,7 +1150,6 @@ async function main() {
1151
1150
  let sessionId = null;
1152
1151
  let answer;
1153
1152
  if (broadcastIds) {
1154
- try { if (projectDir) ocmuxLib.focusWindowByDir(projectDir); } catch {}
1155
1153
  const bres = await runBroadcastSend(serverBase, broadcastIds, actualPromptText, {
1156
1154
  getActiveSessions,
1157
1155
  getSession,
@@ -1173,17 +1171,16 @@ async function main() {
1173
1171
  preferredSession,
1174
1172
  });
1175
1173
 
1176
- // v2: focus the target project's TUI window so the user sees the prompt
1177
- // stream, and prepend the session's annotation (if any) to the prompt.
1174
+ // Prepend the session's annotation (if any) to the prompt. TUI routing is
1175
+ // owned by explicit ocmux session switches, not by prompt submission.
1178
1176
  let prompt = actualPromptText;
1179
1177
  try {
1180
1178
  if (ctx && ctx.statefile) {
1181
1179
  const ann = projectState.readAnnotations(ctx.statefile)[sessionId];
1182
1180
  if (ann) prompt = ann + '\n\n' + actualPromptText;
1183
1181
  }
1184
- if (projectDir) ocmuxLib.focusWindowByDir(projectDir);
1185
1182
  } catch {
1186
- // tmux focus is best-effort; never fail the prompt because of it
1183
+ // Annotation lookup is best-effort.
1187
1184
  }
1188
1185
 
1189
1186
  try {