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.
@@ -1,652 +1,241 @@
1
- # agentp v2 — Project/Session Model Specification
1
+ # agentp 2.x — Project, Session, and TUI Specification
2
2
 
3
- Status: **Draft for review** · Target version: 1.16.0 · Supersedes the state/session
4
- model described in `docs/specification.md`.
3
+ Status: **Current**
5
4
 
6
- ---
5
+ ## 1. Runtime model
7
6
 
8
- ## 1. Motivation
7
+ OpenCode 2 uses a shared service/data store. Sessions belong to locations and
8
+ carry `location.directory`; a server URL does not identify a project.
9
9
 
10
- OpenCode v2 changed the runtime model in ways that invalidate the original
11
- per-project-server design:
10
+ The tools therefore use these identities:
12
11
 
13
- - A single OpenCode **data store** (`~/.local/share/opencode/opencode.db`) holds
14
- *all* sessions for *all* projects. Sessions carry `location.directory` +
15
- `projectID`.
16
- - Any server process can list/create/operate sessions in **any** location:
17
- `GET /api/session?directory=…`, `POST /api/session` with `location`,
18
- `POST /api/session/:id/move`.
19
- - `GET /api/session` **without** a `directory` filter returns sessions from
20
- **every** project known to the store (verified live: all three running
21
- servers returned the same 50 sessions; scoped queries returned 11 / 2 / 50).
12
+ - **Project:** canonical directory.
13
+ - **Prompt target:** OpenCode session ID in that directory.
14
+ - **Display:** an optional, explicitly registered tmux pane.
22
15
 
23
- Consequence: the server URL **no longer identifies a project**, so
24
- `agentp $(ocmux)` intermittently routes prompts to the wrong project's session
25
- (response renders in the wrong TUI window). The project directory is the correct
26
- routing key; the session ID is the correct target.
16
+ The OpenCode server is user-managed. `agentp` and `ocmux` health-check it but do
17
+ not start or stop it.
27
18
 
28
- The v2 model keeps things simple and predictable:
19
+ ## 2. Project state
29
20
 
30
- > **A project is a directory. A target is a session in that directory.
31
- > `agentp` reads both from the nearest `.ocmux.json`.**
21
+ The nearest `.ocmux.json`, found by walking upward from the working directory,
22
+ is the durable source of truth:
32
23
 
33
- ---
24
+ ```json
25
+ {
26
+ "version": 2,
27
+ "directory": "/home/user/project",
28
+ "session": "ses_abc",
29
+ "server": "http://localhost:4096",
30
+ "annotations": {},
31
+ "broadcast": ["ses_abc", "ses_def"]
32
+ }
33
+ ```
34
34
 
35
- ## 2. Design summary
35
+ `directory`, `session`, and `server` route prompts. `annotations` stores optional
36
+ per-session reminders. `broadcast` exists only with at least two selected main
37
+ sessions. Writes use a temporary file plus atomic rename.
36
38
 
37
- | Concern | v1 (current) | v2 (this spec) |
38
- |---|---|---|
39
- | Server | one per project | **user-started; tool only health-checks it** (see §11.1) |
40
- | tmux window | server pane + TUI pane | **TUI only** (pane 0) |
41
- | Project key | server URL / tmux window name | **directory** |
42
- | Target | newest session globally | **session ID stored in `.ocmux.json`** |
43
- | `ocmux` (no args) | switch to project server, print URL | interactive **session** picker for current project |
44
- | `ocmux switch` | interactive session picker | interactive **project** (TUI window) picker |
45
- | `agentp $(ocmux)` | required | **not needed** (kept as override only) |
46
- | `.ocmux.json` | `{url, logfile, window_index}` | `{version, directory, session, server?}` |
39
+ No tmux socket, pane ID, process ID, or TUI assignment belongs in project state.
40
+ Those values are ephemeral and machine-local.
47
41
 
48
- ### 2.1 Guiding principles
42
+ ## 3. TUI runtime registry
49
43
 
50
- 1. **`.ocmux.json` is the single source of truth** for a project's directory and
51
- selected session.
52
- 2. **One shared server per profile.** Profile = OpenCode binary + data dir, so a
53
- work install and a home/dev install can coexist on separate servers.
54
- 3. **`ocmux switch` is read-only** with respect to `.ocmux.json` — it is a
55
- visual/navigation tool for moving between project TUI windows.
56
- 4. **Session switching always keeps the TUI in sync** by relaunching it on the
57
- chosen session (`opencode --server <url> --session <id>`).
58
- 5. **`agentp` works without command substitution**; it resolves everything from
59
- the working directory upward.
44
+ `ocmux tui` registers its current `$TMUX_PANE`. The registry lives at:
60
45
 
61
- ---
46
+ ```text
47
+ $OCMUX_RUNTIME_DIR/ocmux-tuis.json (test/override)
48
+ $XDG_RUNTIME_DIR/agentp/ocmux-tuis.json (normal)
49
+ /tmp/agentp-<uid>/ocmux-tuis.json (fallback)
50
+ ```
62
51
 
63
- ## 3. `.ocmux.json` — new schema
52
+ The directory is mode `0700` and registry files are mode `0600` where the
53
+ platform permits it. Updates are lock-protected and atomically renamed.
64
54
 
65
- Placed at the **project root** (found by upward search, git-like).
55
+ Each instance records:
66
56
 
67
- ```json
68
- {
69
- "version": 2,
70
- "directory": "/home/user/projects/myapp",
71
- "session": "ses_1bd64adb1ffe4zekSiy9oRoJAN",
72
- "server": "http://127.0.0.1:4097"
73
- }
74
- ```
57
+ - opaque instance ID;
58
+ - random verification token;
59
+ - tmux socket and pane ID;
60
+ - dedicated/shared mode;
61
+ - last directory, server, and session;
62
+ - the directory and session currently displayed (so routing can skip a
63
+ respawn that would change nothing);
64
+ - foreground wrapper PID and current child PID (diagnostic only);
65
+ - registration/update timestamps.
75
66
 
