agentp 1.13.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.
@@ -0,0 +1,611 @@
1
+ # agentp v2 — Project/Session Model Specification
2
+
3
+ Status: **Draft for review** · Target version: 1.16.0 · Supersedes the state/session
4
+ model described in `docs/specification.md`.
5
+
6
+ ---
7
+
8
+ ## 1. Motivation
9
+
10
+ OpenCode v2 changed the runtime model in ways that invalidate the original
11
+ per-project-server design:
12
+
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).
22
+
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.
27
+
28
+ The v2 model keeps things simple and predictable:
29
+
30
+ > **A project is a directory. A target is a session in that directory.
31
+ > `agentp` reads both from the nearest `.ocmux.json`.**
32
+
33
+ ---
34
+
35
+ ## 2. Design summary
36
+
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?}` |
47
+
48
+ ### 2.1 Guiding principles
49
+
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.
60
+
61
+ ---
62
+
63
+ ## 3. `.ocmux.json` — new schema
64
+
65
+ Placed at the **project root** (found by upward search, git-like).
66
+
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
+ ```
75
+
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
+
84
+ **Legacy tolerance (read):** old files `{url, logfile, window_index}` must still
85
+ parse. On read:
86
+
87
+ - `directory` ← dirname of the file.
88
+ - `server` ← legacy `url` (or the global state).
89
+ - `session` ← resolved on demand (newest scoped to `directory`, preferring the
90
+ session the TUI last viewed).
91
+
92
+ **Atomic writes:** write `.<name>.tmp` then `rename()`; never partial-write.
93
+ One writer at a time is the norm (ocmux); agentp may bootstrap-write only when it
94
+ created the session.
95
+
96
+ ### 3.1 Global server state — REMOVED
97
+
98
+ Per §11.1, the server is user-managed and out of scope. There is **no** managed
99
+ global server state file and **no** `ensureServer()`. Each project records the
100
+ server URL it uses in its own `.ocmux.json.server` (or relies on the default /
101
+ `OPENCODE_SERVER_URL`).
102
+
103
+ ### 3.2 Server resolution precedence (check-only)
104
+
105
+ 1. `--server` / positional URL argument (back-compat with `agentp $(ocmux)`).
106
+ 2. `$OPENCODE_SERVER_URL` (optional convenience).
107
+ 3. `.ocmux.json.server` (or legacy `url`).
108
+ 4. `http://localhost:4096` (legacy default).
109
+
110
+ The resolved server is **health-checked** (`GET /api/info`); on failure the tool
111
+ exits 1 with a hint to start `opencode serve`. It is never started or managed.
112
+
113
+ ### 3.3 Directory resolution (project)
114
+
115
+ 1. `--directory <path>`.
116
+ 2. Directory of the nearest `.ocmux.json`, searching upward from `cwd`.
117
+ 3. `fs.realpathSync(cwd)`.
118
+
119
+ ### 3.4 Session resolution (target)
120
+
121
+ 1. `--session <id|name>` or `--new <name>` (explicit override; not persisted).
122
+ 2. `.ocmux.json.session`, **if it still exists on the server** (validated).
123
+ 3. Newest session **scoped to `directory`**, preferring max `time.viewed`,
124
+ tie-broken by `time.updated`.
125
+ 4. Create a new session in `directory` (`location`). **agentp never writes
126
+ `.ocmux.json`** — session persistence belongs to `ocmux`; agentp only records
127
+ the resolved session in the deferred ticket (which already carries it).
128
+
129
+ ---
130
+
131
+ ## 4. New behavior — `ocmux`
132
+
133
+ ### 4.1 Command surface
134
+
135
+ | Command | Behavior | Writes `.ocmux.json`? |
136
+ |---|---|---|
137
+ | `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`) |
138
+ | `ocmux switch` | **Interactive project picker** across all project TUI windows; focus windows. Purely visual/navigation. | **No** |
139
+ | `ocmux serve [dir]` | Create/open the project TUI window; create state file and pick/create a session. No picker. Health-checks the server. | **Yes** |
140
+ | `ocmux session <id\|name>` | Non-interactive session switch for the current project (scripts/agentp-adjacent). | **Yes** |
141
+ | `ocmux list [-l]` | List projects (windows) with their selected session and status. | No |
142
+ | `ocmux model [ref]` | Switch the model of the **selected session** of the current project (no more URL-printing contract). | No |
143
+ | `ocmux kill <dir>` | Close the project's TUI window **but keep `.ocmux.json`** (mark `status: "stopped"`). | updates |
144
+ | `ocmux resurrect [dir]` | Recreate a project TUI window (kept as an alias for recovery). | maybe |
145
+
146
+ There is **no `ocmux down`/`up`** — the server is user-managed (§11.1).
147
+
148
+ Retained flags: `--git`, `--GIT`, `-l`, `--print-logs`, `--version`, `-h`.
149
+ `serve` aliases: `new` (deprecated).
150
+
151
+ ### 4.2 `ocmux` (no arguments) — session picker
152
+
153
+ 1. Resolve project dir via upward `.ocmux.json` search (error with a clear hint
154
+ if none — run `ocmux serve`).
155
+ 2. Health-check the server recorded in `.ocmux.json` (complain if down).
156
+ 3. Ensure the project TUI window exists; if it is not showing
157
+ `state.session`, relaunch it (see §5).
158
+ 4. If `stdin.isTTY`: open an alt-screen picker listing sessions
159
+ `GET /api/session?directory=<dir>`, **most recently viewed first**:
160
+ - rows: an **active spinner** (⠋…), title, last-view time (`HH:MM`, or
161
+ `dd/mm/yyyy` when older than 24h; column dropped on narrow terminals),
162
+ current `*`, reminder `◈`
163
+ - a **centered, inverted heading** and a **scrollable viewport** (range in the
164
+ heading) that re-renders on terminal resize
165
+ - `Enter`/`Space` switches to the row but **stays open**; `n` creates (name
166
+ input, blank = auto-title); `r` renames; `R` (Shift+r) sets a reminder;
167
+ `d` deletes (y/N); `a` switches agent; `m` switches model; `p` opens the
168
+ project switcher; `h` toggles help; `q`/`Ctrl+C` quits
169
+ - an **inverted footer** with the key hints, plus info lines about the
170
+ selected session (title, location, and a responsive grid of model, agent,
171
+ status + time in status, tokens, cost, context limit, outcome).
172
+ 5. If **not** a TTY: focus the window and print the selected session ID on
173
+ stdout (non-interactive).
174
+
175
+ Output contract: prints the chosen session ID on stdout at the end (no longer a
176
+ URL). `agentp` does not depend on this.
177
+
178
+ ### 4.3 `ocmux switch` — project picker (read-only)
179
+
180
+ - Lists every tmux window in the `Opencode` session that has a `.ocmux.json`
181
+ (project windows), plus their selected session.
182
+ - Highlights the tmux-active window (live `activeWindowIndex()`).
183
+ - `Enter`/`Space` focuses a window and keeps the menu open; `q` quits.
184
+ - **Must never write `.ocmux.json`.** The dead-TUI restart path may *read* the
185
+ state file, but only to know which session to relaunch with.
186
+
187
+ ### 4.4 Window layout (shared-server model)
188
+
189
+ ```
190
+ tmux session: Opencode
191
+ ├── window "__server__" (optional; server log view) ← excluded from switch/list
192
+ ├── window /home/user/proj-a pane 0: TUI (opencode --server <url> --session <id>)
193
+ └── window /home/user/proj-b pane 0: TUI (opencode --server <url> --session <id>)
194
+ ```
195
+
196
+ - Project windows contain **only the TUI** (pane 0). The old "pane 0 = server,
197
+ pane 1+ = TUI" assumption is dropped (see §6.4 for the transition shim).
198
+ - The server runs detached (or in the `__server__`/`opencode service`), logging to
199
+ `agentp-server-<profileHash>.log`.
200
+
201
+ ---
202
+
203
+ ## 5. TUI session switching
204
+
205
+ OpenCode v2 exposes **no reachable HTTP endpoint to steer a TUI** (verified:
206
+ `/tui/*` is not a v2 namespace; `tui.session.select` exists as a client event
207
+ but has no public emitter). Therefore switching is done by **relaunching the TUI
208
+ process**:
209
+
210
+ ```
211
+ tmux respawn-pane -k -t <tuiPane> "opencode --server '<url>' --session '<id>'"
212
+ ```
213
+
214
+ - If the pane is missing: `split-window`/`new-window` with `cwd = directory`,
215
+ then zoom.
216
+ - **Validate the session exists first** (`getSession`). `--session` *creates* the
217
+ session if the ID is unknown, which we do not want for a stale state file.
218
+ - While the previous session's execution keeps running server-side, the relaunch
219
+ only detaches the *view*; note this in docs.
220
+ - If OpenCode ever ships a TUI-steering endpoint, prefer publishing
221
+ `tui.session.select` (directory-scoped) and keep the relaunch as fallback.
222
+
223
+ ---
224
+
225
+ ## 6. Changes to the codebase
226
+
227
+ ### 6.1 `lib/opencode.js`
228
+
229
+ - `listSessions(server, directory)` — already accepts `directory`; ensure **all**
230
+ callers pass it.
231
+ - `createSession(server, title, location)` — add `location` to the POST body
232
+ (`POST /api/session` supports `location`).
233
+ - `listenV2` — insert a `"\n\n"` separator between assistant text segments:
234
+ on `session.text.started`, if `collected` is non-empty, append `"\n\n"` before
235
+ the next delta. Fixes run-on per-step narration. (Optional `--final` mode to
236
+ emit only the last segment.)
237
+ - `selectSession` — keep the v2 no-op, with an explicit comment pointing at §5.
238
+
239
+ ### 6.2 New state helpers (`lib/ocmux.js` or a new `lib/project-state.js`)
240
+
241
+ - `findProjectStatefile(startDir)` — upward search.
242
+ - `readProjectState(dirOrFile)` — parse + legacy normalization.
243
+ - `writeProjectState(dir, patch)` — atomic merge write.
244
+ - `resolveProject({ server, directory })` — resolve dir/session/server using
245
+ §3.2–§3.4.
246
+ - `checkServer(server)` — `GET /api/info`; throws a clear "start `opencode serve`"
247
+ error when unreachable. No lifecycle management.
248
+ - `listProjects()` — windows with `.ocmux.json`, with selected session.
249
+ - `relaunchTui(windowIndex, dir, server, session)` — the §5 command; tested via
250
+ a mocked `_tmux`.
251
+ - `activateProject(dir)` — focus + zoom; restart dead TUI using the stored
252
+ session (read-only).
253
+
254
+ ### 6.3 `bin/ocmux`
255
+
256
+ - Restructure `main()` dispatch:
257
+ - `ocmux` (default) → session picker (§4.2).
258
+ - `switch` → project picker (§4.3).
259
+ - `serve`/`new` → `cmdNew` rewritten for shared server + TUI-only window.
260
+ - add `session`, `down`; repurpose `resurrect`.
261
+ - `list` shows projects + selected sessions.
262
+ - `model` targets the selected session (drop the URL-printing contract).
263
+ - Remove/retire per-project server startup from `startServer` (server pane,
264
+ `tee` log polling) in favor of `ensureServer()`.
265
+ - Keep `--git`/`--GIT` and directory resolution semantics.
266
+
267
+ ### 6.4 `lib/ocmux.js` transition shim
268
+
269
+ - `tuiPaneId(windowIndex)` currently returns the first non-zero pane; in the new
270
+ layout the TUI is pane 0. During migration, support **both**:
271
+ - layout v2: TUI is pane 0 (or the only pane);
272
+ - layout v1: pane 0 is server, TUI is pane 1+ (tolerate until `ocmux migrate`).
273
+ - `windowByDir`/`windowNameByIndex`/`activeWindowIndex` unchanged.
274
+
275
+ ### 6.5 `bin/agentp`
276
+
277
+ - Replace `resolveTargetSession` boilerplate with `resolveProject` + session
278
+ precedence (§3.4). `resolveTargetSession(server, dir, { sessionName, newSession, preferredSession })`.
279
+ - Pass `directory` to **every** `listSessions` call (`resolveTargetSession`,
280
+ `--getLast`).
281
+ - **Never writes `.ocmux.json`** (ocmux owns session persistence).
282
+ - Keep positional URL arg as override; still accept `agentp $(ocmux)` (the URL is
283
+ now just a server override; directory/session come from the state file).
284
+ - Deferred tickets: unchanged shape (`server`, `sessionId`); store the resolved
285
+ session ID.
286
+ - Output-separator fix is inherited from `lib/opencode.js`.
287
+
288
+ ### 6.6 `bin/tgagentp` (follow-up workstream)
289
+
290
+ - Connection entries already store `dir`; add `session`. Resolve server from the
291
+ global state + `.ocmux.json`; target `session` for messages.
292
+ - `/servers switch` = project switch (windows); add a session switch command
293
+ (e.g. `/session <n>`) mirroring `ocmux`.
294
+ - Scope all `listSessions` by `projectDir` (lines ~602/767 currently unscoped).
295
+ - Larger surface — can land after `ocmux`/`agentp`.
296
+
297
+ ### 6.7 Docs
298
+
299
+ - Update `README.md` (usage, `agentp` no longer needs `$(ocmux)`; `ocmux`
300
+ session/project pickers).
301
+ - Update `docs/specification.md` → point to this file; reconcile state schema.
302
+ - `CHANGELOG.md` full summary. **Do not bump version without approval.**
303
+
304
+ ---
305
+
306
+ ## 7. Edge cases & risks (analysis)
307
+
308
+ 1. **In-TUI session drift (highest risk).** The user can create/switch sessions
309
+ *inside* the TUI (`session.new`, tabs). `.ocmux.json.session` then disagrees
310
+ with what is displayed, and `agentp` would target the stale session.
311
+ - Mitigation A (recommended): `ocmux` is the canonical switcher; in-TUI
312
+ navigation is treated as inspection. Provide `ocmux sync`/`--adopt` to
313
+ adopt the TUI's current session (newest `time.viewed` in the directory).
314
+ - Mitigation B: `agentp` warns when `state.session !== newest-viewed` in the
315
+ same directory.
316
+ 2. **`--session` creates if missing.** A stale/deleted stored ID would be
317
+ silently recreated. Always validate with `getSession` before relaunch/send.
318
+ 3. **Session moved/deleted.** `POST /api/session/:id/move` changes
319
+ `location.directory`; `delete` removes it. Detect 404/mismatch → fall back to
320
+ §3.4.3 and repair the file.
321
+ 4. **Relaunch is disruptive.** Loses scrollback and in-TUI state; if the same
322
+ session is running a prompt, the view detaches (execution continues). Document
323
+ it; skip relaunch when the TUI is already on the target session.
324
+ 5. **`ocmux switch` writes.** Ensure every code path it can reach (activate,
325
+ restart dead TUI, zoom) is read-only for `.ocmux.json`. Add a test asserting
326
+ no write.
327
+ 6. **Persistence races.** Atomic writes + read-once at start. `agentp` must not
328
+ rewrite `session` while `ocmux` is switching.
329
+ 7. **No `.ocmux.json`.** `ocmux` offers `serve`; `agentp` prints a clear error and
330
+ exits 1 (as today). Define the exact message.
331
+ 8. **Empty project (no sessions).** Picker offers `n` (create). `agentp`
332
+ bootstraps a session in `directory` and (optionally) persists it.
333
+ 9. **Nested projects / parent state.** Upward search precedence is nearest file.
334
+ `--git`/`--GIT` unchanged. Validate `directory` matches realpath; repair if
335
+ the file moved.
336
+ 10. **Multiple profiles (work vs home).** One server per profile keyed by data
337
+ dir/binary. Global state namespaced; never share across profiles.
338
+ 11. **Isolation.** A shared server lets any client reach any project. The store is
339
+ already shared, so this is not a regression; document it. True isolation
340
+ requires a separate `XDG_DATA_HOME`.
341
+ 12. **Server restart / port change.** Global state is authoritative; per-project
342
+ cached `server` may be stale. Health-check before use; refresh on success.
343
+ 13. **`ocmux model` contract change.** Previously printed the URL for
344
+ `agentp $(ocmux model …)`. With `agentp` self-resolving, it should instead
345
+ switch the selected session's model and print a human confirmation.
346
+ 14. **`resurrect`/`kill` semantics.** `kill` removes a project window + state;
347
+ with a shared server this is lighter. `resurrect` recreates a window. Define
348
+ whether killing keeps session memory (proposal: remove state, matching today).
349
+ 15. **Window layout migration.** Old windows have a server pane; new ones do not.
350
+ The transition shim (§6.4) handles both until `ocmux migrate` runs.
351
+ 16. **Run-on output (separate but included).** `listenV2` concatenates text
352
+ deltas across steps with no separator — fixes §6.1.
353
+ 17. **Built-in background service.** `opencode service start|stop|status` exists
354
+ and may own the shared server. Option: delegate lifecycle to it and have
355
+ `ocmux` only manage TUIs. Recommend evaluating after Phase 1; keep
356
+ `ensureServer()` abstract so the backend can change.
357
+ 18. **`opencode --session` semantics.** Confirmed present: "Session ID to
358
+ continue, or to create if it does not exist" — hence the validate-first rule.
359
+
360
+ ### 7.1 Things easy to miss
361
+
362
+ - `agentp` must still accept the legacy URL positional for back-compat, but must
363
+ **not** let it determine the project (directory comes from state/cwd).
364
+ - `tgagentp` is a first-class consumer of `.ocmux.json`; keep the schema in one
365
+ place so all three tools agree.
366
+ - `.ocmux.json` is gitignored — never commit it; `ocmux migrate` must not create
367
+ tracked files.
368
+ - The tmux `__server__` window must be excluded from `switch`/`list` counts.
369
+
370
+ ---
371
+
372
+ ## 8. Implementation plan — workstreams ("subagents")
373
+
374
+ Each workstream is a self-contained unit suitable for delegation to a
375
+ `general` subagent (implementation) with an `explore` subagent for audits.
376
+ Workstreams A–B are prerequisites; C and D can run in parallel after A; E–F
377
+ follow. G runs last.
378
+
379
+ ### Subagent A — Core state & resolution (`core-state`)
380
+
381
+ - **Goal:** Centralize `.ocmux.json` read/write/migration and server/dir/session
382
+ resolution; make it the single contract shared by all CLIs.
383
+ - **Deliverables:**
384
+ - New `lib/project-state.js` (or additions to `lib/ocmux.js`):
385
+ `findProjectStatefile`, `readProjectState`, `writeProjectState`,
386
+ `resolveProject`, `serverStatePath/readServerState/writeServerState`.
387
+ - Legacy normalization + atomic writes.
388
+ - Unit tests: legacy parse, moved-file repair, precedence table, atomic write.
389
+ - **Depends on:** nothing.
390
+ - **Checkpoints:** all precedence cases covered; no partial writes under a
391
+ simulated crash; legacy files never throw.
392
+
393
+ ### Subagent B — ~~Server lifecycle~~ (removed)
394
+
395
+ Per §11.1 the server is user-managed. Only a read-only `checkServer()` remains
396
+ (folded into workstream A). No lifecycle workstream.
397
+
398
+ ### Subagent C — `ocmux` CLI rework (`ocmux-cli`)
399
+
400
+ - **Goal:** New command surface (§4): session picker, project picker, serve,
401
+ session, kill, down, list, model; TUI relaunch (§5).
402
+ - **Deliverables:** `bin/ocmux` dispatch + `lib/ocmux.js` `relaunchTui`,
403
+ `listProjects`, `activateProject`, transition shim (§6.4).
404
+ - **Depends on:** A, B.
405
+ - **Checkpoints:** `ocmux` picker updates `session` + relaunches TUI; `ocmux
406
+ switch` writes nothing (test); TUI-only layout works; v1 layout tolerated.
407
+
408
+ ### Subagent D — `agentp` integration (`agentp-integration`)
409
+
410
+ - **Goal:** Self-resolving project/session; scoped listing; output fix.
411
+ - **Deliverables:** `resolveTargetSession(server, dir, opts)`; directory passed
412
+ everywhere; bootstrap persistence; `--getLast` scoping; text-segment separator;
413
+ legacy URL arg preserved.
414
+ - **Depends on:** A (shared resolution).
415
+ - **Checkpoints:** with two projects and a global store, prompts hit the stored
416
+ session of the correct directory; `--session`/`--new` override without
417
+ persisting; run-on output fixed.
418
+
419
+ ### Subagent E — `tgagentp` integration (`tgagentp-integration`)
420
+
421
+ - **Goal:** Target the same project/session; scope listings.
422
+ - **Deliverables:** connection `session` field; session switch command; scoped
423
+ `listSessions`; server resolution via A/B.
424
+ - **Depends on:** A, B, D (patterns).
425
+ - **Checkpoints:** Telegram message lands in the stored session; `/servers
426
+ switch` maps to project windows; no unscoped listing remains.
427
+
428
+ ### Subagent F — Tests, docs, changelog (`tests-docs`)
429
+
430
+ - **Goal:** Regression + migration coverage; user-facing docs.
431
+ - **Deliverables:** `tests/project-state.test.js`; updates to
432
+ `tests/agentp.test.js`, `tests/ocmux.test.js`; `README.md`; align
433
+ `docs/specification.md`; `CHANGELOG.md` entry (no version bump).
434
+ - **Depends on:** A–E (can start on A early).
435
+ - **Checkpoints:** full suite green; docs show `agentp` without `$(ocmux)`; state
436
+ schema documented in one place.
437
+
438
+ ### Subagent G — Integration verification (`integration-verify`)
439
+
440
+ - **Goal:** End-to-end acceptance and a smooth transition.
441
+ - **Deliverables:** a manual/automated checklist run against a live OpenCode v2
442
+ with ≥2 projects:
443
+ 1. `ocmux serve` for two dirs creates two TUI windows on one shared server.
444
+ 2. `ocmux` in project A picks a session; `.ocmux.json.session` updates; TUI
445
+ relaunches on it.
446
+ 3. `ocmux switch` focuses B without touching either state file.
447
+ 4. `agentp --qa <<< "..."` from A targets A's session; from B targets B's;
448
+ responses appear in the correct window.
449
+ 5. Switch session inside the TUI, then verify `ocmux sync`/warning behavior.
450
+ 6. `--defer` ticket round-trip targets the same session.
451
+ 7. Legacy file migration; server restart/port change; `ocmux down`.
452
+ - **Depends on:** A–F.
453
+ - **Checkpoints:** checklist fully green; no wrong-window routing; rollback
454
+ documented.
455
+
456
+ ### 8.1 Suggested execution order
457
+
458
+ ```
459
+ A ──▶ C ──┐
460
+ └──▶ D ──┴──▶ F ──▶ G
461
+ E ──┘ (deferred)
462
+ ```
463
+
464
+ Parallelization: C and D after A; E deferred; F continuously; G last.
465
+
466
+ ---
467
+
468
+ ## 9. Migration & rollout
469
+
470
+ 1. **Read path first:** ship legacy-tolerant readers (A) before any writer
471
+ changes. Old and new files both work.
472
+ 2. **Migration command:** `ocmux migrate [--dry-run]`:
473
+ - health-check the server;
474
+ - for each project window: rewrite `.ocmux.json` to v2 (directory = realpath,
475
+ session = current/newest scoped, server = the server it already used);
476
+ - optionally retire per-project server panes.
477
+ 3. **Compatibility window:** keep the v1 layout shim (§6.4) until all windows are
478
+ migrated.
479
+ 4. **Rollback:** because `.ocmux.json` and code are versioned and the store is
480
+ untouched, reverting the binary restores v1 behavior; migration can be
481
+ re-run. Keep a `.ocmux.json.bak` during migrate.
482
+ 5. **Release:** update CHANGELOG; version bump **only on maintainer approval**
483
+ (per `AGENTS.md`).
484
+
485
+ ---
486
+
487
+ ## 10. Acceptance criteria (definition of done)
488
+
489
+ - [ ] `agentp` resolves server/directory/session from the nearest `.ocmux.json`
490
+ and works without `$(ocmux)`.
491
+ - [ ] No code path calls `listSessions` without a `directory`.
492
+ - [ ] `createSession` sets `location`.
493
+ - [ ] `ocmux` (no args) updates `session` and switches the TUI to it.
494
+ - [ ] `ocmux switch` never writes `.ocmux.json` (tested).
495
+ - [ ] A session deleted/moved is detected and repaired; no silent recreation.
496
+ - [ ] Run-on assistant text is separated into paragraphs.
497
+ - [ ] Full test suite green; migration and legacy reads covered.
498
+ - [ ] `README.md`, `docs/specification.md`, `CHANGELOG.md` updated.
499
+
500
+ ---
501
+
502
+ ## 11. Decisions (resolved 2026-10-07)
503
+
504
+ 0. **OpenCode v1 is dropped.** `lib/opencode.js` is v2-only; the legacy
505
+ `/tui/*`, legacy SSE listeners and legacy endpoint paths were removed.
506
+ 1. **Server is OUT OF SCOPE.** ocmux/agentp do **not** start, stop, restart, or
507
+ supervise the server. The user starts `opencode serve` manually. The tools
508
+ only **check** that the configured server is reachable and **complain**
509
+ (exit 1 with a clear hint) otherwise. There is no `ensureServer()`, no
510
+ `ocmux up`/`down`, and no managed global server state.
511
+ - Note: a server's launch directory is only its *default location*; it can
512
+ serve any location via `directory`/`location` parameters.
513
+ 2. **No in-TUI drift handling.** The TUI is used **only for viewing** (reasoning
514
+ + colored markdown). Sessions are never switched inside the TUI. `.ocmux.json`
515
+ is authoritative; no `ocmux sync`/adoption logic.
516
+ 3. **`kill` keeps `.ocmux.json`.** Closing a project removes the tmux window/TUI
517
+ but preserves the state file (session memory). `kill` may mark it
518
+ `"status": "stopped"`; `list`/`switch` derive/defunct status from tmux window
519
+ existence. There is no server to kill — only the TUI window.
520
+ 4. **`ocmux` (no args) prints nothing** on success; it is the interactive session
521
+ switcher. Any diagnostics go to stderr. The `switch` subcommand was removed;
522
+ **`p`** in the picker opens the project switcher.
523
+ 4b. **Session picker keys:** `Enter` switch (stays open) · `n` create · `r`
524
+ rename (readline-style caret editing) · `R` reminder (annotation) · `d`
525
+ delete · `a` agents · `m` model · `p` projects · `h` help · `q` quit. The
526
+ `model` subcommand was removed (use `m`), like `switch` (use `p`).
527
+ 5. **tgagentp is deferred** to a follow-up release (workstream E).
528
+ 6. **Server URL source:** per-project `.ocmux.json.server`, overridable by
529
+ `--server`/`OPENCODE_SERVER_URL`; default `http://localhost:4096`. Recorded by
530
+ `ocmux serve` and never managed thereafter.
531
+
532
+ ---
533
+
534
+ ## 12. Appendix — verified v2 facts (2026-10-06)
535
+
536
+ - OpenCode v2.0.21; API under `/api`; Basic auth via `OPENCODE_SERVER_PASSWORD`.
537
+ - Store: `~/.local/share/opencode/opencode.db`.
538
+ - `GET /api/session?directory=<realpath>` is exact-match (subdir/trailing slash ⇒
539
+ empty). Cross-location access works from any server.
540
+ - `POST /api/session` payload supports `location`.
541
+ - `GET /api/session/active` lists sessions with running executions.
542
+ - `session.viewed` / `time.viewed` track the TUI's last-viewed session.
543
+ - No reachable HTTP TUI-steering endpoint (`/tui/*` ⇒ 405/catch-all);
544
+ `tui.session.select` is a directory-scoped client event with no public emitter.
545
+ - `opencode --session/-s <id>` opens (or creates) a session in the TUI.
546
+ - `opencode service` manages a built-in background server.
547
+
548
+ ---
549
+
550
+ ## 13. Future improvements
551
+
552
+ ### 13.1 Per-session annotations (`a` key in the session picker)
553
+
554
+ **Status: IMPLEMENTED (2026-10-07).**
555
+
556
+ The `R` (Shift+r) key in the `ocmux` session picker attaches a short
557
+ **reminder** to the selected session (e.g. *"Remember to work in the worktree
558
+ foobar"*). Annotated sessions are marked with `◈`. `agentp` prepends the
559
+ reminder (plus a blank line) to **every prompt** sent to that session.
560
+
561
+ Persistence: an **`annotations` map (sessionID → text) inside `.ocmux.json`**,
562
+ so a project keeps all its state in one file; empty text removes the entry.
563
+ Written atomically (tmp + rename). A legacy `annotations.json` sidecar is read
564
+ for back-compat and folded into the state file on the next write.
565
+
566
+ Integration points (implemented):
567
+ - `lib/project-state.js`: `readAnnotations(dir)`, `writeAnnotation(dir, id, text)`.
568
+ - `bin/ocmux` session picker: `a` key → input mode (reuses the rename input
569
+ with caret editing, ESC cancels); documented in the `h` help menu.
570
+ - `bin/agentp`: after session resolution, prepends `annotation + "\n\n"` to the
571
+ prompt before `sendToSession`.
572
+
573
+ Known limitation: follow-ups queued via a deferred **ticket** do not re-inject
574
+ the annotation (the ticket carries only `server`/`sessionId`). If needed later,
575
+ the annotation can be looked up from the ticket's session on the server.
576
+
577
+ ### 13.2 Completion hardening (annotated)
578
+
579
+ `listenV2` resolves after a **silent** grace window once a terminal signal
580
+ (`session.execution.succeeded|failed`) arrives; ANY stream activity (including
581
+ child-session/sub-agent events) resets it, and `sendToSession` additionally
582
+ verifies the session's idle marker is newer than the send. The default window is
583
+ **15 s** (tunable via `AGENTP_COMPLETION_GRACE_MS`). A sub-agent (or tool) that
584
+ goes completely silent for longer than the window can still truncate an answer.
585
+ Candidates for the next pass:
586
+ - wait on `GET /api/experimental/session/:id/wait` ("Wait for a session agent
587
+ loop to become idle") after the terminal signal;
588
+ - explicit child-session tracking (`parentID`) so sub-agent runs keep the parent
589
+ "working" regardless of event silence.
590
+
591
+ ### 13.3 Deferred-mode long-prompt handling (annotated, planned)
592
+
593
+ Problem: a long prompt with a **silent** gap longer than the completion window
594
+ (default 15s, `AGENTP_COMPLETION_GRACE_MS`) can still return a truncated answer.
595
+ Raising the window reduced this but did not eliminate it.
596
+
597
+ Proposed approach (does not change the `--defer 0` re-send semantics that
598
+ follow-up queuing relies on):
599
+
600
+ 1. In `--defer`/child mode, **do not cap** the "verify idle" retries — keep
601
+ extending while the session is genuinely busy (bounded only by a larger
602
+ safety timeout).
603
+ 2. When the listener *does* give up (verify cap or safety timeout), write an
604
+ `<output>.incomplete` marker next to the deferred result.
605
+ 3. On retrieval, if the marker exists, print the captured output followed by
606
+ `⚠️ may be incomplete — re-send the ticket for the full answer`, then clear
607
+ the marker.
608
+
609
+ Longer term, prefer an authoritative completion signal
610
+ (`GET /api/experimental/session/:id/wait` and/or child-session tracking via
611
+ `parentID`) over any timed quiescence.