agentp 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +7 -5
- package/README.md +129 -27
- package/bin/agentp +224 -48
- package/bin/ocmux +965 -290
- package/bin/tgagentp +121 -130
- package/docs/specification.md +2 -1
- package/docs/specification_v2.md +188 -583
- package/lib/ocmux.js +26 -289
- package/lib/opencode.js +186 -43
- package/lib/project-state.js +8 -0
- package/lib/tui-cmd.js +12 -5
- package/lib/tui-registry.js +313 -0
- package/package.json +1 -1
package/docs/specification_v2.md
CHANGED
|
@@ -1,611 +1,216 @@
|
|
|
1
|
-
# agentp
|
|
1
|
+
# agentp 2.x — Project, Session, and TUI Specification
|
|
2
2
|
|
|
3
|
-
Status: **
|
|
4
|
-
model described in `docs/specification.md`.
|
|
3
|
+
Status: **Current**
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
## 1. Runtime model
|
|
7
6
|
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
per-project-server design:
|
|
10
|
+
The tools therefore use these identities:
|
|
12
11
|
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
19
|
+
## 2. Project state
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
## 3. TUI runtime registry
|
|
49
43
|
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
+
Each instance records:
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
- foreground wrapper PID and current child PID (diagnostic only);
|
|
63
|
+
- registration/update timestamps.
|
|
75
64
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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)
|
|
65
|
+
The pane itself receives `@ocmux_tui_id` and `@ocmux_tui_token` tmux options.
|
|
66
|
+
Before a destructive pane operation, both must match the protected registry.
|
|
67
|
+
This prevents a stale registration from respawning an unrelated pane. Pane IDs
|
|
68
|
+
remain useful after `move-window`, `join-pane`, or `break-pane` within the same
|
|
69
|
+
tmux server; recording the socket disambiguates multiple tmux servers.
|
|
188
70
|
|
|
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
|
-
```
|
|
71
|
+
There are two assignment slots:
|
|
195
72
|
|
|
196
|
-
-
|
|
197
|
-
|
|
198
|
-
- The server runs detached (or in the `__server__`/`opencode service`), logging to
|
|
199
|
-
`agentp-server-<profileHash>.log`.
|
|
73
|
+
- one dedicated TUI per canonical project directory;
|
|
74
|
+
- exactly one shared TUI across all projects and server URLs.
|
|
200
75
|
|
|
201
|
-
|
|
76
|
+
There is no `--global` alias.
|
|
202
77
|
|
|
203
|
-
##
|
|
78
|
+
## 4. TUI routing
|
|
204
79
|
|
|
205
|
-
|
|
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**:
|
|
80
|
+
When `ocmux` selects or inspects a session, it resolves the display in order:
|
|
209
81
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
82
|
+
1. live TUI dedicated to the target directory;
|
|
83
|
+
2. the one live shared TUI;
|
|
84
|
+
3. no display (headless success).
|
|
213
85
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
86
|
+
A stale/dead dedicated registration is ignored, so routing falls back to the
|
|
87
|
+
shared registration. A stale shared registration yields headless operation.
|
|
88
|
+
Display failure is a warning and never rolls back the durable session choice.
|
|
457
89
|
|
|
90
|
+
The shared TUI is deliberately not server-scoped. When a selected project uses
|
|
91
|
+
a different server, the same pane reconnects to that server.
|
|
92
|
+
|
|
93
|
+
## 5. Foreground TUI wrapper
|
|
94
|
+
|
|
95
|
+
`ocmux tui [--shared] [directory]`:
|
|
96
|
+
|
|
97
|
+
1. Requires `$TMUX` and `$TMUX_PANE`.
|
|
98
|
+
2. Resolves `.ocmux.json`, server, directory, and session.
|
|
99
|
+
3. Health-checks the server and validates the session, creating one with a
|
|
100
|
+
model when necessary.
|
|
101
|
+
4. Marks and registers the current pane.
|
|
102
|
+
5. Stops the displaced wrapper for the same dedicated/shared slot, preserving
|
|
103
|
+
its pane.
|
|
104
|
+
6. Spawns `opencode` as a foreground child with inherited stdio:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
opencode --server <url> --session <id> <directory>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
7. Forwards `SIGHUP`, `SIGTERM`, and `SIGINT` to the child.
|
|
111
|
+
8. On child exit, unregisters only when instance ID and token still match.
|
|
112
|
+
|
|
113
|
+
Token-conditional cleanup prevents an old wrapper from deleting a replacement's
|
|
114
|
+
new registration.
|
|
115
|
+
|
|
116
|
+
## 6. Switching a registered TUI
|
|
117
|
+
|
|
118
|
+
OpenCode exposes no HTTP operation for steering one specific TUI client. A
|
|
119
|
+
registered pane is therefore switched with:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
tmux -S <socket> respawn-pane -k -t <pane> -c <directory> \
|
|
123
|
+
"<ocmux> tui [--shared] --server <url> --session-id <id> -- <directory>"
|
|
458
124
|
```
|
|
459
|
-
A ──▶ C ──┐
|
|
460
|
-
└──▶ D ──┴──▶ F ──▶ G
|
|
461
|
-
E ──┘ (deferred)
|
|
462
|
-
```
|
|
463
125
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
##
|
|
535
|
-
|
|
536
|
-
-
|
|
537
|
-
-
|
|
538
|
-
-
|
|
539
|
-
|
|
540
|
-
-
|
|
541
|
-
-
|
|
542
|
-
-
|
|
543
|
-
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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.
|
|
126
|
+
The new `ocmux tui` process re-registers the pane and hosts the new OpenCode
|
|
127
|
+
child. Server-side session execution continues while the old client disconnects.
|
|
128
|
+
Client-local state—draft prompt, scroll position, dialogs, and tabs—is lost.
|
|
129
|
+
|
|
130
|
+
Native TUI navigation is temporary inspection and is not observable through
|
|
131
|
+
the OpenCode HTTP API. The next ocmux selection restores the canonical target.
|
|
132
|
+
|
|
133
|
+
## 7. Commands
|
|
134
|
+
|
|
135
|
+
### `ocmux serve [dir]`
|
|
136
|
+
|
|
137
|
+
Initializes `.ocmux.json`, selecting the newest main session in the directory
|
|
138
|
+
or creating one with a model. It creates no tmux session, window, or pane.
|
|
139
|
+
`--force --server <url>` repoints existing state and refreshes the routed TUI.
|
|
140
|
+
|
|
141
|
+
### `ocmux tui [--shared] [--server <url>] [dir]`
|
|
142
|
+
|
|
143
|
+
Registers and hosts a dedicated or shared TUI as described above. `--shared`
|
|
144
|
+
is valid only for this command. `--server` overrides the project's recorded
|
|
145
|
+
server for the launched wrapper. Management modes are:
|
|
146
|
+
|
|
147
|
+
- `ocmux tui --list` — prune stale entries and print every live registration;
|
|
148
|
+
- `ocmux tui [dir] --status` — inspect the dedicated slot for that project;
|
|
149
|
+
- `ocmux tui --shared --status` — inspect the shared slot;
|
|
150
|
+
- `ocmux tui [dir] --detach` — unregister the dedicated slot;
|
|
151
|
+
- `ocmux tui --shared --detach` — unregister the shared slot.
|
|
152
|
+
|
|
153
|
+
`--detach` clears the pane's verification options but does not stop OpenCode or
|
|
154
|
+
destroy the pane. Management modes do not require running inside tmux.
|
|
155
|
+
|
|
156
|
+
### `ocmux`
|
|
157
|
+
|
|
158
|
+
Opens the current project's session picker. Picking writes that project's
|
|
159
|
+
session and refreshes its routed TUI. Session creation, rename/delete,
|
|
160
|
+
annotations, agent/model choice, broadcast selection, search, and project
|
|
161
|
+
inspection operate through OpenCode's API.
|
|
162
|
+
|
|
163
|
+
### `ocmux session <id|title> [dir]`
|
|
164
|
+
|
|
165
|
+
Non-interactively writes the selected session and refreshes its routed TUI.
|
|
166
|
+
|
|
167
|
+
### Project switcher (`p`)
|
|
168
|
+
|
|
169
|
+
Configured projects are discovered from `/api/project` plus session locations;
|
|
170
|
+
only directories containing `.ocmux.json` are included. This captures worktrees
|
|
171
|
+
that are locations but not separate OpenCode project records.
|
|
172
|
+
|
|
173
|
+
By default the switcher only inspects sessions through the routed TUI and never
|
|
174
|
+
writes another project's state. `--all-projects` lets the outer session picker
|
|
175
|
+
move to another project and subsequently update that project's state.
|
|
176
|
+
|
|
177
|
+
### `ocmux list [-l]`
|
|
178
|
+
|
|
179
|
+
Lists configured projects, selected sessions, and display status:
|
|
180
|
+
|
|
181
|
+
- `project` — a live dedicated TUI wins;
|
|
182
|
+
- `shared` — the live shared fallback applies;
|
|
183
|
+
- `headless` — no live registration.
|
|
184
|
+
|
|
185
|
+
`-l` also prints the server URL.
|
|
186
|
+
|
|
187
|
+
The old managed-window `kill` and `resurrect` commands do not exist.
|
|
188
|
+
|
|
189
|
+
## 8. `agentp`
|
|
190
|
+
|
|
191
|
+
`agentp` resolves project/session/server from explicit options and the nearest
|
|
192
|
+
state file, then sends directly to the session API. It does not focus, move, or
|
|
193
|
+
refresh a TUI; TUI routing is an explicit consequence of ocmux session
|
|
194
|
+
selection, not prompt submission.
|
|
195
|
+
|
|
196
|
+
## 9. Failure behavior
|
|
197
|
+
|
|
198
|
+
- Missing/unreachable server: command fails before registration or switching.
|
|
199
|
+
- Missing TUI: session selection succeeds headlessly.
|
|
200
|
+
- Dead pane or mismatched token: registration is pruned and fallback routing is
|
|
201
|
+
attempted on the next resolution.
|
|
202
|
+
- Failed `respawn-pane`: selected state remains valid and a warning is printed.
|
|
203
|
+
- Manual OpenCode exit: foreground wrapper removes its own registration.
|
|
204
|
+
- Replaced wrapper exits late: token check prevents it removing the new owner.
|
|
205
|
+
- tmux restart/reboot: pane checks invalidate and prune old runtime entries.
|
|
206
|
+
- Multiple tmux servers: recorded `-S <socket>` targets the correct one.
|
|
207
|
+
|
|
208
|
+
## 10. Security boundaries
|
|
209
|
+
|
|
210
|
+
- `.ocmux.json` never identifies a process or pane to terminate.
|
|
211
|
+
- Destructive pane operations require matching registry and pane tokens.
|
|
212
|
+
- Commands are built from validated server/session/directory values with shell
|
|
213
|
+
quoting; no command string is read from project state.
|
|
214
|
+
- Runtime registry and lock files are private to the user.
|
|
215
|
+
- Wrapper/child PIDs are diagnostic only and are never used as durable identity
|
|
216
|
+
or as the target of a destructive operation.
|