76
- | Field | Required | Meaning |
77
- |---|---|---|
78
- | `version` | yes (written) | Schema version; absent/`1` ⇒ legacy file. |
79
- | `directory` | yes (written) | Absolute, realpath'd project root. Guard against file moves: if `realpath(dirname(file)) !== directory`, warn and repair. |
80
- | `session` | yes (written) | OpenCode session ID to target. May be absent until first `ocmux serve`/`agentp` run. |
81
- | `server` | no | Cached shared-server URL. **Not authoritative** — the global server state wins. Kept for self-containment/offline diagnostics. |
82
- | `annotations` | no | Map `sessionID → reminder text` (the picker's `R` key). Prepended by `agentp` to every prompt for that session. |
83
- | `broadcast` | no | Multi-session target (the picker's `Space` key): array of session IDs. When ≥2, `agentp` sends the prompt to all of them (waiting for idle). |
84
-
85
- **Legacy tolerance (read):** old files `{url, logfile, window_index}` must still
86
- parse. On read:
87
-
88
- - `directory` ← dirname of the file.
89
- - `server` ← legacy `url` (or the global state).
90
- - `session` ← resolved on demand (newest scoped to `directory`, preferring the
91
- session the TUI last viewed).
92
-
93
- **Atomic writes:** write `.<name>.tmp` then `rename()`; never partial-write.
94
- One writer at a time is the norm (ocmux); agentp may bootstrap-write only when it
95
- created the session.
96
-
97
- ### 3.1 Global server state — REMOVED
98
-
99
- Per §11.1, the server is user-managed and out of scope. There is **no** managed
100
- global server state file and **no** `ensureServer()`. Each project records the
101
- server URL it uses in its own `.ocmux.json.server` (or relies on the default /
102
- `OPENCODE_SERVER_URL`).
103
-
104
- ### 3.2 Server resolution precedence (check-only)
105
-
106
- 1. `--server` / positional URL argument (back-compat with `agentp $(ocmux)`).
107
- 2. `$OPENCODE_SERVER_URL` (optional convenience).
108
- 3. `.ocmux.json.server` (or legacy `url`).
109
- 4. `http://localhost:4096` (legacy default).
110
-
111
- The resolved server is **health-checked** (`GET /api/info`); on failure the tool
112
- exits 1 with a hint to start `opencode serve`. It is never started or managed.
113
-
114
- ### 3.3 Directory resolution (project)
115
-
116
- 1. `--directory <path>`.
117
- 2. Directory of the nearest `.ocmux.json`, searching upward from `cwd`.
118
- 3. `fs.realpathSync(cwd)`.
119
-
120
- ### 3.4 Session resolution (target)
121
-
122
- 1. `--session <id|name>` or `--new <name>` (explicit override; not persisted).
123
- 2. `.ocmux.json.session`, **if it still exists on the server** (validated).
124
- 3. Newest session **scoped to `directory`**, preferring max `time.viewed`,
125
- tie-broken by `time.updated`.
126
- 4. Create a new session in `directory` (`location`). **agentp never writes
127
- `.ocmux.json`** — session persistence belongs to `ocmux`; agentp only records
128
- the resolved session in the deferred ticket (which already carries it).
129
-
130
- ---
131
-
132
- ## 4. New behavior — `ocmux`
133
-
134
- ### 4.1 Command surface
135
-
136
- | Command | Behavior | Writes `.ocmux.json`? |
137
- |---|---|---|
138
- | `ocmux` | Ensure project window (server health-checked); **interactive session picker** for the current project; switch TUI to the chosen session. Silent on success. | **Yes** (`session`) |
139
- | `ocmux switch` | **Interactive project picker** across all project TUI windows; focus windows. Purely visual/navigation. | **No** |
140
- | `ocmux serve [dir]` | Create/open the project TUI window; create state file and pick/create a session. No picker. Health-checks the server. | **Yes** |
141
- | `ocmux session <id\|name>` | Non-interactive session switch for the current project (scripts/agentp-adjacent). | **Yes** |
142
- | `ocmux list [-l]` | List projects (windows) with their selected session and status. | No |
143
- | `ocmux model [ref]` | Switch the model of the **selected session** of the current project (no more URL-printing contract). | No |
144
- | `ocmux kill <dir>` | Close the project's TUI window **but keep `.ocmux.json`** (mark `status: "stopped"`). | updates |
145
- | `ocmux resurrect [dir]` | Recreate a project TUI window (kept as an alias for recovery). | maybe |
146
-
147
- There is **no `ocmux down`/`up`** — the server is user-managed (§11.1).
148
-
149
- Retained flags: `--git`, `--GIT`, `-l`, `--print-logs`, `--version`, `-h`.
150
- `serve` aliases: `new` (deprecated).
151
-
152
- ### 4.2 `ocmux` (no arguments) — session picker
153
-
154
- 1. Resolve project dir via upward `.ocmux.json` search (error with a clear hint
155
- if none — run `ocmux serve`).
156
- 2. Health-check the server recorded in `.ocmux.json` (complain if down).
157
- 3. Ensure the project TUI window exists; if it is not showing
158
- `state.session`, relaunch it (see §5).
159
- 4. If `stdin.isTTY`: open an alt-screen picker listing sessions
160
- `GET /api/session?directory=<dir>`, **most recently viewed first**:
161
- - rows: an **active spinner** (⠋…), title, last-view time (`HH:MM`, or
162
- `dd/mm/yyyy` when older than 24h; column dropped on narrow terminals),
163
- current `*`, reminder `◈`
164
- - a **centered, inverted heading** and a **scrollable viewport** (range in the
165
- heading) that re-renders on terminal resize
166
- - `Enter`/`Space` switches to the row but **stays open**; `n` creates (name
167
- input, blank = auto-title); `r` renames; `R` (Shift+r) sets a reminder;
168
- `d` deletes (y/N); `a` switches agent; `m` switches model; `p` opens the
169
- project switcher; `h` toggles help; `q` quits (only here — see below)
170
- - **Menu exit rule**: `q`/`ESC` only *close the current menu and return to the
171
- previous one* in every menu except the session picker itself; **`Ctrl+C`
172
- fully exits `ocmux` from any menu** (help, input/confirm prompts, pickers,
173
- broadcast).
174
- - **`/` incremental search** (session picker, model/agent pickers and project
175
- switcher): typing filters the list live (case-insensitive; whitespace
176
- tokens are ANDed), the inverted bottom line shows `Search: <pattern>▏`, and
177
- its right end shows `Enter: confirm · Esc: cancel`. `Enter` keeps the filter
178
- and returns to normal navigation, `ESC` clears it, `/` resumes editing it,
179
- arrows still move through the filtered view, and `Backspace` on an
180
- already-empty search also exits it.
181
- - **Broadcast (`Space`)**: only `Enter` switches. `Space` over a *different*
182
- session enters broadcast mode: `Space` toggles each session (selection
183
- persists to `.ocmux.json` `broadcast`), `Enter` keeps the selection and
184
- switches to the cursor session, `ESC`/`q` cancels and returns to the
185
- session list with the TUI back on the stored session. **Each newly selected
186
- session is opened in the TUI window** (so you can inspect what is going on
187
- in it before deciding); deselecting does not switch back — except
188
- **deselecting down to a single session ends broadcast mode by selecting
189
- that remaining session**.
190
- `h` opens broadcast-specific help, `d` deletes the cursor session with
191
- confirmation, and `m` applies a selected model to **all** broadcast
192
- sessions. The footer shows only `Broadcast to sessions: <names>`
193
- while selecting. `agentp` then sends the prompt to **all** selected
194
- sessions,
195
- waiting for each to become idle first; if the broadcast is cancelled before
196
- every session answers, the received responses are printed plus a
197
- detailed "not completed" list (session name/id, error, timestamp).
198
- Deferred broadcast tickets carry `sessionIds` (not a misleading single
199
- `sessionId`) and preserve the target list for the detached child even if
200
- `.ocmux.json` changes later.
201
- - an **inverted footer** with the key hints, plus info lines about the
202
- selected session (title, location, and a responsive grid of model, agent,
203
- status + time in status, tokens, cost, context limit, outcome).
204
- - only **main** sessions are listed — child/subagent sessions (`parentID`
205
- set, e.g. old `@explore`/`@general` runs) are hidden: OpenCode's TUI
206
- defaults those to a "Subagents" tab, so they are not offered as targets
207
- (`ocmux session <id>` / `agentp --session` can still reach one explicitly).
208
- - **new sessions inherit the model of the previously selected session**
209
- (falling back to the server default) — API-created v2 sessions have no
210
- model and won't run a prompt until one is set.
211
- 5. If **not** a TTY: focus the window and print the selected session ID on
212
- stdout (non-interactive).
213
-
214
- Output contract: prints the chosen session ID on stdout at the end (no longer a
215
- URL). `agentp` does not depend on this.
216
-
217
- ### 4.3 `ocmux switch` — project picker (read-only)
218
-
219
- - Lists every tmux window in the `Opencode` session that has a `.ocmux.json`
220
- (project windows), plus their selected session.
221
- - Highlights the tmux-active window (live `activeWindowIndex()`).
222
- - `Enter`/`Space` focuses a window and keeps the menu open; `/` searches
223
- (same incremental filter as §4.2); `h` toggles help; `q` closes the switcher
224
- and returns to the session picker; `Ctrl+C` fully exits.
225
- - **Must never write `.ocmux.json`.** The dead-TUI restart path may *read* the
226
- state file, but only to know which session to relaunch with.
227
-
228
- ### 4.4 Window layout (shared-server model)
67
+ The pane itself receives `@ocmux_tui_id` and `@ocmux_tui_token` tmux options.
68
+ Before a destructive pane operation, both must match the protected registry.
69
+ This prevents a stale registration from respawning an unrelated pane. Pane IDs
70
+ remain useful after `move-window`, `join-pane`, or `break-pane` within the same
71
+ tmux server; recording the socket disambiguates multiple tmux servers.
229
72
 
230
- ```
231
- tmux session: Opencode
232
- ├── window "__server__" (optional; server log view) ← excluded from switch/list
233
- ├── window /home/user/proj-a pane 0: TUI (opencode --server <url> --session <id>)
234
- └── window /home/user/proj-b pane 0: TUI (opencode --server <url> --session <id>)
235
- ```
73
+ There are two assignment slots:
236
74
 
237
- - Project windows contain **only the TUI** (pane 0). The old "pane 0 = server,
238
- pane 1+ = TUI" assumption is dropped (see §6.4 for the transition shim).
239
- - The server runs detached (or in the `__server__`/`opencode service`), logging to
240
- `agentp-server-<profileHash>.log`.
75
+ - one dedicated TUI per canonical project directory;
76
+ - exactly one shared TUI across all projects and server URLs.
241
77
 
242
- ---
78
+ There is no `--global` alias.
243
79
 
244
- ## 5. TUI session switching
80
+ ## 4. TUI routing
245
81
 
246
- OpenCode v2 exposes **no reachable HTTP endpoint to steer a TUI** (verified:
247
- `/tui/*` is not a v2 namespace; `tui.session.select` exists as a client event
248
- but has no public emitter). Therefore switching is done by **relaunching the TUI
249
- process**:
82
+ When `ocmux` selects or inspects a session, it resolves the display in order:
250
83
 
251
- ```
252
- tmux respawn-pane -k -t <tuiPane> "opencode --server '<url>' --session '<id>'"
253
- ```
84
+ 1. live TUI dedicated to the target directory;
85
+ 2. the one live shared TUI;
86
+ 3. no display (headless success).
254
87
 
255
- - If the pane is missing: `split-window`/`new-window` with `cwd = directory`,
256
- then zoom.
257
- - **Validate the session exists first** (`getSession`). `--session` *creates* the
258
- session if the ID is unknown, which we do not want for a stale state file.
259
- - While the previous session's execution keeps running server-side, the relaunch
260
- only detaches the *view*; note this in docs.
261
- - If OpenCode ever ships a TUI-steering endpoint, prefer publishing
262
- `tui.session.select` (directory-scoped) and keep the relaunch as fallback.
263
-
264
- ---
265
-
266
- ## 6. Changes to the codebase
267
-
268
- ### 6.1 `lib/opencode.js`
269
-
270
- - `listSessions(server, directory)` — already accepts `directory`; ensure **all**
271
- callers pass it.
272
- - `createSession(server, title, location)` — add `location` to the POST body
273
- (`POST /api/session` supports `location`).
274
- - `listenV2` — insert a `"\n\n"` separator between assistant text segments:
275
- on `session.text.started`, if `collected` is non-empty, append `"\n\n"` before
276
- the next delta. Fixes run-on per-step narration. (Optional `--final` mode to
277
- emit only the last segment.)
278
- - `selectSession` — keep the v2 no-op, with an explicit comment pointing at §5.
279
-
280
- ### 6.2 New state helpers (`lib/ocmux.js` or a new `lib/project-state.js`)
281
-
282
- - `findProjectStatefile(startDir)` — upward search.
283
- - `readProjectState(dirOrFile)` — parse + legacy normalization.
284
- - `writeProjectState(dir, patch)` — atomic merge write.
285
- - `resolveProject({ server, directory })` — resolve dir/session/server using
286
- §3.2–§3.4.
287
- - `checkServer(server)` — `GET /api/info`; throws a clear "start `opencode serve`"
288
- error when unreachable. No lifecycle management.
289
- - `listProjects()` — windows with `.ocmux.json`, with selected session.
290
- - `relaunchTui(windowIndex, dir, server, session)` — the §5 command; tested via
291
- a mocked `_tmux`.
292
- - `activateProject(dir)` — focus + zoom; restart dead TUI using the stored
293
- session (read-only).
294
-
295
- ### 6.3 `bin/ocmux`
296
-
297
- - Restructure `main()` dispatch:
298
- - `ocmux` (default) → session picker (§4.2).
299
- - `switch` → project picker (§4.3).
300
- - `serve`/`new` → `cmdNew` rewritten for shared server + TUI-only window.
301
- - add `session`, `down`; repurpose `resurrect`.
302
- - `list` shows projects + selected sessions.
303
- - `model` targets the selected session (drop the URL-printing contract).
304
- - Remove/retire per-project server startup from `startServer` (server pane,
305
- `tee` log polling) in favor of `ensureServer()`.
306
- - Keep `--git`/`--GIT` and directory resolution semantics.
307
-
308
- ### 6.4 `lib/ocmux.js` transition shim
309
-
310
- - `tuiPaneId(windowIndex)` currently returns the first non-zero pane; in the new
311
- layout the TUI is pane 0. During migration, support **both**:
312
- - layout v2: TUI is pane 0 (or the only pane);
313
- - layout v1: pane 0 is server, TUI is pane 1+ (tolerate until `ocmux migrate`).
314
- - `windowByDir`/`windowNameByIndex`/`activeWindowIndex` unchanged.
315
-
316
- ### 6.5 `bin/agentp`
317
-
318
- - Replace `resolveTargetSession` boilerplate with `resolveProject` + session
319
- precedence (§3.4). `resolveTargetSession(server, dir, { sessionName, newSession, preferredSession })`.
320
- - Pass `directory` to **every** `listSessions` call (`resolveTargetSession`,
321
- `--getLast`).
322
- - **Never writes `.ocmux.json`** (ocmux owns session persistence).
323
- - Keep positional URL arg as override; still accept `agentp $(ocmux)` (the URL is
324
- now just a server override; directory/session come from the state file).
325
- - Deferred tickets: unchanged shape (`server`, `sessionId`); store the resolved
326
- session ID.
327
- - Output-separator fix is inherited from `lib/opencode.js`.
328
-
329
- ### 6.6 `bin/tgagentp` (follow-up workstream)
330
-
331
- - Connection entries already store `dir`; add `session`. Resolve server from the
332
- global state + `.ocmux.json`; target `session` for messages.
333
- - `/servers switch` = project switch (windows); add a session switch command
334
- (e.g. `/session <n>`) mirroring `ocmux`.
335
- - Scope all `listSessions` by `projectDir` (lines ~602/767 currently unscoped).
336
- - Larger surface — can land after `ocmux`/`agentp`.
337
-
338
- ### 6.7 Docs
339
-
340
- - Update `README.md` (usage, `agentp` no longer needs `$(ocmux)`; `ocmux`
341
- session/project pickers).
342
- - Update `docs/specification.md` → point to this file; reconcile state schema.
343
- - `CHANGELOG.md` full summary. **Do not bump version without approval.**
344
-
345
- ---
346
-
347
- ## 7. Edge cases & risks (analysis)
348
-
349
- 1. **In-TUI session drift (highest risk).** The user can create/switch sessions
350
- *inside* the TUI (`session.new`, tabs). `.ocmux.json.session` then disagrees
351
- with what is displayed, and `agentp` would target the stale session.
352
- - Mitigation A (recommended): `ocmux` is the canonical switcher; in-TUI
353
- navigation is treated as inspection. Provide `ocmux sync`/`--adopt` to
354
- adopt the TUI's current session (newest `time.viewed` in the directory).
355
- - Mitigation B: `agentp` warns when `state.session !== newest-viewed` in the
356
- same directory.
357
- 2. **`--session` creates if missing.** A stale/deleted stored ID would be
358
- silently recreated. Always validate with `getSession` before relaunch/send.
359
- 3. **Session moved/deleted.** `POST /api/session/:id/move` changes
360
- `location.directory`; `delete` removes it. Detect 404/mismatch → fall back to
361
- §3.4.3 and repair the file.
362
- 4. **Relaunch is disruptive.** Loses scrollback and in-TUI state; if the same
363
- session is running a prompt, the view detaches (execution continues). Document
364
- it; skip relaunch when the TUI is already on the target session.
365
- 5. **`ocmux switch` writes.** Ensure every code path it can reach (activate,
366
- restart dead TUI, zoom) is read-only for `.ocmux.json`. Add a test asserting
367
- no write.
368
- 6. **Persistence races.** Atomic writes + read-once at start. `agentp` must not
369
- rewrite `session` while `ocmux` is switching.
370
- 7. **No `.ocmux.json`.** `ocmux` offers `serve`; `agentp` prints a clear error and
371
- exits 1 (as today). Define the exact message.
372
- 8. **Empty project (no sessions).** Picker offers `n` (create). `agentp`
373
- bootstraps a session in `directory` and (optionally) persists it.
374
- 9. **Nested projects / parent state.** Upward search precedence is nearest file.
375
- `--git`/`--GIT` unchanged. Validate `directory` matches realpath; repair if
376
- the file moved.
377
- 10. **Multiple profiles (work vs home).** One server per profile keyed by data
378
- dir/binary. Global state namespaced; never share across profiles.
379
- 11. **Isolation.** A shared server lets any client reach any project. The store is
380
- already shared, so this is not a regression; document it. True isolation
381
- requires a separate `XDG_DATA_HOME`.
382
- 12. **Server restart / port change.** Global state is authoritative; per-project
383
- cached `server` may be stale. Health-check before use; refresh on success.
384
- 13. **`ocmux model` contract change.** Previously printed the URL for
385
- `agentp $(ocmux model …)`. With `agentp` self-resolving, it should instead
386
- switch the selected session's model and print a human confirmation.
387
- 14. **`resurrect`/`kill` semantics.** `kill` removes a project window + state;
388
- with a shared server this is lighter. `resurrect` recreates a window. Define
389
- whether killing keeps session memory (proposal: remove state, matching today).
390
- 15. **Window layout migration.** Old windows have a server pane; new ones do not.
391
- The transition shim (§6.4) handles both until `ocmux migrate` runs.
392
- 16. **Run-on output (separate but included).** `listenV2` concatenates text
393
- deltas across steps with no separator — fixes §6.1.
394
- 17. **Built-in background service.** `opencode service start|stop|status` exists
395
- and may own the shared server. Option: delegate lifecycle to it and have
396
- `ocmux` only manage TUIs. Recommend evaluating after Phase 1; keep
397
- `ensureServer()` abstract so the backend can change.
398
- 18. **`opencode --session` semantics.** Confirmed present: "Session ID to
399
- continue, or to create if it does not exist" — hence the validate-first rule.
400
-
401
- ### 7.1 Things easy to miss
402
-
403
- - `agentp` must still accept the legacy URL positional for back-compat, but must
404
- **not** let it determine the project (directory comes from state/cwd).
405
- - `tgagentp` is a first-class consumer of `.ocmux.json`; keep the schema in one
406
- place so all three tools agree.
407
- - `.ocmux.json` is gitignored — never commit it; `ocmux migrate` must not create
408
- tracked files.
409
- - The tmux `__server__` window must be excluded from `switch`/`list` counts.
410
-
411
- ---
412
-
413
- ## 8. Implementation plan — workstreams ("subagents")
414
-
415
- Each workstream is a self-contained unit suitable for delegation to a
416
- `general` subagent (implementation) with an `explore` subagent for audits.
417
- Workstreams A–B are prerequisites; C and D can run in parallel after A; E–F
418
- follow. G runs last.
419
-
420
- ### Subagent A — Core state & resolution (`core-state`)
421
-
422
- - **Goal:** Centralize `.ocmux.json` read/write/migration and server/dir/session
423
- resolution; make it the single contract shared by all CLIs.
424
- - **Deliverables:**
425
- - New `lib/project-state.js` (or additions to `lib/ocmux.js`):
426
- `findProjectStatefile`, `readProjectState`, `writeProjectState`,
427
- `resolveProject`, `serverStatePath/readServerState/writeServerState`.
428
- - Legacy normalization + atomic writes.
429
- - Unit tests: legacy parse, moved-file repair, precedence table, atomic write.
430
- - **Depends on:** nothing.
431
- - **Checkpoints:** all precedence cases covered; no partial writes under a
432
- simulated crash; legacy files never throw.
433
-
434
- ### Subagent B — ~~Server lifecycle~~ (removed)
435
-
436
- Per §11.1 the server is user-managed. Only a read-only `checkServer()` remains
437
- (folded into workstream A). No lifecycle workstream.
438
-
439
- ### Subagent C — `ocmux` CLI rework (`ocmux-cli`)
440
-
441
- - **Goal:** New command surface (§4): session picker, project picker, serve,
442
- session, kill, down, list, model; TUI relaunch (§5).
443
- - **Deliverables:** `bin/ocmux` dispatch + `lib/ocmux.js` `relaunchTui`,
444
- `listProjects`, `activateProject`, transition shim (§6.4).
445
- - **Depends on:** A, B.
446
- - **Checkpoints:** `ocmux` picker updates `session` + relaunches TUI; `ocmux
447
- switch` writes nothing (test); TUI-only layout works; v1 layout tolerated.
448
-
449
- ### Subagent D — `agentp` integration (`agentp-integration`)
450
-
451
- - **Goal:** Self-resolving project/session; scoped listing; output fix.
452
- - **Deliverables:** `resolveTargetSession(server, dir, opts)`; directory passed
453
- everywhere; bootstrap persistence; `--getLast` scoping; text-segment separator;
454
- legacy URL arg preserved.
455
- - **Depends on:** A (shared resolution).
456
- - **Checkpoints:** with two projects and a global store, prompts hit the stored
457
- session of the correct directory; `--session`/`--new` override without
458
- persisting; run-on output fixed.
459
-
460
- ### Subagent E — `tgagentp` integration (`tgagentp-integration`)
461
-
462
- - **Goal:** Target the same project/session; scope listings.
463
- - **Deliverables:** connection `session` field; session switch command; scoped
464
- `listSessions`; server resolution via A/B.
465
- - **Depends on:** A, B, D (patterns).
466
- - **Checkpoints:** Telegram message lands in the stored session; `/servers
467
- switch` maps to project windows; no unscoped listing remains.
468
-
469
- ### Subagent F — Tests, docs, changelog (`tests-docs`)
470
-
471
- - **Goal:** Regression + migration coverage; user-facing docs.
472
- - **Deliverables:** `tests/project-state.test.js`; updates to
473
- `tests/agentp.test.js`, `tests/ocmux.test.js`; `README.md`; align
474
- `docs/specification.md`; `CHANGELOG.md` entry (no version bump).
475
- - **Depends on:** A–E (can start on A early).
476
- - **Checkpoints:** full suite green; docs show `agentp` without `$(ocmux)`; state
477
- schema documented in one place.
478
-
479
- ### Subagent G — Integration verification (`integration-verify`)
480
-
481
- - **Goal:** End-to-end acceptance and a smooth transition.
482
- - **Deliverables:** a manual/automated checklist run against a live OpenCode v2
483
- with ≥2 projects:
484
- 1. `ocmux serve` for two dirs creates two TUI windows on one shared server.
485
- 2. `ocmux` in project A picks a session; `.ocmux.json.session` updates; TUI
486
- relaunches on it.
487
- 3. `ocmux switch` focuses B without touching either state file.
488
- 4. `agentp --qa <<< "..."` from A targets A's session; from B targets B's;
489
- responses appear in the correct window.
490
- 5. Switch session inside the TUI, then verify `ocmux sync`/warning behavior.
491
- 6. `--defer` ticket round-trip targets the same session.
492
- 7. Legacy file migration; server restart/port change; `ocmux down`.
493
- - **Depends on:** A–F.
494
- - **Checkpoints:** checklist fully green; no wrong-window routing; rollback
495
- documented.
496
-
497
- ### 8.1 Suggested execution order
88
+ A stale/dead dedicated registration is ignored, so routing falls back to the
89
+ shared registration. A stale shared registration yields headless operation.
90
+ Display failure is a warning and never rolls back the durable session choice.
498
91
 
92
+ The shared TUI is deliberately not server-scoped. When a selected project uses
93
+ a different server, the same pane reconnects to that server.
94
+
95
+ ## 5. Foreground TUI wrapper
96
+
97
+ `ocmux tui [--shared] [directory]`:
98
+
99
+ 1. Requires `$TMUX` and `$TMUX_PANE`.
100
+ 2. Resolves `.ocmux.json`, server, directory, and session. A shared TUI may run
101
+ without a state file, in which case it uses the current directory and the
102
+ default server (`$OCMUX_SERVER` or `http://localhost:4096`).
103
+ 3. Health-checks the server and validates the session, creating one with a
104
+ model when necessary.
105
+ 4. Marks and registers the current pane.
106
+ 5. Stops the displaced wrapper for the same dedicated/shared slot, preserving
107
+ its pane.
108
+ 6. Spawns `opencode` as a foreground child with inherited stdio:
109
+
110
+ ```text
111
+ opencode --server <url> --session <id> <directory>
112
+ ```
113
+
114
+ 7. Forwards `SIGHUP`, `SIGTERM`, and `SIGINT` to the child.
115
+ 8. On child exit, unregisters only when instance ID and token still match.
116
+
117
+ Token-conditional cleanup prevents an old wrapper from deleting a replacement's
118
+ new registration.
119
+
120
+ ## 6. Switching a registered TUI
121
+
122
+ OpenCode exposes no HTTP operation for steering one specific TUI client. A
123
+ registered pane is therefore switched with:
124
+
125
+ ```text
126
+ tmux -S <socket> respawn-pane -k -t <pane> -c <directory> \
127
+ "<ocmux> tui [--shared] --server <url> --session-id <id> -- <directory>"
499
128
  ```
500
- A ──▶ C ──┐
501
- └──▶ D ──┴──▶ F ──▶ G
502
- E ──┘ (deferred)
503
- ```
504
129
 
505
- Parallelization: C and D after A; E deferred; F continuously; G last.
506
-
507
- ---
508
-
509
- ## 9. Migration & rollout
510
-
511
- 1. **Read path first:** ship legacy-tolerant readers (A) before any writer
512
- changes. Old and new files both work.
513
- 2. **Migration command:** `ocmux migrate [--dry-run]`:
514
- - health-check the server;
515
- - for each project window: rewrite `.ocmux.json` to v2 (directory = realpath,
516
- session = current/newest scoped, server = the server it already used);
517
- - optionally retire per-project server panes.
518
- 3. **Compatibility window:** keep the v1 layout shim (§6.4) until all windows are
519
- migrated.
520
- 4. **Rollback:** because `.ocmux.json` and code are versioned and the store is
521
- untouched, reverting the binary restores v1 behavior; migration can be
522
- re-run. Keep a `.ocmux.json.bak` during migrate.
523
- 5. **Release:** update CHANGELOG; version bump **only on maintainer approval**
524
- (per `AGENTS.md`).
525
-
526
- ---
527
-
528
- ## 10. Acceptance criteria (definition of done)
529
-
530
- - [ ] `agentp` resolves server/directory/session from the nearest `.ocmux.json`
531
- and works without `$(ocmux)`.
532
- - [ ] No code path calls `listSessions` without a `directory`.
533
- - [ ] `createSession` sets `location`.
534
- - [ ] `ocmux` (no args) updates `session` and switches the TUI to it.
535
- - [ ] `ocmux switch` never writes `.ocmux.json` (tested).
536
- - [ ] A session deleted/moved is detected and repaired; no silent recreation.
537
- - [ ] Run-on assistant text is separated into paragraphs.
538
- - [ ] Full test suite green; migration and legacy reads covered.
539
- - [ ] `README.md`, `docs/specification.md`, `CHANGELOG.md` updated.
540
-
541
- ---
542
-
543
- ## 11. Decisions (resolved 2026-10-07)
544
-
545
- 0. **OpenCode v1 is dropped.** `lib/opencode.js` is v2-only; the legacy
546
- `/tui/*`, legacy SSE listeners and legacy endpoint paths were removed.
547
- 1. **Server is OUT OF SCOPE.** ocmux/agentp do **not** start, stop, restart, or
548
- supervise the server. The user starts `opencode serve` manually. The tools
549
- only **check** that the configured server is reachable and **complain**
550
- (exit 1 with a clear hint) otherwise. There is no `ensureServer()`, no
551
- `ocmux up`/`down`, and no managed global server state.
552
- - Note: a server's launch directory is only its *default location*; it can
553
- serve any location via `directory`/`location` parameters.
554
- 2. **No in-TUI drift handling.** The TUI is used **only for viewing** (reasoning
555
- + colored markdown). Sessions are never switched inside the TUI. `.ocmux.json`
556
- is authoritative; no `ocmux sync`/adoption logic.
557
- 3. **`kill` keeps `.ocmux.json`.** Closing a project removes the tmux window/TUI
558
- but preserves the state file (session memory). `kill` may mark it
559
- `"status": "stopped"`; `list`/`switch` derive/defunct status from tmux window
560
- existence. There is no server to kill — only the TUI window.
561
- 4. **`ocmux` (no args) prints nothing** on success; it is the interactive session
562
- switcher. Any diagnostics go to stderr. The `switch` subcommand was removed;
563
- **`p`** in the picker opens the project switcher.
564
- 4b. **Session picker keys:** `Enter` switch (stays open) · `n` create · `r`
565
- rename (readline-style caret editing) · `R` reminder (annotation) · `d`
566
- delete · `a` agents · `m` model · `p` projects · `h` help · `q` quit. The
567
- `model` subcommand was removed (use `m`), like `switch` (use `p`).
568
- 5. **tgagentp is deferred** to a follow-up release (workstream E).
569
- 6. **Server URL source:** per-project `.ocmux.json.server`, overridable by
570
- `--server`/`OPENCODE_SERVER_URL`; default `http://localhost:4096`. Recorded by
571
- `ocmux serve` and never managed thereafter.
572
-
573
- ---
574
-
575
- ## 12. Appendix — verified v2 facts (2026-10-06)
576
-
577
- - OpenCode v2.0.21; API under `/api`; Basic auth via `OPENCODE_SERVER_PASSWORD`.
578
- - Store: `~/.local/share/opencode/opencode.db`.
579
- - `GET /api/session?directory=<realpath>` is exact-match (subdir/trailing slash ⇒
580
- empty). Cross-location access works from any server.
581
- - `POST /api/session` payload supports `location`.
582
- - `GET /api/session/active` lists sessions with running executions.
583
- - `session.viewed` / `time.viewed` track the TUI's last-viewed session.
584
- - No reachable HTTP TUI-steering endpoint (`/tui/*` ⇒ 405/catch-all);
585
- `tui.session.select` is a directory-scoped client event with no public emitter.
586
- - `opencode --session/-s <id>` opens (or creates) a session in the TUI.
587
- - `opencode service` manages a built-in background server.
588
-
589
- ---
590
-
591
- ## 13. Future improvements
592
-
593
- ### 13.1 Per-session annotations (`a` key in the session picker)
594
-
595
- **Status: IMPLEMENTED (2026-10-07).**
596
-
597
- The `R` (Shift+r) key in the `ocmux` session picker attaches a short
598
- **reminder** to the selected session (e.g. *"Remember to work in the worktree
599
- foobar"*). Annotated sessions are marked with `◈`. `agentp` prepends the
600
- reminder (plus a blank line) to **every prompt** sent to that session.
601
-
602
- Persistence: an **`annotations` map (sessionID → text) inside `.ocmux.json`**,
603
- so a project keeps all its state in one file; empty text removes the entry.
604
- Written atomically (tmp + rename). A legacy `annotations.json` sidecar is read
605
- for back-compat and folded into the state file on the next write.
606
-
607
- Integration points (implemented):
608
- - `lib/project-state.js`: `readAnnotations(dir)`, `writeAnnotation(dir, id, text)`.
609
- - `bin/ocmux` session picker: `a` key → input mode (reuses the rename input
610
- with caret editing, ESC cancels); documented in the `h` help menu.
611
- - `bin/agentp`: after session resolution, prepends `annotation + "\n\n"` to the
612
- prompt before `sendToSession`.
613
-
614
- Known limitation: follow-ups queued via a deferred **ticket** do not re-inject
615
- the annotation (the ticket carries only `server`/`sessionId`). If needed later,
616
- the annotation can be looked up from the ticket's session on the server.
617
-
618
- ### 13.2 Completion hardening (annotated)
619
-
620
- `listenV2` resolves after a **silent** grace window once a terminal signal
621
- (`session.execution.succeeded|failed`) arrives; ANY stream activity (including
622
- child-session/sub-agent events) resets it, and `sendToSession` additionally
623
- verifies the session's idle marker is newer than the send. The default window is
624
- **15 s** (tunable via `AGENTP_COMPLETION_GRACE_MS`). A sub-agent (or tool) that
625
- goes completely silent for longer than the window can still truncate an answer.
626
- Candidates for the next pass:
627
- - wait on `GET /api/experimental/session/:id/wait` ("Wait for a session agent
628
- loop to become idle") after the terminal signal;
629
- - explicit child-session tracking (`parentID`) so sub-agent runs keep the parent
630
- "working" regardless of event silence.
631
-
632
- ### 13.3 Deferred-mode long-prompt handling (annotated, planned)
633
-
634
- Problem: a long prompt with a **silent** gap longer than the completion window
635
- (default 15s, `AGENTP_COMPLETION_GRACE_MS`) can still return a truncated answer.
636
- Raising the window reduced this but did not eliminate it.
637
-
638
- Proposed approach (does not change the `--defer 0` re-send semantics that
639
- follow-up queuing relies on):
640
-
641
- 1. In `--defer`/child mode, **do not cap** the "verify idle" retries — keep
642
- extending while the session is genuinely busy (bounded only by a larger
643
- safety timeout).
644
- 2. When the listener *does* give up (verify cap or safety timeout), write an
645
- `<output>.incomplete` marker next to the deferred result.
646
- 3. On retrieval, if the marker exists, print the captured output followed by
647
- `⚠️ may be incomplete — re-send the ticket for the full answer`, then clear
648
- the marker.
649
-
650
- Longer term, prefer an authoritative completion signal
651
- (`GET /api/experimental/session/:id/wait` and/or child-session tracking via
652
- `parentID`) over any timed quiescence.
130
+ The new `ocmux tui` process re-registers the pane and hosts the new OpenCode
131
+ child. Server-side session execution continues while the old client disconnects.
132
+ Client-local state—draft prompt, scroll position, dialogs, and tabs—is lost.
133
+
134
+ Routing is idempotent: the registry records the directory and session the pane
135
+ currently displays, so a switch to the same project/session is skipped without
136
+ respawning.
137
+
138
+ Native TUI navigation is temporary inspection and is not observable through
139
+ the OpenCode HTTP API. The next ocmux selection restores the canonical target.
140
+
141
+ ## 7. Commands
142
+
143
+ ### `ocmux serve [dir]`
144
+
145
+ Initializes `.ocmux.json`, selecting the newest main session in the directory
146
+ or creating one with a model. It creates no tmux session, window, or pane.
147
+ `--force --server <url>` repoints existing state and refreshes the routed TUI.
148
+
149
+ ### `ocmux tui [--shared] [--server <url>] [dir]`
150
+
151
+ Registers and hosts a dedicated or shared TUI as described above. `--shared`
152
+ is valid only for this command. `--server` overrides the project's recorded
153
+ server for the launched wrapper; when `--shared` runs without a state file it
154
+ falls back to the default server. Management modes are:
155
+
156
+ - `ocmux tui --list` — prune stale entries and print every live registration;
157
+ - `ocmux tui [dir] --status` — inspect the dedicated slot for that project;
158
+ - `ocmux tui --shared --status` — inspect the shared slot;
159
+ - `ocmux tui [dir] --detach` — unregister the dedicated slot;
160
+ - `ocmux tui --shared --detach` — unregister the shared slot.
161
+
162
+ `--detach` clears the pane's verification options but does not stop OpenCode or
163
+ destroy the pane. Management modes do not require running inside tmux.
164
+
165
+ ### `ocmux`
166
+
167
+ Opens the current project's session picker. Picking writes that project's
168
+ session and refreshes its routed TUI. Merely opening the picker also routes the
169
+ applicable TUI to the project's stored session (skipped when it already shows
170
+ it), so starting `ocmux` in a project switches the display even without picking
171
+ a row. When the recorded server is unreachable but the default server answers,
172
+ `ocmux` offers on a TTY to repoint the project to the default and rewrites
173
+ `server`. Session creation, rename/delete,
174
+ annotations, agent/model choice, broadcast selection, search, and project
175
+ inspection operate through OpenCode's API. Deleting the session currently
176
+ selected adopts the session under the cursor as the new current (state write,
177
+ TUI refresh, and list highlight); deleting any other session leaves the current
178
+ selection unchanged. In broadcast mode `d` deletes just the cursor session
179
+ while `D` (Shift+d) deletes every selected session at once — after confirming,
180
+ broadcast mode ends and the row under the cursor becomes the new current.
181
+ Interactive prompts (create/rename/reminder/delete/delete-all) take over the
182
+ status bar and flip its background from brown to light yellow — the same color
183
+ as the session-list pointer — so an active question is immediately visible.
184
+ The broadcast info line (`Broadcast to sessions: …`) never wraps: it holds up
185
+ to 480 characters of names (or the terminal width, whichever is smaller), and
186
+ an oversized selection is truncated from the beginning with a leading `...`.
187
+
188
+ ### `ocmux session <id|title> [dir]`
189
+
190
+ Non-interactively writes the selected session and refreshes its routed TUI.
191
+
192
+ ### Project switcher (`p`)
193
+
194
+ Configured projects are discovered from `/api/project` plus session locations;
195
+ only directories containing `.ocmux.json` are included. This captures worktrees
196
+ that are locations but not separate OpenCode project records.
197
+
198
+ By default the switcher only inspects sessions through the routed TUI and never
199
+ writes another project's state. `--all-projects` lets the outer session picker
200
+ move to another project and subsequently update that project's state.
201
+
202
+ ### `ocmux list [-l]`
203
+
204
+ Lists configured projects, selected sessions, and display status:
205
+
206
+ - `project` — a live dedicated TUI wins;
207
+ - `shared` — the live shared fallback applies;
208
+ - `headless` — no live registration.
209
+
210
+ `-l` also prints the server URL.
211
+
212
+ The old managed-window `kill` and `resurrect` commands do not exist.
213
+
214
+ ## 8. `agentp`
215
+
216
+ `agentp` resolves project/session/server from explicit options and the nearest
217
+ state file, then sends directly to the session API. It does not focus, move, or
218
+ refresh a TUI; TUI routing is an explicit consequence of ocmux session
219
+ selection, not prompt submission.
220
+
221
+ ## 9. Failure behavior
222
+
223
+ - Missing/unreachable server: command fails before registration or switching.
224
+ - Missing TUI: session selection succeeds headlessly.
225
+ - Dead pane or mismatched token: registration is pruned and fallback routing is
226
+ attempted on the next resolution.
227
+ - Failed `respawn-pane`: selected state remains valid and a warning is printed.
228
+ - Manual OpenCode exit: foreground wrapper removes its own registration.
229
+ - Replaced wrapper exits late: token check prevents it removing the new owner.
230
+ - tmux restart/reboot: pane checks invalidate and prune old runtime entries.
231
+ - Multiple tmux servers: recorded `-S <socket>` targets the correct one.
232
+
233
+ ## 10. Security boundaries
234
+
235
+ - `.ocmux.json` never identifies a process or pane to terminate.
236
+ - Destructive pane operations require matching registry and pane tokens.
237
+ - Commands are built from validated server/session/directory values with shell
238
+ quoting; no command string is read from project state.
239
+ - Runtime registry and lock files are private to the user.
240
+ - Wrapper/child PIDs are diagnostic only and are never used as durable identity
241
+ or as the target of a destructive operation.