agentp 1.14.0 → 2.0.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 +80 -83
- package/README.md +165 -51
- package/bin/agentp +525 -84
- package/bin/ocmux +1376 -580
- package/docs/specification.md +12 -454
- package/docs/specification_v2.md +652 -0
- package/lib/ocmux.js +123 -197
- package/lib/opencode.js +363 -657
- package/lib/project-state.js +213 -0
- package/package.json +1 -1
|
@@ -0,0 +1,652 @@
|
|
|
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
|
+
| `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)
|
|
229
|
+
|
|
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
|
+
```
|
|
236
|
+
|
|
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`.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 5. TUI session switching
|
|
245
|
+
|
|
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**:
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
tmux respawn-pane -k -t <tuiPane> "opencode --server '<url>' --session '<id>'"
|
|
253
|
+
```
|
|
254
|
+
|
|
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
|
|
498
|
+
|
|
499
|
+
```
|
|
500
|
+
A ──▶ C ──┐
|
|
501
|
+
└──▶ D ──┴──▶ F ──▶ G
|
|
502
|
+
E ──┘ (deferred)
|
|
503
|
+
```
|
|
504
|
+
|
|
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.
|