cyber-mux 0.3.0 → 0.4.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/dist/agent.d.mts +66 -0
- package/dist/agent.d.mts.map +1 -0
- package/dist/agent.mjs +58 -0
- package/dist/agent.mjs.map +1 -0
- package/dist/{backend-DnrbmL6N.mjs → backend-Cif257ji.mjs} +326 -6
- package/dist/backend-Cif257ji.mjs.map +1 -0
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +324 -19
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +66 -581
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/mux-CoYDrk3v.d.mts +733 -0
- package/dist/mux-CoYDrk3v.d.mts.map +1 -0
- package/dist/template.mjs +1 -1
- package/dist/{worktree-CX88bGzZ.d.mts → worktree-9QDz-iKC.d.mts} +63 -2
- package/dist/{worktree-CX88bGzZ.d.mts.map → worktree-9QDz-iKC.d.mts.map} +1 -1
- package/dist/{worktree-CoQdRgv1.mjs → worktree-hHuFZkpW.mjs} +84 -2
- package/dist/{worktree-CoQdRgv1.mjs.map → worktree-hHuFZkpW.mjs.map} +1 -1
- package/dist/worktree.d.mts +2 -2
- package/dist/worktree.mjs +2 -2
- package/package.json +5 -1
- package/src/agent.ts +95 -0
- package/src/backend.ts +22 -0
- package/src/cli-error.ts +31 -0
- package/src/cli.ts +436 -60
- package/src/index.ts +3 -1
- package/src/mux.herdr.ts +193 -3
- package/src/mux.tmux.ts +10 -0
- package/src/mux.ts +155 -0
- package/src/mux.wezterm.ts +9 -0
- package/src/mux.zellij.ts +9 -0
- package/src/nudge.ts +9 -0
- package/src/ratio.ts +25 -0
- package/src/template-capture.ts +66 -1
- package/src/wait-output.ts +116 -0
- package/src/worktree.ts +105 -0
- package/dist/backend-DnrbmL6N.mjs.map +0 -1
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
import { t as Exec } from "./exec-B81m4yjz.mjs";
|
|
2
|
+
import { n as Worktree } from "./worktree-9QDz-iKC.mjs";
|
|
3
|
+
//#region src/mux.d.ts
|
|
4
|
+
/** Generic multiplexer seam — no host-specific concepts, so this composes with any caller. */
|
|
5
|
+
/** Where a new pane/window/session should be placed relative to the caller's current one.
|
|
6
|
+
* `'workspace'` opens a genuinely separate workspace/session (herdr: `workspace create`; tmux: a
|
|
7
|
+
* new detached session) — the caller's current workspace/session is left untouched, unlike every
|
|
8
|
+
* other placement, which adds a pane/window inside it. */
|
|
9
|
+
type MuxPlacement = "pane:right" | "pane:down" | "tab" | "workspace";
|
|
10
|
+
/**
|
|
11
|
+
* The tier a `rename` names — which SPACE is being named, not where one is opened, so this is its
|
|
12
|
+
* own vocabulary rather than a reuse of `MuxPlacement`. The caller must say, because the two
|
|
13
|
+
* tiers are different commands on both backends (tmux `rename-window` vs `select-pane -T`; herdr
|
|
14
|
+
* `tab rename` vs `pane rename`) and neither backend can infer one from the other's id.
|
|
15
|
+
*
|
|
16
|
+
* `pane` collapses `MuxPlacement`'s two split directions — a direction is how a pane is BORN and
|
|
17
|
+
* says nothing about naming one that already exists. There is no `workspace` member: renaming exists
|
|
18
|
+
* for the one tier birth cannot name (a new workspace's root tab, which is a `tab`), and every
|
|
19
|
+
* backend that has a workspace tier already takes its label at birth (`workspace create --label`).
|
|
20
|
+
*/
|
|
21
|
+
type MuxSpaceTier = "pane" | "tab";
|
|
22
|
+
interface MuxOpenOptions {
|
|
23
|
+
/** Working directory the new pane/window/session should start in. */
|
|
24
|
+
cwd: string;
|
|
25
|
+
/** Command line to launch inside the new pane once it is open; omit for a blank pane. */
|
|
26
|
+
launch?: string | undefined;
|
|
27
|
+
/** Placement relative to the caller; defaults to 'tab'. */
|
|
28
|
+
at?: MuxPlacement | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The pane a `pane:*` placement splits. Ignored by `tab`/`workspace`, which split nothing.
|
|
31
|
+
*
|
|
32
|
+
* Pass it. Omitting it does **not** mean "the calling pane" — it means "whatever this backend
|
|
33
|
+
* defaults to", and the two backends default to opposite things: herdr resolves `--current` from
|
|
34
|
+
* `$HERDR_PANE_ID`, silently falling back to the UI-focused pane when that is unset; tmux ignores
|
|
35
|
+
* `$TMUX_PANE` entirely and always splits the session's ACTIVE pane. Both defaults track the pane
|
|
36
|
+
* the *user* is looking at, which is only coincidentally the caller's — they agree whenever a
|
|
37
|
+
* human is typing and diverge exactly when a program is driving. Naming the pane is the only way
|
|
38
|
+
* `pane:right` means the same thing on both backends.
|
|
39
|
+
*/
|
|
40
|
+
from?: MuxTarget | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* The workspace a `tab` placement opens INSIDE — a backend workspace id, exactly the value
|
|
43
|
+
* `OpenedPane.workspace` reports. Ignored by `pane:*` (a split lands in its pane's own space) and
|
|
44
|
+
* by `workspace` (which creates the space it opens in).
|
|
45
|
+
*
|
|
46
|
+
* This is `from`'s counterpart one tier up, and it exists for the same reason: omitting it does
|
|
47
|
+
* NOT mean "the caller's workspace", it means "whatever this backend defaults to", and every
|
|
48
|
+
* backend defaults to the workspace the USER is looking at (herdr's `tab create` without
|
|
49
|
+
* `--workspace`, WezTerm's `spawn` without `--window-id`). That default is only coincidentally the
|
|
50
|
+
* caller's, and it diverges exactly when a program is driving — a walk that opens a workspace and
|
|
51
|
+
* then fills it with tabs must name the workspace it just opened, or every tab after the first
|
|
52
|
+
* lands beside the pane the command was RUN from.
|
|
53
|
+
*
|
|
54
|
+
* Absent on a backend with no workspace tier (tmux, where `workspace` and `tab` both collapse onto
|
|
55
|
+
* a Window): an adapter with nothing to resolve it against ignores it, which still satisfies the
|
|
56
|
+
* contract — there is no second space for a tab to land in the wrong one of.
|
|
57
|
+
*/
|
|
58
|
+
within?: string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Fraction of the split region kept by `first` — the ORIGINAL pane, not the new one. Only
|
|
61
|
+
* meaningful for a `pane:*` placement; `0 < ratio < 1`, and omitting it takes the backend's own
|
|
62
|
+
* even (50/50) default.
|
|
63
|
+
*
|
|
64
|
+
* The range is a PRECONDITION the seam enforces, not a hint: a sizing backend rejects a ratio
|
|
65
|
+
* outside `0 < ratio < 1` (it would render a negative or whole-region split) rather than pass it
|
|
66
|
+
* through — see `assertRatioInRange` (`ratio.ts`). A backend that cannot size a split renders no
|
|
67
|
+
* ratio and so never checks one; callers degrade to the even default there. `template`'s schema
|
|
68
|
+
* refuses a degenerate ratio earlier, per node, so the two layers do different jobs.
|
|
69
|
+
*
|
|
70
|
+
* The sign convention is the trap, and the two real backends convert in OPPOSITE directions:
|
|
71
|
+
* herdr's `--ratio` sizes the original pane, so it is exactly this value and passes through
|
|
72
|
+
* unconverted; tmux's `-l` sizes the NEW pane, so it takes `1 - ratio`. Applying the inversion to
|
|
73
|
+
* both, or to neither, is the single most likely way to get a split backwards.
|
|
74
|
+
*/
|
|
75
|
+
ratio?: number | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Environment variables set at the birth of whatever tier `at` opens — NOT just a split. Native
|
|
78
|
+
* on both real backends, which take a repeatable flag on every space-creating command (herdr
|
|
79
|
+
* `--env KEY=VALUE` on `workspace create`/`tab create`/`pane split`, tmux `-e KEY=VALUE` on
|
|
80
|
+
* `new-window`/`split-window`), one per variable.
|
|
81
|
+
*
|
|
82
|
+
* That breadth is load-bearing, not incidental: a pane pool's root pane is born by the region
|
|
83
|
+
* open and never by a split, so scoping env to `pane:*` would drop it silently exactly where a
|
|
84
|
+
* caller needs it. Valid with or without `launch` — a pane with env and no command is a blank
|
|
85
|
+
* shell with that env set.
|
|
86
|
+
*
|
|
87
|
+
* The one exception among herdr's own routes is its WORKTREE one: `worktree create`/`worktree
|
|
88
|
+
* open` take no env param (0.7.4 answers `--env` with `unknown option`), so env is dropped there
|
|
89
|
+
* rather than failing the checkout — see `WorktreeWorkspaceCapability`. WezTerm has no `--env` on
|
|
90
|
+
* ANY route at all (`session.wezterm.ts`), so every one of its opens takes this same fallback path
|
|
91
|
+
* rather than just the one.
|
|
92
|
+
*/
|
|
93
|
+
env?: Record<string, string> | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Name for the space this opens, at whatever tier `at` opens it — every backend can name every
|
|
96
|
+
* tier, so this is host-neutral: on herdr a workspace/tab/pane label, on tmux a window name
|
|
97
|
+
* (`workspace` and `tab` both collapse to a Window there) or a pane title. Omit for the backend's
|
|
98
|
+
* own default.
|
|
99
|
+
*/
|
|
100
|
+
label?: string | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* An OPAQUE id grouping the spaces one caller opens, for a backend with no workspace tier to group
|
|
103
|
+
* them in. A caller opening several tabs as one workspace needs them recognizable as a group
|
|
104
|
+
* afterwards; where a real Workspace tier exists the tier IS the group, so this is ignored (herdr
|
|
105
|
+
* already stamps every pane and tab record with its `workspace_id` — a second grouping would
|
|
106
|
+
* duplicate a fact the backend never reads). Where there is none, the adapter stores it in the
|
|
107
|
+
* backend's own native mechanism (tmux: a window option it can filter on server-side, surviving a
|
|
108
|
+
* window rename).
|
|
109
|
+
*
|
|
110
|
+
* Opaque means opaque: an adapter stores and forwards the value and never parses, splits, or
|
|
111
|
+
* derives it. It is deliberately NOT the `label`, and that separation is the whole point — a label
|
|
112
|
+
* is chosen by a human and may contain anything, so recovering a grouping by parsing one is
|
|
113
|
+
* unsound (`acme - beta - main` reads as group `acme` with tab `beta - main` exactly as well as
|
|
114
|
+
* group `acme - beta` with tab `main`). The label is what a human reads; this is what a machine
|
|
115
|
+
* reads.
|
|
116
|
+
*
|
|
117
|
+
* NEW OPTIONAL member: an adapter that ignores it still satisfies the contract. Omit it and
|
|
118
|
+
* nothing is grouped — no adapter invents one, and a space nobody grouped stays ungrouped.
|
|
119
|
+
*
|
|
120
|
+
* A group id is NOT a workspace: `open` still reports `OpenedPane.workspace` absent on a backend
|
|
121
|
+
* with no workspace tier, tag or no tag. A tag cyber-mux wrote is its own bookkeeping, not a tier
|
|
122
|
+
* the backend gained.
|
|
123
|
+
*
|
|
124
|
+
* This option is a CONVENIENCE over `group`, never a second implementation of it: every adapter
|
|
125
|
+
* routes it through that member rather than spelling the grouping twice, exactly as `open`'s
|
|
126
|
+
* pane-`label` routes through `rename`. It costs nothing to route — tmux has no birth flag for a
|
|
127
|
+
* window option, so the grouping was always a second call after the space exists. A caller that
|
|
128
|
+
* did not open the space calls `group` directly; this option is only the shorthand for a caller
|
|
129
|
+
* that did.
|
|
130
|
+
*/
|
|
131
|
+
workspaceGroup?: string | undefined;
|
|
132
|
+
}
|
|
133
|
+
/** Opaque handle to an open pane/window/session; backend-specific id lives in `id`. */
|
|
134
|
+
interface MuxTarget {
|
|
135
|
+
id: string;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A pane `open` just created: its handle, plus the workspace it landed in.
|
|
139
|
+
*
|
|
140
|
+
* `workspace` is OCCUPANCY — which workspace the new pane LIVES IN — and it is deliberately not the
|
|
141
|
+
* worktree binding. A worktree opened at a `pane:right` placement lives in the caller's workspace
|
|
142
|
+
* while being bound to none: the pane has a workspace, the worktree is still ungrouped. The two are
|
|
143
|
+
* reported by separate outputs and neither answers for the other, so a caller must never read this
|
|
144
|
+
* as evidence that a worktree was grouped — that fact is `WorktreeWorkspaceCapability`'s alone.
|
|
145
|
+
*
|
|
146
|
+
* Widened from a bare `MuxTarget` because `open` returning only a pane id left nothing
|
|
147
|
+
* downstream able to report a workspace: the template manifest is framed as the complete
|
|
148
|
+
* machine-readable answer to "which panes exist and what are they for", and a consumer grouping
|
|
149
|
+
* panes by workspace had nothing to group on.
|
|
150
|
+
*/
|
|
151
|
+
interface OpenedPane extends MuxTarget {
|
|
152
|
+
/**
|
|
153
|
+
* The tab the new pane landed in — a tab id, addressable by `rename(exec, { id: tab }, 'tab', …)`.
|
|
154
|
+
*
|
|
155
|
+
* REQUIRED, and the contrast with `workspace` below is the whole point: only SOME multiplexers
|
|
156
|
+
* have a Workspace level, so that field is absent where the tier is; EVERY multiplexer has the Tab
|
|
157
|
+
* level, so every backend answers this and none reports it absent. tmux's Tab is its Window, which
|
|
158
|
+
* is also why `workspace` and `tab` placements both collapse onto `new-window` there — a tmux open
|
|
159
|
+
* has no workspace to report and always has a window.
|
|
160
|
+
*
|
|
161
|
+
* Per route: a new tab reports itself, a created workspace reports its ROOT tab, and a split
|
|
162
|
+
* reports the tab it landed in — the caller's own, since a split opens no tab of its own.
|
|
163
|
+
*
|
|
164
|
+
* This is what makes naming a new workspace's root tab portable, and it is not a convenience: a
|
|
165
|
+
* caller reaching for `id` (the pane) instead would be green on tmux, which resolves a pane id in
|
|
166
|
+
* a window target and succeeds, and silently broken on herdr, which refuses it outright
|
|
167
|
+
* (`tab_not_found`) — and since a failed command's output is discarded, the root tab would just
|
|
168
|
+
* stay named `1` with nothing raised.
|
|
169
|
+
*
|
|
170
|
+
* Costs no extra call on either backend — the same argument `workspace` is already reported on:
|
|
171
|
+
* the backend answered when the pane was opened (herdr carries `tab_id` in the create envelope;
|
|
172
|
+
* tmux reports `#{window_id}` from the same `-F` the pane id rides out on), so a surface that hid
|
|
173
|
+
* it would be discarding a fact it already held.
|
|
174
|
+
*/
|
|
175
|
+
tab: string;
|
|
176
|
+
/**
|
|
177
|
+
* The workspace the new pane landed in; `undefined` when the backend has no workspace tier —
|
|
178
|
+
* ABSENT rather than a false "none", the same convention `isPaneFocused`'s `undefined` follows.
|
|
179
|
+
* tmux, where `workspace` and `tab` both collapse to a Window, has nothing to report here, which
|
|
180
|
+
* is not the same as reporting that nothing is there.
|
|
181
|
+
*/
|
|
182
|
+
workspace?: string | undefined;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The lifecycle state a per-pane agent-state feed reports — herdr's native `agent_status`
|
|
186
|
+
* (`herdr` 0.7.5). `unknown` is herdr's OWN value for a pane whose agent it cannot classify, NOT a
|
|
187
|
+
* cyber-mux stand-in for "no feed": a backend with no feed at all reports no `agentStatus` field on
|
|
188
|
+
* `LivePane` rather than `'unknown'`, the same absent-not-false convention `harness` and `label`
|
|
189
|
+
* follow.
|
|
190
|
+
*/
|
|
191
|
+
type AgentStatus = "idle" | "working" | "blocked" | "done" | "unknown";
|
|
192
|
+
/** A pane the backend can currently see, as reported by `listPanes` (bulk enumeration). */
|
|
193
|
+
interface LivePane {
|
|
194
|
+
/** Backend-native pane id. */
|
|
195
|
+
id: string;
|
|
196
|
+
/** Which multiplexer this pane belongs to. */
|
|
197
|
+
mux: "tmux" | "herdr" | "wezterm" | "zellij";
|
|
198
|
+
/** The harness running in this pane, when the backend can report it (herdr only). */
|
|
199
|
+
harness?: string | undefined;
|
|
200
|
+
/** The pane's working directory, when the backend reports it. */
|
|
201
|
+
cwd?: string | undefined;
|
|
202
|
+
/**
|
|
203
|
+
* The human name a person gave this pane, when there is one — what lets a caller address the pane
|
|
204
|
+
* by name instead of by id.
|
|
205
|
+
*
|
|
206
|
+
* **Absent means nobody named it**, and that is the whole point of the field being optional. A
|
|
207
|
+
* backend never invents one: herdr omits the key until `pane rename`, and tmux — which has no
|
|
208
|
+
* unset title and defaults `pane_title` to the hostname — reports a label only for a title that
|
|
209
|
+
* differs from the host (`paneLabel` in `session.tmux.ts` carries the rule). Exporting tmux's
|
|
210
|
+
* default would put the same label on every pane in the session, and that name would then resolve
|
|
211
|
+
* to all of them: ambiguity manufactured out of a name nobody chose.
|
|
212
|
+
*
|
|
213
|
+
* **A name, not a key.** Neither backend requires one unique, so duplicates are ordinary and are
|
|
214
|
+
* resolved where the caller is — at lookup — rather than refused at authoring time.
|
|
215
|
+
*/
|
|
216
|
+
label?: string | undefined;
|
|
217
|
+
/**
|
|
218
|
+
* The pane's agent-lifecycle state, when the backend has a per-pane agent-state feed (herdr only —
|
|
219
|
+
* herdr 0.7.5's `agent_status`).
|
|
220
|
+
*
|
|
221
|
+
* **Absent means the backend has no agent-state feed**, exactly as `harness` means "the backend
|
|
222
|
+
* cannot report a harness" — NOT `'unknown'`, which is herdr's own value for a pane whose agent it
|
|
223
|
+
* genuinely cannot classify. tmux, wezterm and zellij carry no such feed at all, so this field is
|
|
224
|
+
* simply not present on their panes; a caller must never read its absence as `'unknown'`. The
|
|
225
|
+
* blocking wait built on this feed is the separate, herdr-only `AgentLifecycle` capability.
|
|
226
|
+
*/
|
|
227
|
+
agentStatus?: AgentStatus | undefined;
|
|
228
|
+
}
|
|
229
|
+
interface MuxReadOptions {
|
|
230
|
+
/** How many trailing lines of output to capture; omit for the backend's default. */
|
|
231
|
+
lines?: number | undefined;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* What `waitForOutput` waits FOR, and how long it is willing to wait.
|
|
235
|
+
*
|
|
236
|
+
* **Exactly one of `match`/`regex`.** Neither leaves nothing to wait for; both would need a rule for
|
|
237
|
+
* combining them that no caller asked for. The seam rejects both cases rather than picking a winner
|
|
238
|
+
* (`assertWaitPattern`, `wait-output.ts`), so the refusal reads the same on every backend — herdr's own
|
|
239
|
+
* CLI already refuses the same pair, and a polling backend has no CLI to refuse it for us.
|
|
240
|
+
*/
|
|
241
|
+
interface MuxWaitOptions {
|
|
242
|
+
/** A LITERAL substring to wait for — the portable form: no dialect, so it means the same on every
|
|
243
|
+
* backend. Mutually exclusive with `regex`. */
|
|
244
|
+
match?: string | undefined;
|
|
245
|
+
/**
|
|
246
|
+
* A regular expression SOURCE to wait for (no delimiters, no flags). Mutually exclusive with `match`.
|
|
247
|
+
*
|
|
248
|
+
* **The dialect is the backend's own**, and that is the one place this primitive is not fully
|
|
249
|
+
* portable: herdr matches with Rust's `regex` crate (no backreferences, no lookaround), the polling
|
|
250
|
+
* backends with ECMAScript's `RegExp`. The portable subset is what both accept — character classes,
|
|
251
|
+
* quantifiers, alternation, anchors. The seam compiles the source as a `RegExp` up front on EVERY
|
|
252
|
+
* backend, so a *malformed* pattern is refused identically everywhere instead of only where a real
|
|
253
|
+
* poll happens to run it; a pattern that is merely *dialect-specific* compiles here and is then
|
|
254
|
+
* herdr's to accept or refuse. Reach for `match` whenever a literal will do.
|
|
255
|
+
*/
|
|
256
|
+
regex?: string | undefined;
|
|
257
|
+
/**
|
|
258
|
+
* Give up after this many ms. REQUIRED, deliberately: herdr's native wait blocks FOREVER without
|
|
259
|
+
* `--timeout` and a poll loop with no deadline spins forever, so an omitted bound is the one
|
|
260
|
+
* spelling whose failure mode is an agent that never returns. Making the caller name it is the whole
|
|
261
|
+
* difference between a wait and a hang.
|
|
262
|
+
*/
|
|
263
|
+
timeoutMs: number;
|
|
264
|
+
/** Restrict the searched snapshot to this many trailing lines — `MuxReadOptions.lines`, applied to
|
|
265
|
+
* the snapshot the match runs against. Omit to search the backend's default read. */
|
|
266
|
+
lines?: number | undefined;
|
|
267
|
+
/** How long a POLLING backend sleeps between reads; ignored by a backend with a native wait, which
|
|
268
|
+
* has its own cadence. Defaults to `DEFAULT_WAIT_POLL_MS`. */
|
|
269
|
+
pollMs?: number | undefined;
|
|
270
|
+
/** Test seam: the sleep between polls. Injected exactly as `NudgeOptions.sleep` is, and for the same
|
|
271
|
+
* reason — a real wall-clock wait in a test buys nothing but seconds. */
|
|
272
|
+
sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
273
|
+
/** Test seam: the clock the deadline is measured against; defaults to `Date.now`. Paired with
|
|
274
|
+
* `sleep` so an injected sleep can advance an injected clock and a timeout is reached deterministically. */
|
|
275
|
+
now?: (() => number) | undefined;
|
|
276
|
+
}
|
|
277
|
+
/** What `waitForOutput` answers with. */
|
|
278
|
+
interface MuxWaitResult {
|
|
279
|
+
/** `true` = the pattern was seen, `false` = the timeout elapsed without it. A pane that is GONE is
|
|
280
|
+
* neither: it throws, because reporting a dead pane as a timeout would bury the real cause behind a
|
|
281
|
+
* shape the caller reads as "still working" (the same rule `nudge` opens with). */
|
|
282
|
+
matched: boolean;
|
|
283
|
+
/** The snapshot searched when the wait ended — the matching read on success, the last read before
|
|
284
|
+
* the deadline on a timeout. Always the caller's evidence for the verdict, never a second read. */
|
|
285
|
+
output: string;
|
|
286
|
+
/** The first line of `output` that matches, when the match sits on ONE line. Absent when the match
|
|
287
|
+
* spans lines (nothing single-line to point at) or when a native backend reports none. */
|
|
288
|
+
matchedLine?: string | undefined;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* A pane's rectangle, in whatever coordinate space the backend measures its region in. Only the
|
|
292
|
+
* rects' relationship to EACH OTHER is meaningful — the origin is not comparable across backends
|
|
293
|
+
* (tmux reports window-relative, so a region starts at 0,0; herdr reports screen-absolute, so the
|
|
294
|
+
* same region starts wherever its workspace sits). Every consumer works off the panes' bounding box
|
|
295
|
+
* rather than an assumed origin, which is what makes the two reports interchangeable.
|
|
296
|
+
*/
|
|
297
|
+
interface PaneRect {
|
|
298
|
+
x: number;
|
|
299
|
+
y: number;
|
|
300
|
+
/** In cells. Excludes the divider between this pane and the next, where the backend draws one. */
|
|
301
|
+
width: number;
|
|
302
|
+
height: number;
|
|
303
|
+
}
|
|
304
|
+
/** One pane of a region, as `describeRegion` reports it. */
|
|
305
|
+
interface RegionPane {
|
|
306
|
+
id: string;
|
|
307
|
+
rect: PaneRect;
|
|
308
|
+
/** The pane's working directory. */
|
|
309
|
+
cwd?: string | undefined;
|
|
310
|
+
/** The pane's label, when it has one the AUTHOR set — see `describeRegion` on the tmux caveat. */
|
|
311
|
+
label?: string | undefined;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* One tab of a workspace, as `describeWorkspace` reports it — a tab's identity, its own name, and the
|
|
315
|
+
* region inside it.
|
|
316
|
+
*
|
|
317
|
+
* `panes` is exactly what `describeRegion` reports for that tab, and deliberately so: a tab IS a
|
|
318
|
+
* region, so a workspace-wide read is the region read repeated rather than a second geometry
|
|
319
|
+
* vocabulary. Everything `RegionPane` documents — the rects' incomparable origin, `label` being the
|
|
320
|
+
* author's or absent — holds here unchanged.
|
|
321
|
+
*/
|
|
322
|
+
interface WorkspaceTab {
|
|
323
|
+
/** Backend-native tab id (herdr `tab_id`; tmux `#{window_id}`, its Tab being its Window). */
|
|
324
|
+
id: string;
|
|
325
|
+
/**
|
|
326
|
+
* The tab's OWN name, when the backend reports one — the name a caller gave the tab, never the
|
|
327
|
+
* display name composed out of it.
|
|
328
|
+
*
|
|
329
|
+
* Where the two differ they are stored separately and this reports the stored original: on a
|
|
330
|
+
* backend with no workspace tier the display name is the composed `<workspace> - <tab>`, so `group`
|
|
331
|
+
* stored `editor` beside the tag and the read takes it from THERE. Never split back out of the
|
|
332
|
+
* display name — `acme - beta - main` is ambiguous under every split rule, which is the whole
|
|
333
|
+
* reason the option exists — and never taken from the display name verbatim, which would compound
|
|
334
|
+
* the prefix on every round trip (`pool - pool - editor`). Capture is the inverse of apply or it is
|
|
335
|
+
* a lie about the user's screen.
|
|
336
|
+
*
|
|
337
|
+
* Where no own name was stored, the backend's own name for the space stands: nobody composed it, so
|
|
338
|
+
* it already IS the tab's own name. A backend whose label is never composed (herdr) reports that
|
|
339
|
+
* label unchanged and stores nothing.
|
|
340
|
+
*/
|
|
341
|
+
label?: string | undefined;
|
|
342
|
+
/** Every pane in this tab, with its rectangle — `describeRegion`'s answer for this tab. */
|
|
343
|
+
panes: RegionPane[];
|
|
344
|
+
}
|
|
345
|
+
interface CreateWorktreeWorkspaceOptions {
|
|
346
|
+
/** The primary checkout's root — the repo the new worktree branches from. */
|
|
347
|
+
primaryRoot: string;
|
|
348
|
+
/** Branch to create the worktree on. */
|
|
349
|
+
branch: string;
|
|
350
|
+
/** Where the new worktree should be checked out. */
|
|
351
|
+
path: string;
|
|
352
|
+
/** Start point for the new branch; omit for the backend's own default (the current HEAD). */
|
|
353
|
+
base?: string | undefined;
|
|
354
|
+
/** Command line to launch inside the new workspace's root pane; omit for a blank pane. */
|
|
355
|
+
launch?: string | undefined;
|
|
356
|
+
/** Environment variables set in the new workspace's root pane at birth. */
|
|
357
|
+
env?: Record<string, string> | undefined;
|
|
358
|
+
/** Name for the bound workspace; omit for the backend's own default. */
|
|
359
|
+
label?: string | undefined;
|
|
360
|
+
}
|
|
361
|
+
interface OpenWorktreeWorkspaceOptions {
|
|
362
|
+
/** The primary checkout's root — the repo the worktree belongs to. */
|
|
363
|
+
primaryRoot: string;
|
|
364
|
+
/** An EXISTING worktree's checkout path. */
|
|
365
|
+
path: string;
|
|
366
|
+
/** Command line to launch inside the new workspace's root pane; omit for a blank pane. */
|
|
367
|
+
launch?: string | undefined;
|
|
368
|
+
/** Environment variables set in the new workspace's root pane at birth. */
|
|
369
|
+
env?: Record<string, string> | undefined;
|
|
370
|
+
/** Name for the bound workspace; omit for the backend's own default. */
|
|
371
|
+
label?: string | undefined;
|
|
372
|
+
}
|
|
373
|
+
/** A worktree open in a workspace bound to it — the capability's whole product. */
|
|
374
|
+
interface WorktreeWorkspace {
|
|
375
|
+
/**
|
|
376
|
+
* The workspace's root pane, and the ROOT TAB it sits in — an `OpenedPane` rather than a bare pane
|
|
377
|
+
* handle for the reason that field is required everywhere else: every multiplexer has the Tab
|
|
378
|
+
* level, and this route already holds the answer (herdr's worktree envelope carries `tab_id` in the
|
|
379
|
+
* same `root_pane` record `workspace create` reports it in). A caller handed only the pane could
|
|
380
|
+
* not address the region's tab — it could not group it, and it could not rename it — and reaching
|
|
381
|
+
* for `id` instead would be green on tmux (which resolves a pane id in a window target) and
|
|
382
|
+
* silently broken on herdr (`tab_not_found`, discarded). Reporting it costs nothing; hiding it
|
|
383
|
+
* would discard a fact this route already held.
|
|
384
|
+
*/
|
|
385
|
+
target: OpenedPane;
|
|
386
|
+
worktree: Worktree;
|
|
387
|
+
/** The backend workspace now bound to the worktree. */
|
|
388
|
+
workspace: string;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The optional capability a backend implements when it binds a git worktree to a workspace as a
|
|
392
|
+
* FIRST-CLASS RECORD — the binding a multiplexer's UI groups a repo's primary checkout and its
|
|
393
|
+
* worktrees by.
|
|
394
|
+
*
|
|
395
|
+
* That binding — NOT "understands git worktrees" — is the all-or-nothing property, which is why
|
|
396
|
+
* these members ship as one object rather than as separate optional methods. Established
|
|
397
|
+
* empirically against herdr: `git worktree add` followed by `workspace create --cwd <checkout>`
|
|
398
|
+
* yields a workspace with no worktree record at all — herdr does not know it is a worktree and
|
|
399
|
+
* leaves it out of the repo's group. Only routing through herdr's own `worktree create`/`open`
|
|
400
|
+
* produces the binding. tmux has no workspace tier and never binds, so it omits this entirely;
|
|
401
|
+
* callers fall back to plain git plus a placement-appropriate `open()`.
|
|
402
|
+
*
|
|
403
|
+
* Two things this deliberately does NOT own:
|
|
404
|
+
*
|
|
405
|
+
* - **The worktree facts.** Path, branch, linked, prunable are git's, read from git on every
|
|
406
|
+
* backend (`listWorktreesFromGit`). A backend that also enumerates worktrees is only re-reading
|
|
407
|
+
* git; letting it answer would let two backends disagree about the same worktree's branch. The
|
|
408
|
+
* backend contributes `bindings` alone — the one fact git cannot know.
|
|
409
|
+
* - **Removal.** herdr's `worktree remove` takes a workspace id, so it cannot even address an
|
|
410
|
+
* unbound worktree, and whether it dirty-checks is unknown — delegating would make a destructive
|
|
411
|
+
* operation's safety depend on whether a workspace happened to be open. Removal is always
|
|
412
|
+
* cyber-mux's own gates plus `git worktree remove`; a backend only releases its binding.
|
|
413
|
+
*
|
|
414
|
+
* NOTE: every member here OPENS a workspace — herdr has no "create a worktree without a workspace"
|
|
415
|
+
* primitive. This is never the route for a bare worktree add; that is always plain git.
|
|
416
|
+
*/
|
|
417
|
+
interface WorktreeWorkspaceCapability {
|
|
418
|
+
/**
|
|
419
|
+
* Create a worktree AND open it in a workspace bound to it, in one call (herdr `worktree
|
|
420
|
+
* create`). Always makes a workspace, so it cannot serve a pane or tab placement.
|
|
421
|
+
*/
|
|
422
|
+
createInWorkspace(exec: Exec, opts: CreateWorktreeWorkspaceOptions): WorktreeWorkspace;
|
|
423
|
+
/**
|
|
424
|
+
* Open an EXISTING worktree in a workspace bound to it (herdr `worktree open`) — the remedy that
|
|
425
|
+
* groups a worktree plain git created earlier.
|
|
426
|
+
*/
|
|
427
|
+
openInWorkspace(exec: Exec, opts: OpenWorktreeWorkspaceOptions): WorktreeWorkspace;
|
|
428
|
+
/**
|
|
429
|
+
* Which workspace each of the repo's worktrees is currently open in, keyed by normalized checkout
|
|
430
|
+
* path. A worktree with nothing open on it is absent from the map. The only fact git cannot answer.
|
|
431
|
+
*/
|
|
432
|
+
bindings(exec: Exec, opts: {
|
|
433
|
+
primaryRoot: string;
|
|
434
|
+
}): Map<string, string>;
|
|
435
|
+
/**
|
|
436
|
+
* Close the workspace, releasing the binding WITHOUT touching the checkout on disk — the worktree
|
|
437
|
+
* is left for `git worktree remove` to take under cyber-mux's own gates.
|
|
438
|
+
*/
|
|
439
|
+
releaseWorkspace(exec: Exec, workspace: string): void;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* The optional capability a backend implements when its pane listing reports pane GEOMETRY —
|
|
443
|
+
* position, not merely size. That one fact is what both members are derived from, and the single
|
|
444
|
+
* all-or-nothing precondition that bundles them into ONE object rather than two separate optional
|
|
445
|
+
* methods (mirroring `WorktreeWorkspaceCapability`): a backend either reports pane rects or it does
|
|
446
|
+
* not, and neither read is possible without them. tmux (`#{window_layout}`) and herdr (`pane
|
|
447
|
+
* layout`'s rects) both report position, so both ship this; WezTerm's `list` reports a pane's size
|
|
448
|
+
* but no position — nothing to build a `PaneRect` from — so it omits this entirely. A caller that
|
|
449
|
+
* finds this absent refuses (`template save` exits naming the backend) rather than guessing a tree.
|
|
450
|
+
*/
|
|
451
|
+
interface RegionInspector {
|
|
452
|
+
/**
|
|
453
|
+
* Report the geometry of the region (tab/window) the target pane sits in — every pane in it, with
|
|
454
|
+
* its rectangle. `template save` runs this backwards into a template.
|
|
455
|
+
*
|
|
456
|
+
* **Rects, not a tree, and that is the whole design of this verb.** Both backends can answer
|
|
457
|
+
* "what does this region look like", and both answer in a DIFFERENT structure: tmux hands back a
|
|
458
|
+
* nested tree encoded in a string (`#{window_layout}` — `83ae,200x50,0,0{133x50,0,0[...],...}`,
|
|
459
|
+
* where `{}` is a side-by-side split and `[]` a stacked one), while herdr hands back a FLAT
|
|
460
|
+
* `splits[]` array whose parent/child links exist only inside an undocumented id convention
|
|
461
|
+
* (`split_1_0` meaning "split 1, child of split 0" — inferred from the shape, never specified).
|
|
462
|
+
* Neither structure survives being made portable: one needs a bespoke parser for a string format
|
|
463
|
+
* tmux does not promise to keep, and the other needs cyber-mux to bet on herdr's id spelling.
|
|
464
|
+
*
|
|
465
|
+
* Rects are the fact both report exactly and neither can spell differently. The tree is then
|
|
466
|
+
* *derived* from them by recursive guillotine cuts (`template-capture.ts`), which is sound because a
|
|
467
|
+
* multiplexer region is built by splitting and therefore always guillotine-cuttable. That buys
|
|
468
|
+
* two things: the tricky half — n-ary rows, ratios, ambiguous grids — is a PURE function testable
|
|
469
|
+
* with no multiplexer at all, and a third backend owes this verb four numbers per pane rather
|
|
470
|
+
* than a tree in its own dialect.
|
|
471
|
+
*
|
|
472
|
+
* **`label` is the author's, or absent.** Only a label someone deliberately set is reported —
|
|
473
|
+
* herdr omits the field entirely until `pane rename`, and tmux defaults `pane_title` to the
|
|
474
|
+
* HOSTNAME, so the tmux adapter drops a title equal to `#{host}` rather than exporting `zeta` as
|
|
475
|
+
* every pane's name.
|
|
476
|
+
*
|
|
477
|
+
* Throws rather than returning empty when the region cannot be read: an export built from a
|
|
478
|
+
* region the backend could not describe would be a confident lie about the user's screen.
|
|
479
|
+
*/
|
|
480
|
+
describeRegion(exec: Exec, target: MuxTarget): RegionPane[];
|
|
481
|
+
/**
|
|
482
|
+
* Report every tab of the workspace the target pane sits in, each with its own region's geometry —
|
|
483
|
+
* the workspace-wide read beside `describeRegion`'s one-region read. `template save --workspace` runs
|
|
484
|
+
* this backwards into a `tabs` template, and it is the exact inverse of the tabs walk.
|
|
485
|
+
*
|
|
486
|
+
* `save`'s default subject is unaffected by this member: a bare `save` reads `describeRegion` and
|
|
487
|
+
* captures one region.
|
|
488
|
+
*
|
|
489
|
+
* **The grouping is read from the tag, never off the label.** On a backend with a real workspace
|
|
490
|
+
* tier the tier IS the answer (herdr: the caller's `workspace_id`, whose tabs and panes the backend
|
|
491
|
+
* already stamps). On one without, the workspace is not a fact the backend holds at all, so the
|
|
492
|
+
* read is *"which spaces carry this group id"* — the tag `MuxOpenOptions.workspaceGroup` wrote,
|
|
493
|
+
* which is opaque and survives a rename. Parsing `<workspace> - <tab>` back apart is unsound
|
|
494
|
+
* (`acme - beta - main` splits two ways, both legal), which is the whole reason the tag exists.
|
|
495
|
+
*
|
|
496
|
+
* **A space carrying no tag is a workspace of ONE.** That is the honest answer for a space nobody
|
|
497
|
+
* grouped, not an error and not an empty list: the caller's own region is a workspace of one tab.
|
|
498
|
+
*
|
|
499
|
+
* Throws rather than returning empty when the workspace cannot be read, matching `describeRegion`:
|
|
500
|
+
* a template built from a workspace the backend could not describe would be a confident lie about
|
|
501
|
+
* the user's screen.
|
|
502
|
+
*/
|
|
503
|
+
describeWorkspace(exec: Exec, target: MuxTarget): WorkspaceTab[];
|
|
504
|
+
}
|
|
505
|
+
/** What a `waitForState` blocks on: the states that end the wait, and how long it may run. */
|
|
506
|
+
interface AgentWaitOptions {
|
|
507
|
+
/**
|
|
508
|
+
* The agent states any one of which ends the wait. Omit to let the backend apply its OWN default
|
|
509
|
+
* (herdr's is `idle|done|blocked`) — cyber-mux never restates that default in the command it runs,
|
|
510
|
+
* so a future change to herdr's default is not silently pinned by this binding.
|
|
511
|
+
*/
|
|
512
|
+
until?: AgentStatus[] | undefined;
|
|
513
|
+
/** Milliseconds before the wait gives up; omit for an indefinite wait (the backend's own default). */
|
|
514
|
+
timeoutMs?: number | undefined;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* The optional capability a backend implements when it has a NATIVE, blocking per-pane agent-state
|
|
518
|
+
* wait (herdr's `agent wait`, built on the 0.7.5 `agent_status` feed) — present ONLY on a backend with
|
|
519
|
+
* that primitive, absent on every backend without one.
|
|
520
|
+
*
|
|
521
|
+
* Absent rather than a degraded emulation, the same all-or-nothing convention `worktree` and `regions`
|
|
522
|
+
* follow: a lookalike wait built from `read()` polling would silently disagree with herdr's own state
|
|
523
|
+
* derivation on the same question, so a backend that lacks the primitive is not present here in a weak
|
|
524
|
+
* form — it is not present at all, and the orchestrator (`deriveAgentWait`, `agent.ts`) refuses rather
|
|
525
|
+
* than guesses. `waitForState` never sees the adapter: it only ever runs against the one backend that
|
|
526
|
+
* has it, which is why the emulate-or-refuse decision lives one level up.
|
|
527
|
+
*/
|
|
528
|
+
interface AgentLifecycle {
|
|
529
|
+
/**
|
|
530
|
+
* Block until the target pane's agent reaches one of `opts.until` (or the backend's own default set
|
|
531
|
+
* when omitted), or `opts.timeoutMs` elapses. Returns the `AgentStatus` actually reached, so the
|
|
532
|
+
* caller learns WHICH requested state (or the timeout) ended the wait, not merely that it ended.
|
|
533
|
+
*/
|
|
534
|
+
waitForState(exec: Exec, target: MuxTarget, opts: AgentWaitOptions): AgentStatus;
|
|
535
|
+
}
|
|
536
|
+
interface MuxAdapter {
|
|
537
|
+
/** Backend name, e.g. "tmux" or "herdr". */
|
|
538
|
+
readonly name: string;
|
|
539
|
+
/**
|
|
540
|
+
* Create a new pane/window in `opts.cwd`, running `opts.launch` if given; returns its handle plus
|
|
541
|
+
* the workspace it landed in (absent on a backend with no workspace tier — see `OpenedPane`).
|
|
542
|
+
*/
|
|
543
|
+
open(exec: Exec, opts: MuxOpenOptions): OpenedPane;
|
|
544
|
+
/**
|
|
545
|
+
* Name an ALREADY-OPEN space at `tier`, addressed by that tier's own id (`target.id` is a tab id
|
|
546
|
+
* for `'tab'`, a pane id for `'pane'`).
|
|
547
|
+
*
|
|
548
|
+
* This is the naming route for the one case birth cannot serve, NOT a second way to do what
|
|
549
|
+
* `MuxOpenOptions.label` does: `label` names a space at birth wherever the backend's CLI
|
|
550
|
+
* allows, and exactly one tier does not allow it — herdr labels a new workspace's ROOT TAB `1` and
|
|
551
|
+
* offers no flag to change it, only `tab rename` afterwards. Every later tab takes `label` at birth
|
|
552
|
+
* like any other space, so the whole cost of this member is one rename on herdr's first tab.
|
|
553
|
+
*
|
|
554
|
+
* REQUIRED rather than optional, unlike `regions`/`worktree`. Those are optional because a
|
|
555
|
+
* backend may genuinely lack the concept, leaving a caller something to do about it (refuse, or
|
|
556
|
+
* fall back to plain git). Naming has neither property: every backend names every tier — the same
|
|
557
|
+
* breadth `label` already relies on at birth (tmux a window name or a pane title, herdr a tab or a
|
|
558
|
+
* pane rename) — and a caller that found this missing could not degrade, because a rename is the
|
|
559
|
+
* ONLY way to name a root tab. An optional member here would buy a branch every caller must write
|
|
560
|
+
* and no caller could ever take. Declaring it required is the adapter author's debt instead, which
|
|
561
|
+
* is the honest place for it. (`canSizeSplits` is the other precedent, and the contrast holds: a
|
|
562
|
+
* ratio has a real degrade — the backend's own even default — so it is DECLARED; a name has none.)
|
|
563
|
+
*
|
|
564
|
+
* As read-only in its side effects as `isPaneFocused` is: it moves no focus and opens nothing.
|
|
565
|
+
* Naming a space is not visiting it.
|
|
566
|
+
*/
|
|
567
|
+
rename(exec: Exec, target: MuxTarget, tier: MuxSpaceTier, name: string): void;
|
|
568
|
+
/**
|
|
569
|
+
* Group an ALREADY-OPEN space into `group`, and store the space's own `name` beside it.
|
|
570
|
+
* `target.id` is a TAB id — the tier a workspace groups, which is why this takes no
|
|
571
|
+
* `MuxSpaceTier`: `rename` needs one because both its tiers are nameable and neither can be
|
|
572
|
+
* inferred, while grouping has exactly one meaningful tier. A `pane` is not a member of a
|
|
573
|
+
* workspace; the tab it sits in is. A tier parameter here would buy a branch every caller must
|
|
574
|
+
* write and no caller could ever take — the same argument `MuxSpaceTier` itself makes for
|
|
575
|
+
* having no `workspace` member.
|
|
576
|
+
*
|
|
577
|
+
* **`open` cannot be the only way in.** A caller that did not open the space still has to group it
|
|
578
|
+
* — `worktree add --template` has its region opened by the worktree verbs before the walk ever runs
|
|
579
|
+
* — and it holds that space's own id the moment the open returns. So this is its own member acting
|
|
580
|
+
* on an already-open space, exactly as `rename` does, and `MuxOpenOptions.workspaceGroup`
|
|
581
|
+
* ROUTES THROUGH it, so there is one spelling per backend rather than two that can drift. Routing
|
|
582
|
+
* costs no call: tmux has no birth flag for a window option, so grouping was ALREADY a second call
|
|
583
|
+
* after the window exists.
|
|
584
|
+
*
|
|
585
|
+
* **`name` is the space's OWN name, never its display name**, and storing it is not optional
|
|
586
|
+
* bookkeeping — it is the same rule the group id follows, one tier down. A backend with one name
|
|
587
|
+
* field per space (tmux) whose caller composes a display name out of the tab's name has DESTROYED
|
|
588
|
+
* the original: the field holds `pool - editor`, and `editor` is gone. Recovering it would mean
|
|
589
|
+
* splitting on a separator already proven ambiguous (`acme - beta - main` splits two legal ways),
|
|
590
|
+
* and taking the display name verbatim would re-prefix it on every round trip
|
|
591
|
+
* (`pool - pool - editor`). So the caller that composed the name stores the original here, and a
|
|
592
|
+
* reader takes it from there. The display name is a human's to read; an opaque option carries what
|
|
593
|
+
* a machine reads back. Omit it when the caller named nothing — there is no own name to store, and
|
|
594
|
+
* no adapter invents one.
|
|
595
|
+
*
|
|
596
|
+
* **A backend with a real workspace tier stores NEITHER**, and that is a complete answer rather
|
|
597
|
+
* than a stub: its tier IS the group (herdr stamps every pane and tab record with its
|
|
598
|
+
* `workspace_id`), and its tab label IS the tab's own name, never composed — so both are facts the
|
|
599
|
+
* backend already holds, and storing them again would duplicate what it never reads.
|
|
600
|
+
*
|
|
601
|
+
* REQUIRED, for `rename`'s reason: a caller finding this missing could not degrade, because there
|
|
602
|
+
* is no other way to group a space it did not open. As read-only in its side effects as `rename`
|
|
603
|
+
* is — it moves no focus and opens nothing.
|
|
604
|
+
*/
|
|
605
|
+
group(exec: Exec, target: MuxTarget, group: string, name?: string | undefined): void;
|
|
606
|
+
/**
|
|
607
|
+
* Whether this backend can size a split — i.e. whether it honors `MuxOpenOptions.ratio`. Both
|
|
608
|
+
* real backends can (herdr `--ratio`, tmux `-l`), so both declare it. Absent/`false` means a
|
|
609
|
+
* caller asking for a ratio gets the backend's own even default instead, which callers DEGRADE to
|
|
610
|
+
* (with one warning) rather than reject: the template schema is backend-agnostic, so a template's
|
|
611
|
+
* validity must never depend on which multiplexer happens to be running.
|
|
612
|
+
*/
|
|
613
|
+
readonly canSizeSplits?: boolean | undefined;
|
|
614
|
+
/**
|
|
615
|
+
* Present only on a backend that binds a git worktree to a workspace (herdr); `undefined` on one
|
|
616
|
+
* with no such concept (tmux), where callers fall back to plain git plus `open()`.
|
|
617
|
+
*/
|
|
618
|
+
readonly worktree?: WorktreeWorkspaceCapability | undefined;
|
|
619
|
+
/**
|
|
620
|
+
* Type `text` into the target as literal characters, pressing **no** Enter — the text is left
|
|
621
|
+
* staged in the pane's input box. Literal means literal: text that happens to name a key
|
|
622
|
+
* (`Enter`, `Up`) is typed as those characters, never interpreted as that key. That guarantee is
|
|
623
|
+
* why this is its own method rather than a mode of `sendKeys` — tmux's `send-keys` resolves an
|
|
624
|
+
* ambiguous token by *guessing* which was meant ("if the string is not recognised as a key, it is
|
|
625
|
+
* sent as a series of characters"), so only the explicit literal form is safe.
|
|
626
|
+
*/
|
|
627
|
+
sendText(exec: Exec, target: MuxTarget, text: string): void;
|
|
628
|
+
/**
|
|
629
|
+
* Press each named key in order, typing nothing. Keys are named in the portable core vocabulary —
|
|
630
|
+
* `Up` `Down` `Left` `Right` `Enter` `Escape` `Tab` `Space` `Backspace` `C-c` `F1`–`F12` — which
|
|
631
|
+
* each adapter maps onto whatever its backend calls them. A token *outside* the core is forwarded
|
|
632
|
+
* verbatim, reaching backend-specific keys at the cost of portability; whether it is honored or
|
|
633
|
+
* refused is then the backend's own answer, and the two differ (herdr refuses an unknown key;
|
|
634
|
+
* tmux cannot refuse one and types it instead).
|
|
635
|
+
*
|
|
636
|
+
* `Enter` is a key like any other here: `sendKeys(exec, t, ['Enter'])` presses it and takes the
|
|
637
|
+
* pane's turn — because the caller asked for it. What this method never does is *add* an Enter
|
|
638
|
+
* the caller did not write. Supplying one is `submit`'s job alone.
|
|
639
|
+
*/
|
|
640
|
+
sendKeys(exec: Exec, target: MuxTarget, keys: string[]): void;
|
|
641
|
+
/**
|
|
642
|
+
* Take the target's turn: type `text` if given, then **always** press Enter.
|
|
643
|
+
*
|
|
644
|
+
* With `text`, the guarantee is the observable outcome — the text typed *literally* (same bar as
|
|
645
|
+
* `sendText`), then Enter — never a particular backend command: a backend with a native
|
|
646
|
+
* text-plus-Enter primitive uses it, one without composes typing and Enter.
|
|
647
|
+
*
|
|
648
|
+
* Without `text` (or with an empty one), it sends a **bare Enter only**, flushing an
|
|
649
|
+
* already-staged input buffer without re-typing it. That is how a turn is completed when a
|
|
650
|
+
* booting harness swallowed the Enter of an earlier submit and left the text staged unsent;
|
|
651
|
+
* because flushing never re-types, a repeated flush cannot duplicate the message.
|
|
652
|
+
*/
|
|
653
|
+
submit(exec: Exec, target: MuxTarget, text?: string | undefined): void;
|
|
654
|
+
/** Capture the target session's current output. */
|
|
655
|
+
read(exec: Exec, target: MuxTarget, opts?: MuxReadOptions | undefined): string;
|
|
656
|
+
/**
|
|
657
|
+
* Block until the target's output matches `match`/`regex`, or until `timeoutMs` elapses.
|
|
658
|
+
*
|
|
659
|
+
* REQUIRED on every backend, unlike `regions`/`worktree`, and that is the whole point of the member:
|
|
660
|
+
* what it waits on is RAW TERMINAL TEXT, which every multiplexer can already read (`read` is
|
|
661
|
+
* required too). A backend with a native wait drives it (herdr `pane wait-output`); one without
|
|
662
|
+
* realizes it by polling its own `read` until the deadline (`pollForOutput`, `wait-output.ts`). Both
|
|
663
|
+
* are real support, so no caller ever writes a branch for a backend that "cannot wait" — contrast a
|
|
664
|
+
* wait on a DERIVED agent state (idle/working/blocked), which only a backend that computes that
|
|
665
|
+
* state can answer at all and which therefore does not belong on this seam.
|
|
666
|
+
*
|
|
667
|
+
* **The searched snapshot is `read`'s snapshot**, on every backend, so a pattern that `read` would
|
|
668
|
+
* show is a pattern this can wait for — one definition of "the pane's output" seam-wide. herdr's own
|
|
669
|
+
* `--source` vocabulary (`visible`/`recent`/`recent-unwrapped`) is not exposed for that reason: it is
|
|
670
|
+
* one backend's snapshot dialect, and the adapter pins it to the same `visible` its `read` uses
|
|
671
|
+
* rather than letting the same wait mean different things per backend.
|
|
672
|
+
*
|
|
673
|
+
* **Existing output counts.** The snapshot is searched IMMEDIATELY, before any sleeping, so a pattern
|
|
674
|
+
* already on screen returns at once — a wait that could only see text arriving after the call would
|
|
675
|
+
* lose every race it exists to win.
|
|
676
|
+
*
|
|
677
|
+
* The only ASYNC member of this seam: waiting is the one verb whose whole job is the passage of time,
|
|
678
|
+
* and `Exec` is synchronous by construction. Everything else stays sync.
|
|
679
|
+
*
|
|
680
|
+
* Returns `{ matched: false }` on the timeout rather than throwing — a deadline that passed is an
|
|
681
|
+
* ANSWER, and the caller's own to act on. Throws when the pane is gone (see `MuxWaitResult.matched`)
|
|
682
|
+
* or when the pattern is unusable (`assertWaitPattern`).
|
|
683
|
+
*/
|
|
684
|
+
waitForOutput(exec: Exec, target: MuxTarget, opts: MuxWaitOptions): Promise<MuxWaitResult>;
|
|
685
|
+
/**
|
|
686
|
+
* Beam the attached client's view all the way to the target pane — across workspace and tab, not
|
|
687
|
+
* just within the current one. Resolves the pane's own workspace/tab from the backend and drives
|
|
688
|
+
* the full switch chain; best-effort within (the backend owns the actual move), but throws rather
|
|
689
|
+
* than reporting a false success when the recorded pane no longer resolves to a live pane.
|
|
690
|
+
*/
|
|
691
|
+
focus(exec: Exec, target: MuxTarget): void;
|
|
692
|
+
/** Close the target session. */
|
|
693
|
+
teardown(exec: Exec, target: MuxTarget): void;
|
|
694
|
+
/**
|
|
695
|
+
* Whether the target pane still exists in this backend — the liveness check `prune` runs against a
|
|
696
|
+
* record's pane locator. Each backend answers with its own primitive so a herdr pane id is never
|
|
697
|
+
* probed with a tmux query (or vice versa).
|
|
698
|
+
*/
|
|
699
|
+
paneExists(exec: Exec, target: MuxTarget): boolean;
|
|
700
|
+
/**
|
|
701
|
+
* Whether the attached client is currently viewing this pane — a read-only focus probe. `true` =
|
|
702
|
+
* positively focused, `false` = positively not focused, `undefined` = the backend cannot report
|
|
703
|
+
* focus or the query could not be answered (callers FAIL OPEN on undefined). Read-only: moves no
|
|
704
|
+
* focus, opens nothing (unlike `focus`).
|
|
705
|
+
*/
|
|
706
|
+
isPaneFocused(exec: Exec, target: MuxTarget): boolean | undefined;
|
|
707
|
+
/**
|
|
708
|
+
* Enumerate every live pane this backend can currently see — the bulk counterpart to
|
|
709
|
+
* `paneExists`'s single targeted query. `reconcile` uses this to cull dead records in one pass
|
|
710
|
+
* against the mux the caller is actually inside; it never enumerates the other mux.
|
|
711
|
+
*/
|
|
712
|
+
listPanes(exec: Exec): LivePane[];
|
|
713
|
+
/**
|
|
714
|
+
* The optional geometry-introspection capability — `describeRegion` and `describeWorkspace` bundled
|
|
715
|
+
* as one object (see `RegionInspector`), present on a backend whose pane listing reports pane
|
|
716
|
+
* POSITION and absent on one that cannot. `template save` gates on it — refusing by NAMING the
|
|
717
|
+
* backend rather than degrading, because a region the backend cannot describe has nothing to degrade
|
|
718
|
+
* to. Bundled rather than shipped as two loose optional methods for the same reason `worktree` is
|
|
719
|
+
* one object: the two reads share a single all-or-nothing precondition.
|
|
720
|
+
*/
|
|
721
|
+
readonly regions?: RegionInspector | undefined;
|
|
722
|
+
/**
|
|
723
|
+
* The optional native agent-lifecycle-wait capability (`AgentLifecycle`), present only on a backend
|
|
724
|
+
* with a blocking per-pane agent-state primitive (herdr) and absent on one without (tmux, wezterm,
|
|
725
|
+
* zellij). `agent wait` gates on it — refusing by NAMING the backend rather than emulating, because
|
|
726
|
+
* a wait has no truthful degrade. Its ABSENCE is the refusal; see `deriveAgentWait` in `agent.ts`,
|
|
727
|
+
* the single place that sees the adapter and so the single place the refusal can be made.
|
|
728
|
+
*/
|
|
729
|
+
readonly agentLifecycle?: AgentLifecycle | undefined;
|
|
730
|
+
}
|
|
731
|
+
//#endregion
|
|
732
|
+
export { RegionInspector as _, LivePane as a, WorktreeWorkspace as b, MuxPlacement as c, MuxTarget as d, MuxWaitOptions as f, PaneRect as g, OpenedPane as h, CreateWorktreeWorkspaceOptions as i, MuxReadOptions as l, OpenWorktreeWorkspaceOptions as m, AgentStatus as n, MuxAdapter as o, MuxWaitResult as p, AgentWaitOptions as r, MuxOpenOptions as s, AgentLifecycle as t, MuxSpaceTier as u, RegionPane as v, WorktreeWorkspaceCapability as x, WorkspaceTab as y };
|
|
733
|
+
//# sourceMappingURL=mux-CoYDrk3v.d.mts.map
|