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
package/dist/agent.d.mts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { t as Exec } from "./exec-B81m4yjz.mjs";
|
|
2
|
+
import { d as MuxTarget, n as AgentStatus, o as MuxAdapter, r as AgentWaitOptions, t as AgentLifecycle } from "./mux-CoYDrk3v.mjs";
|
|
3
|
+
//#region src/agent.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* An agent-lifecycle wait asked of a backend that has no native agent-state primitive (`agent wait`
|
|
6
|
+
* on tmux, wezterm or zellij). An absent `agentLifecycle` seam member is a REFUSAL, never a guess: a
|
|
7
|
+
* wait built from `read()` polling would silently disagree with herdr's own state derivation on the
|
|
8
|
+
* same question, and a wait has no truthful degrade the way a snapshot does — so a backend that cannot
|
|
9
|
+
* answer is refused rather than emulated.
|
|
10
|
+
*
|
|
11
|
+
* PORTABLE and exit-code-free by design, the exact mirror of `CaptureUnsupportedError`. The DECISION
|
|
12
|
+
* to refuse is the library's and lives in `deriveAgentWait`, the one place that sees the adapter — how
|
|
13
|
+
* the refusal SURFACES (the exit code, the fix hint, the exact sentence) is the CLI's, which catches
|
|
14
|
+
* this and re-raises its own `backend-unsupported` error. `backend` names the backend so the caller
|
|
15
|
+
* composes the message without re-deriving it; the terse `message` is a factual log line.
|
|
16
|
+
*/
|
|
17
|
+
declare class AgentLifecycleUnsupportedError extends Error {
|
|
18
|
+
readonly backend: string;
|
|
19
|
+
constructor(backend: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wait for the target pane's agent to reach one of `opts.until` (or the backend's default set) through
|
|
23
|
+
* the adapter — the surface-independent orchestrator `agent wait` drives, and the single home of the
|
|
24
|
+
* agent-wait refusal. The optional `agentLifecycle` seam is where a backend says whether it has a
|
|
25
|
+
* native wait at all; a backend without it is refused HERE (`AgentLifecycleUnsupportedError`), BEFORE
|
|
26
|
+
* any exec, because `waitForState` never sees the adapter and so cannot make that call. A backend that
|
|
27
|
+
* HAS the capability delegates to it unchanged.
|
|
28
|
+
*
|
|
29
|
+
* Mirrors `deriveRegionCapture` (`template-capture.ts`) exactly: the orchestrator is the one place that
|
|
30
|
+
* sees the adapter, so it is the one place the emulate-or-refuse decision can be made.
|
|
31
|
+
*/
|
|
32
|
+
declare function deriveAgentWait(adapter: MuxAdapter, exec: Exec, target: MuxTarget, opts: AgentWaitOptions): AgentStatus;
|
|
33
|
+
/**
|
|
34
|
+
* The `agent` subpath facade with its `Exec` and backend BOUND — the exec-bound parallel of
|
|
35
|
+
* `worktreeApi`/`templateApi`. `agentApi(env, deps?)` resolves the backend adapter from `env` ONCE
|
|
36
|
+
* (`resolveMuxAdapter`, defaulting `exec` to `nodeExec`) and exposes `supported`/`status`/`wait` with
|
|
37
|
+
* the seams already threaded, so a caller never re-plumbs an adapter or a runner into them.
|
|
38
|
+
*
|
|
39
|
+
* It ADDS no logic of its own: `supported` reads the very capability presence `deriveAgentWait` gates
|
|
40
|
+
* on, `status` reads the same `LivePane.agentStatus` the listing already carries (for one pane rather
|
|
41
|
+
* than redefining it), and `wait` routes THROUGH `deriveAgentWait` — so the emulate-or-refuse decision
|
|
42
|
+
* stays specified once and enforced once, with no second refusal path here that could drift from it.
|
|
43
|
+
*/
|
|
44
|
+
interface AgentApi {
|
|
45
|
+
/** Whether this backend reports agent-lifecycle state at all (herdr yes; tmux/wezterm/zellij no). */
|
|
46
|
+
supported(): boolean;
|
|
47
|
+
/** A pane's current agent state, or `undefined` when the backend has no feed (absent-not-false). */
|
|
48
|
+
status(target: MuxTarget): AgentStatus | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Block until the pane's agent reaches one of `opts.until` (or the backend's default set); throws
|
|
51
|
+
* `AgentLifecycleUnsupportedError` naming the backend on one without the capability, via
|
|
52
|
+
* `deriveAgentWait`. A bare `wait(target)` takes herdr's own defaults (`opts ?? {}`).
|
|
53
|
+
*/
|
|
54
|
+
wait(target: MuxTarget, opts?: AgentWaitOptions | undefined): AgentStatus;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Bind the agent-lifecycle capability to an environment and runner once, returning an `AgentApi` whose
|
|
58
|
+
* methods no longer take an `Exec`. `deps.exec` defaults to `nodeExec`; `env` is bound like
|
|
59
|
+
* `resolveMux(env)` because it is what the probe resolves the backend from.
|
|
60
|
+
*/
|
|
61
|
+
declare function agentApi(env: NodeJS.ProcessEnv, deps?: {
|
|
62
|
+
exec?: Exec | undefined;
|
|
63
|
+
} | undefined): AgentApi;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { AgentApi, type AgentLifecycle, AgentLifecycleUnsupportedError, type AgentStatus, type AgentWaitOptions, agentApi, deriveAgentWait };
|
|
66
|
+
//# sourceMappingURL=agent.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.mts","names":[],"sources":["../src/agent.ts"],"mappings":";;;;;;;;;;;;;;;;cA4Ba,uCAAuC;WACvC;EAAZ,YAAY;;;;;;;;;;;;;iBAiBG,gBACf,SAAS,YACT,MAAM,MACN,QAAQ,WACR,MAAM,mBACJ;;;;;;;;;;;;UAiBc;;EAEhB;;EAEA,OAAO,QAAQ,YAAY;;;;;;EAM3B,KAAK,QAAQ,WAAW,OAAO,+BAA+B;;;;;;;iBAQ/C,SAAS,KAAK,OAAO,YAAY;EAAS,OAAO;gBAAiC"}
|
package/dist/agent.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { m as nodeExec } from "./worktree-hHuFZkpW.mjs";
|
|
2
|
+
import { r as resolveMuxAdapter } from "./backend-Cif257ji.mjs";
|
|
3
|
+
//#region src/agent.ts
|
|
4
|
+
/**
|
|
5
|
+
* An agent-lifecycle wait asked of a backend that has no native agent-state primitive (`agent wait`
|
|
6
|
+
* on tmux, wezterm or zellij). An absent `agentLifecycle` seam member is a REFUSAL, never a guess: a
|
|
7
|
+
* wait built from `read()` polling would silently disagree with herdr's own state derivation on the
|
|
8
|
+
* same question, and a wait has no truthful degrade the way a snapshot does — so a backend that cannot
|
|
9
|
+
* answer is refused rather than emulated.
|
|
10
|
+
*
|
|
11
|
+
* PORTABLE and exit-code-free by design, the exact mirror of `CaptureUnsupportedError`. The DECISION
|
|
12
|
+
* to refuse is the library's and lives in `deriveAgentWait`, the one place that sees the adapter — how
|
|
13
|
+
* the refusal SURFACES (the exit code, the fix hint, the exact sentence) is the CLI's, which catches
|
|
14
|
+
* this and re-raises its own `backend-unsupported` error. `backend` names the backend so the caller
|
|
15
|
+
* composes the message without re-deriving it; the terse `message` is a factual log line.
|
|
16
|
+
*/
|
|
17
|
+
var AgentLifecycleUnsupportedError = class extends Error {
|
|
18
|
+
backend;
|
|
19
|
+
constructor(backend) {
|
|
20
|
+
super(`${backend} cannot report agent-lifecycle state`);
|
|
21
|
+
this.backend = backend;
|
|
22
|
+
this.name = "AgentLifecycleUnsupportedError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Wait for the target pane's agent to reach one of `opts.until` (or the backend's default set) through
|
|
27
|
+
* the adapter — the surface-independent orchestrator `agent wait` drives, and the single home of the
|
|
28
|
+
* agent-wait refusal. The optional `agentLifecycle` seam is where a backend says whether it has a
|
|
29
|
+
* native wait at all; a backend without it is refused HERE (`AgentLifecycleUnsupportedError`), BEFORE
|
|
30
|
+
* any exec, because `waitForState` never sees the adapter and so cannot make that call. A backend that
|
|
31
|
+
* HAS the capability delegates to it unchanged.
|
|
32
|
+
*
|
|
33
|
+
* Mirrors `deriveRegionCapture` (`template-capture.ts`) exactly: the orchestrator is the one place that
|
|
34
|
+
* sees the adapter, so it is the one place the emulate-or-refuse decision can be made.
|
|
35
|
+
*/
|
|
36
|
+
function deriveAgentWait(adapter, exec, target, opts) {
|
|
37
|
+
const agentLifecycle = adapter.agentLifecycle;
|
|
38
|
+
if (!agentLifecycle) throw new AgentLifecycleUnsupportedError(adapter.name);
|
|
39
|
+
return agentLifecycle.waitForState(exec, target, opts);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Bind the agent-lifecycle capability to an environment and runner once, returning an `AgentApi` whose
|
|
43
|
+
* methods no longer take an `Exec`. `deps.exec` defaults to `nodeExec`; `env` is bound like
|
|
44
|
+
* `resolveMux(env)` because it is what the probe resolves the backend from.
|
|
45
|
+
*/
|
|
46
|
+
function agentApi(env, deps) {
|
|
47
|
+
const exec = deps?.exec ?? nodeExec;
|
|
48
|
+
const adapter = resolveMuxAdapter(env, exec);
|
|
49
|
+
return {
|
|
50
|
+
supported: () => adapter.agentLifecycle !== void 0,
|
|
51
|
+
status: (target) => adapter.listPanes(exec).find((p) => p.id === target.id)?.agentStatus,
|
|
52
|
+
wait: (target, opts) => deriveAgentWait(adapter, exec, target, opts ?? {})
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { AgentLifecycleUnsupportedError, agentApi, deriveAgentWait };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=agent.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.mjs","names":[],"sources":["../src/agent.ts"],"sourcesContent":["import { resolveMuxAdapter } from './backend.ts'\nimport { type Exec, nodeExec } from './exec.ts'\nimport type { AgentStatus, AgentWaitOptions, MuxAdapter, MuxTarget } from './mux.ts'\n\n/**\n * The `cyber-mux/agent` subpath — the agent-lifecycle capability's orchestrator and its refusal.\n *\n * The `AgentStatus` type rides out on the `.` barrel (it is part of `LivePane`, and `mux.ts` is\n * re-exported there); the WAIT capability — the `AgentLifecycle` seam plus the emulate-or-refuse\n * decision below — is this subpath alone, kept off the core barrel exactly as `template`'s apply\n * engine is: a capability nobody has to import to drive a pane is not on the surface everybody gets.\n */\n\nexport type { AgentLifecycle, AgentStatus, AgentWaitOptions } from './mux.ts'\n\n/**\n * An agent-lifecycle wait asked of a backend that has no native agent-state primitive (`agent wait`\n * on tmux, wezterm or zellij). An absent `agentLifecycle` seam member is a REFUSAL, never a guess: a\n * wait built from `read()` polling would silently disagree with herdr's own state derivation on the\n * same question, and a wait has no truthful degrade the way a snapshot does — so a backend that cannot\n * answer is refused rather than emulated.\n *\n * PORTABLE and exit-code-free by design, the exact mirror of `CaptureUnsupportedError`. The DECISION\n * to refuse is the library's and lives in `deriveAgentWait`, the one place that sees the adapter — how\n * the refusal SURFACES (the exit code, the fix hint, the exact sentence) is the CLI's, which catches\n * this and re-raises its own `backend-unsupported` error. `backend` names the backend so the caller\n * composes the message without re-deriving it; the terse `message` is a factual log line.\n */\nexport class AgentLifecycleUnsupportedError extends Error {\n\tconstructor(readonly backend: string) {\n\t\tsuper(`${backend} cannot report agent-lifecycle state`)\n\t\tthis.name = 'AgentLifecycleUnsupportedError'\n\t}\n}\n\n/**\n * Wait for the target pane's agent to reach one of `opts.until` (or the backend's default set) through\n * the adapter — the surface-independent orchestrator `agent wait` drives, and the single home of the\n * agent-wait refusal. The optional `agentLifecycle` seam is where a backend says whether it has a\n * native wait at all; a backend without it is refused HERE (`AgentLifecycleUnsupportedError`), BEFORE\n * any exec, because `waitForState` never sees the adapter and so cannot make that call. A backend that\n * HAS the capability delegates to it unchanged.\n *\n * Mirrors `deriveRegionCapture` (`template-capture.ts`) exactly: the orchestrator is the one place that\n * sees the adapter, so it is the one place the emulate-or-refuse decision can be made.\n */\nexport function deriveAgentWait(\n\tadapter: MuxAdapter,\n\texec: Exec,\n\ttarget: MuxTarget,\n\topts: AgentWaitOptions,\n): AgentStatus {\n\tconst agentLifecycle = adapter.agentLifecycle\n\tif (!agentLifecycle) throw new AgentLifecycleUnsupportedError(adapter.name)\n\treturn agentLifecycle.waitForState(exec, target, opts)\n}\n\n/**\n * The `agent` subpath facade with its `Exec` and backend BOUND — the exec-bound parallel of\n * `worktreeApi`/`templateApi`. `agentApi(env, deps?)` resolves the backend adapter from `env` ONCE\n * (`resolveMuxAdapter`, defaulting `exec` to `nodeExec`) and exposes `supported`/`status`/`wait` with\n * the seams already threaded, so a caller never re-plumbs an adapter or a runner into them.\n *\n * It ADDS no logic of its own: `supported` reads the very capability presence `deriveAgentWait` gates\n * on, `status` reads the same `LivePane.agentStatus` the listing already carries (for one pane rather\n * than redefining it), and `wait` routes THROUGH `deriveAgentWait` — so the emulate-or-refuse decision\n * stays specified once and enforced once, with no second refusal path here that could drift from it.\n */\nexport interface AgentApi {\n\t/** Whether this backend reports agent-lifecycle state at all (herdr yes; tmux/wezterm/zellij no). */\n\tsupported(): boolean\n\t/** A pane's current agent state, or `undefined` when the backend has no feed (absent-not-false). */\n\tstatus(target: MuxTarget): AgentStatus | undefined\n\t/**\n\t * Block until the pane's agent reaches one of `opts.until` (or the backend's default set); throws\n\t * `AgentLifecycleUnsupportedError` naming the backend on one without the capability, via\n\t * `deriveAgentWait`. A bare `wait(target)` takes herdr's own defaults (`opts ?? {}`).\n\t */\n\twait(target: MuxTarget, opts?: AgentWaitOptions | undefined): AgentStatus\n}\n\n/**\n * Bind the agent-lifecycle capability to an environment and runner once, returning an `AgentApi` whose\n * methods no longer take an `Exec`. `deps.exec` defaults to `nodeExec`; `env` is bound like\n * `resolveMux(env)` because it is what the probe resolves the backend from.\n */\nexport function agentApi(env: NodeJS.ProcessEnv, deps?: { exec?: Exec | undefined } | undefined): AgentApi {\n\tconst exec = deps?.exec ?? nodeExec\n\tconst adapter = resolveMuxAdapter(env, exec)\n\treturn {\n\t\tsupported: () => adapter.agentLifecycle !== undefined,\n\t\tstatus: (target) => adapter.listPanes(exec).find((p) => p.id === target.id)?.agentStatus,\n\t\twait: (target, opts) => deriveAgentWait(adapter, exec, target, opts ?? {}),\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,IAAa,iCAAb,cAAoD,MAAM;CACpC;CAArB,YAAY,SAA0B;EACrC,MAAM,GAAG,QAAQ,qCAAqC;EADlC,KAAA,UAAA;EAEpB,KAAK,OAAO;CACb;AACD;;;;;;;;;;;;AAaA,SAAgB,gBACf,SACA,MACA,QACA,MACc;CACd,MAAM,iBAAiB,QAAQ;CAC/B,IAAI,CAAC,gBAAgB,MAAM,IAAI,+BAA+B,QAAQ,IAAI;CAC1E,OAAO,eAAe,aAAa,MAAM,QAAQ,IAAI;AACtD;;;;;;AA+BA,SAAgB,SAAS,KAAwB,MAA0D;CAC1G,MAAM,OAAO,MAAM,QAAQ;CAC3B,MAAM,UAAU,kBAAkB,KAAK,IAAI;CAC3C,OAAO;EACN,iBAAiB,QAAQ,mBAAmB,KAAA;EAC5C,SAAS,WAAW,QAAQ,UAAU,IAAI,CAAC,CAAC,MAAM,MAAM,EAAE,OAAO,OAAO,EAAE,CAAC,EAAE;EAC7E,OAAO,QAAQ,SAAS,gBAAgB,SAAS,MAAM,QAAQ,QAAQ,CAAC,CAAC;CAC1E;AACD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as withReason, m as nodeExec, s as normalizeWorktreePath } from "./worktree-hHuFZkpW.mjs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/env-fallback.ts
|
|
@@ -50,6 +50,130 @@ function envFallback(env, command) {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
//#endregion
|
|
53
|
+
//#region src/ratio.ts
|
|
54
|
+
/**
|
|
55
|
+
* The seam's own precondition on `MuxOpenOptions.ratio`: a fraction STRICTLY between 0 and 1.
|
|
56
|
+
*
|
|
57
|
+
* `ratio` is the fraction kept by the ORIGINAL pane. Outside `0 < ratio < 1` there is no split it can
|
|
58
|
+
* name: `1 - ratio` goes negative above 1 (tmux `-l -50%` / wezterm `--percent -50`), and 0 or 1 hands
|
|
59
|
+
* one side the whole region and the other nothing — a mistake, never an intent worth honoring. Left
|
|
60
|
+
* unrendered these produce a silently broken split, not an error, which is the exact silent-wrong
|
|
61
|
+
* output this seam's loud-over-quiet preference exists to refuse.
|
|
62
|
+
*
|
|
63
|
+
* Enforced HERE, at the seam, rather than left to each caller, because the invariant is a universal
|
|
64
|
+
* property of what a ratio IS — true on every backend — not a per-caller policy. (The DEGRADE policy —
|
|
65
|
+
* what a caller does when a backend cannot size a split at all — genuinely stays the caller's, unchanged;
|
|
66
|
+
* range validity and degrade policy are different questions.) A caller cannot reach an adapter with an
|
|
67
|
+
* out-of-range ratio and have it silently rendered; `template`'s schema still refuses one earlier, per
|
|
68
|
+
* node, with a path-qualified message, so the two layers do different jobs and the seam is the backstop.
|
|
69
|
+
*
|
|
70
|
+
* The guard lives WITH the rendering: it is called by each backend's size render helper, so a backend
|
|
71
|
+
* that cannot size a split (zellij) renders no ratio and so never reaches this guard — a dropped value
|
|
72
|
+
* is never checked, valid or not, which is the same as the even-default degrade its callers already take.
|
|
73
|
+
*/
|
|
74
|
+
function assertRatioInRange(ratio) {
|
|
75
|
+
if (!Number.isFinite(ratio) || ratio <= 0 || ratio >= 1) throw new Error(`ratio must be strictly between 0 and 1 — got ${ratio}`);
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/wait-output.ts
|
|
79
|
+
/** How long a polling backend sleeps between reads when the caller names no cadence. */
|
|
80
|
+
const DEFAULT_WAIT_POLL_MS = 150;
|
|
81
|
+
/**
|
|
82
|
+
* The seam's own precondition on a wait pattern: EXACTLY ONE of `match`/`regex`, and a `regex` that
|
|
83
|
+
* compiles.
|
|
84
|
+
*
|
|
85
|
+
* Enforced here, at the seam, rather than per adapter, for `assertRatioInRange`'s reason — it is a
|
|
86
|
+
* universal property of what a wait pattern IS, true on every backend, not a per-backend policy. Both
|
|
87
|
+
* halves matter for portability in different ways: the one-of rule is refusable by herdr's CLI and by
|
|
88
|
+
* nothing at all on a polling backend, so leaving it to the backend would make the same call fail on
|
|
89
|
+
* one and silently pick a winner on another; and compiling the source turns a MALFORMED pattern into
|
|
90
|
+
* the same loud failure everywhere, instead of a herdr refusal on one backend and a poll that throws
|
|
91
|
+
* on its first read somewhere else.
|
|
92
|
+
*
|
|
93
|
+
* What it deliberately does NOT check is dialect: a pattern using ECMAScript-only syntax compiles here
|
|
94
|
+
* and is then herdr's own to accept or refuse (see `MuxWaitOptions.regex`). Validating against the
|
|
95
|
+
* intersection of two regex engines would mean shipping a third one.
|
|
96
|
+
*/
|
|
97
|
+
function assertWaitPattern(opts) {
|
|
98
|
+
const hasMatch = opts.match != null;
|
|
99
|
+
const hasRegex = opts.regex != null;
|
|
100
|
+
if (hasMatch && hasRegex) throw new Error("wait pattern must be one of match or regex — got both");
|
|
101
|
+
if (!hasMatch && !hasRegex) throw new Error("wait pattern must be one of match or regex — got neither");
|
|
102
|
+
if (opts.match != null && opts.match === "") throw new Error("wait pattern match must not be empty");
|
|
103
|
+
if (opts.regex != null) try {
|
|
104
|
+
new RegExp(opts.regex);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
throw new Error(`wait pattern regex is not a valid expression: ${opts.regex} — ${err.message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Whether `output` satisfies the pattern, and the single line to point at when it does.
|
|
111
|
+
*
|
|
112
|
+
* The match runs against the WHOLE snapshot, not line by line, so a regex that spans a newline still
|
|
113
|
+
* hits — that is why `matchedLine` is derived separately and left absent when no single line carries
|
|
114
|
+
* the match on its own. Pure, so the tricky half is testable with no multiplexer at all, exactly as
|
|
115
|
+
* `template-capture`'s geometry derivation is.
|
|
116
|
+
*/
|
|
117
|
+
function matchWaitPattern(output, opts) {
|
|
118
|
+
assertWaitPattern(opts);
|
|
119
|
+
const hit = (text) => opts.match != null ? text.includes(opts.match) : new RegExp(opts.regex).test(text);
|
|
120
|
+
if (!hit(output)) return {
|
|
121
|
+
matched: false,
|
|
122
|
+
output
|
|
123
|
+
};
|
|
124
|
+
const line = output.split("\n").find(hit);
|
|
125
|
+
return {
|
|
126
|
+
matched: true,
|
|
127
|
+
output,
|
|
128
|
+
...line != null ? { matchedLine: line } : {}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* `waitForOutput` for a backend with NO native wait — poll its own `read` until the pattern matches or
|
|
133
|
+
* the deadline passes. tmux, WezTerm and Zellij all route their seam method straight through here, so
|
|
134
|
+
* the three share one cadence, one deadline rule and one liveness rule rather than three copies that
|
|
135
|
+
* can drift; herdr overrides it with its native primitive.
|
|
136
|
+
*
|
|
137
|
+
* **Reads first, sleeps second.** The snapshot on screen when the call arrives is searched before any
|
|
138
|
+
* sleeping, so a pattern already printed returns immediately — the seam's stated "existing output
|
|
139
|
+
* counts" rule, and the same order herdr's native wait documents for itself.
|
|
140
|
+
*
|
|
141
|
+
* **A gone pane throws instead of timing out**, which is `nudge`'s rule for the same reason: a dead
|
|
142
|
+
* pane and a quiet one both read back empty, so without the liveness probe every dead peer would be
|
|
143
|
+
* reported as a timeout — a shape the caller reads as "still working" — and the real cause would be
|
|
144
|
+
* buried. Probed BEFORE the first read (so a pane that was already gone fails at once rather than
|
|
145
|
+
* after the full timeout) and again after the deadline (so a pane that died mid-wait is not reported as
|
|
146
|
+
* one that merely stayed quiet). Never probed per poll: that would double every backend's query load
|
|
147
|
+
* for a fact that only changes the verdict at the end.
|
|
148
|
+
*/
|
|
149
|
+
async function pollForOutput(adapter, exec, target, opts) {
|
|
150
|
+
assertWaitPattern(opts);
|
|
151
|
+
const pollMs = opts.pollMs ?? 150;
|
|
152
|
+
const now = opts.now ?? (() => Date.now());
|
|
153
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
154
|
+
const readOpts = opts.lines != null ? { lines: opts.lines } : void 0;
|
|
155
|
+
assertPaneLive(adapter, exec, target);
|
|
156
|
+
const deadline = now() + opts.timeoutMs;
|
|
157
|
+
let output = "";
|
|
158
|
+
for (;;) {
|
|
159
|
+
output = adapter.read(exec, target, readOpts);
|
|
160
|
+
const result = matchWaitPattern(output, opts);
|
|
161
|
+
if (result.matched) return result;
|
|
162
|
+
if (now() >= deadline) break;
|
|
163
|
+
await sleep(pollMs);
|
|
164
|
+
}
|
|
165
|
+
assertPaneLive(adapter, exec, target);
|
|
166
|
+
return {
|
|
167
|
+
matched: false,
|
|
168
|
+
output
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** The liveness probe both ends of a poll share, throwing `nudge`'s named failure rather than letting a
|
|
172
|
+
* dead pane be reported as a quiet one. */
|
|
173
|
+
function assertPaneLive(adapter, exec, target) {
|
|
174
|
+
if (!adapter.paneExists(exec, target)) throw new Error(`wait failed: pane ${target.id} no longer exists — the pane is gone, not quiet.`);
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
53
177
|
//#region src/mux.herdr.ts
|
|
54
178
|
/**
|
|
55
179
|
* herdr backend — detected via `$HERDR_ENV`. herdr (https://herdr.dev) is an agent-aware terminal
|
|
@@ -97,7 +221,7 @@ const herdrMuxAdapter = {
|
|
|
97
221
|
} else {
|
|
98
222
|
const direction = at === "pane:down" ? "down" : "right";
|
|
99
223
|
const from = opts.from ? [opts.from.id] : ["--current"];
|
|
100
|
-
const size = opts.ratio != null ? ["--ratio",
|
|
224
|
+
const size = opts.ratio != null ? ["--ratio", toHerdrRatio(opts.ratio)] : [];
|
|
101
225
|
const out = exec("herdr", [
|
|
102
226
|
"pane",
|
|
103
227
|
"split",
|
|
@@ -170,6 +294,61 @@ const herdrMuxAdapter = {
|
|
|
170
294
|
if (opts?.lines != null) args.push("--lines", String(opts.lines));
|
|
171
295
|
return exec("herdr", args) ?? "";
|
|
172
296
|
},
|
|
297
|
+
/**
|
|
298
|
+
* The one backend with a NATIVE wait: `pane wait-output` blocks in herdr itself (0.7.5), so no poll
|
|
299
|
+
* loop is run here and no snapshot is pulled across the CLI boundary on every tick.
|
|
300
|
+
*
|
|
301
|
+
* `--source visible` is pinned rather than left to herdr's own default (`recent_unwrapped`, verified
|
|
302
|
+
* against 0.7.5 — the help says `recent`). The seam's rule is that a wait searches exactly what
|
|
303
|
+
* `read` returns, and `read` pins `visible` here; taking the default would make the same wait mean a
|
|
304
|
+
* different snapshot on this backend than on every polling one.
|
|
305
|
+
*
|
|
306
|
+
* Telling a TIMEOUT (an answer) from a broken wait (a failure) is the whole difficulty, because herdr
|
|
307
|
+
* spells both the same way: exit 1 with an error envelope on stderr, so `Exec` yields `null` for
|
|
308
|
+
* either. Two tiers answer it, in order:
|
|
309
|
+
*
|
|
310
|
+
* 1. **The envelope's `code`**, when the runner captured stderr into `lastError` (verified against
|
|
311
|
+
* 0.7.5: `{"error":{"code":"timeout",…}}` vs `{"error":{"code":"pane_not_found",…}}`). Exact.
|
|
312
|
+
* 2. **A live pane that actually consumed the deadline**, when it did not. `Exec.lastError` is
|
|
313
|
+
* specified as a diagnostic and NEVER a control-flow signal — a runner that discards stderr must
|
|
314
|
+
* still work — so the code cannot be the only answer. Liveness alone is not enough either, and the
|
|
315
|
+
* reason is a whole released version of the backend: herdr 0.7.4 has no `pane wait-output` at all,
|
|
316
|
+
* so it answers with clap's usage text (not an envelope) INSTANTLY, and a liveness-only rule reads
|
|
317
|
+
* that as "timed out" — a silently wrong answer for a wait that never ran. Elapsed time is the fact
|
|
318
|
+
* that separates them and needs no stderr: a wait that returns in a fraction of its own timeout did
|
|
319
|
+
* not wait. Both must hold — the pane is live AND the deadline was spent — or this throws.
|
|
320
|
+
*
|
|
321
|
+
* A timeout costs ONE extra `read`, because herdr's timeout envelope carries no snapshot and the seam
|
|
322
|
+
* promises the caller the evidence its verdict was reached on. It is taken at the deadline, so it is
|
|
323
|
+
* the same "last look at the pane" a polling backend returns, one poll interval later.
|
|
324
|
+
*/
|
|
325
|
+
async waitForOutput(exec, target, opts) {
|
|
326
|
+
assertWaitPattern(opts);
|
|
327
|
+
const now = opts.now ?? (() => Date.now());
|
|
328
|
+
const pattern = opts.match != null ? ["--match", opts.match] : ["--regex", opts.regex];
|
|
329
|
+
const args = [
|
|
330
|
+
"pane",
|
|
331
|
+
"wait-output",
|
|
332
|
+
target.id,
|
|
333
|
+
"--source",
|
|
334
|
+
"visible",
|
|
335
|
+
"--timeout",
|
|
336
|
+
String(opts.timeoutMs)
|
|
337
|
+
];
|
|
338
|
+
args.push(...pattern);
|
|
339
|
+
if (opts.lines != null) args.push("--lines", String(opts.lines));
|
|
340
|
+
const started = now();
|
|
341
|
+
const out = exec("herdr", args);
|
|
342
|
+
if (out == null) {
|
|
343
|
+
if (!isHerdrWaitTimeout(exec, target, opts.timeoutMs, now() - started)) throw new Error(withReason(exec, `herdr pane wait-output failed for pane ${target.id}`));
|
|
344
|
+
const readOpts = opts.lines != null ? { lines: opts.lines } : void 0;
|
|
345
|
+
return {
|
|
346
|
+
matched: false,
|
|
347
|
+
output: herdrMuxAdapter.read(exec, target, readOpts)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return parseWaitOutput(out);
|
|
351
|
+
},
|
|
173
352
|
focus(exec, target) {
|
|
174
353
|
const { workspaceId, tabId } = parsePaneLocation$1(exec("herdr", [
|
|
175
354
|
"pane",
|
|
@@ -230,10 +409,12 @@ const herdrMuxAdapter = {
|
|
|
230
409
|
return panes.filter((p) => typeof p?.pane_id === "string").map((p) => {
|
|
231
410
|
const harness = p.agent || void 0;
|
|
232
411
|
const label = p.label || void 0;
|
|
412
|
+
const agentStatus = toAgentStatus(p.agent_status);
|
|
233
413
|
return {
|
|
234
414
|
id: p.pane_id,
|
|
235
415
|
mux: "herdr",
|
|
236
416
|
...harness !== void 0 ? { harness } : {},
|
|
417
|
+
...agentStatus !== void 0 ? { agentStatus } : {},
|
|
237
418
|
...p.cwd !== void 0 ? { cwd: p.cwd } : {},
|
|
238
419
|
...label !== void 0 ? { label } : {}
|
|
239
420
|
};
|
|
@@ -298,9 +479,54 @@ const herdrMuxAdapter = {
|
|
|
298
479
|
if (tabs.length === 0) throw new Error(`herdr reported no usable tabs in workspace ${workspaceId}: ${out.slice(0, 200)}`);
|
|
299
480
|
return tabs;
|
|
300
481
|
}
|
|
301
|
-
}
|
|
482
|
+
},
|
|
483
|
+
agentLifecycle: { waitForState(exec, target, opts) {
|
|
484
|
+
const until = opts.until ?? [];
|
|
485
|
+
const status = parseReachedAgentStatus(exec("herdr", [
|
|
486
|
+
"agent",
|
|
487
|
+
"wait",
|
|
488
|
+
target.id,
|
|
489
|
+
...until.flatMap((state) => ["--until", state]),
|
|
490
|
+
...opts.timeoutMs != null ? ["--timeout", String(opts.timeoutMs)] : []
|
|
491
|
+
]));
|
|
492
|
+
if (!status) throw new Error(withReason(exec, `herdr agent wait reported no reached agent_status for pane ${target.id}`));
|
|
493
|
+
return status;
|
|
494
|
+
} }
|
|
302
495
|
};
|
|
303
496
|
/**
|
|
497
|
+
* The set of `agent_status` values herdr 0.7.5 reports — the runtime witness of the `AgentStatus`
|
|
498
|
+
* type, so a string read off a herdr envelope can be NARROWED to it rather than cast. A value outside
|
|
499
|
+
* this set is treated as absent (the feed said something this build does not model), never forced into
|
|
500
|
+
* the type.
|
|
501
|
+
*/
|
|
502
|
+
const AGENT_STATUSES = [
|
|
503
|
+
"idle",
|
|
504
|
+
"working",
|
|
505
|
+
"blocked",
|
|
506
|
+
"done",
|
|
507
|
+
"unknown"
|
|
508
|
+
];
|
|
509
|
+
/** A value narrowed to `AgentStatus`, or `undefined` for anything else (a non-string, an empty string,
|
|
510
|
+
* or a status this build does not model) — the normalization both the listing and the wait share. */
|
|
511
|
+
function toAgentStatus(value) {
|
|
512
|
+
return typeof value === "string" && AGENT_STATUSES.includes(value) ? value : void 0;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* The `AgentStatus` a `herdr agent wait` run reached, read defensively from its JSON envelope —
|
|
516
|
+
* `{"result":{"agent":{…,"agent_status":"idle",…},"type":"agent_info"}}` (verified against 0.7.5), so
|
|
517
|
+
* the reached status lives at `.result.agent.agent_status`. Every unresolvable shape — `out` is null
|
|
518
|
+
* (an Exec failure), the JSON does not parse, or the field is missing/empty/unmodeled — folds to
|
|
519
|
+
* `undefined`, exactly as `parsePaneRecord`/`isPaneFocused` fold, so the caller states its own failure.
|
|
520
|
+
*/
|
|
521
|
+
function parseReachedAgentStatus(out) {
|
|
522
|
+
if (out == null) return void 0;
|
|
523
|
+
try {
|
|
524
|
+
return toAgentStatus(JSON.parse(out)?.result?.agent?.agent_status);
|
|
525
|
+
} catch {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
304
530
|
* The rects of the region `paneId` sits in, joined with the cwd/label half.
|
|
305
531
|
*
|
|
306
532
|
* Two sources, because herdr splits the answer across two verbs: `pane layout` reports the region's
|
|
@@ -391,6 +617,16 @@ function envFlags(env) {
|
|
|
391
617
|
return env ? Object.entries(env).flatMap(([k, v]) => ["--env", `${k}=${v}`]) : [];
|
|
392
618
|
}
|
|
393
619
|
/**
|
|
620
|
+
* `--ratio` takes the seam's number VERBATIM — herdr sizes the ORIGINAL pane, so no inversion, unlike
|
|
621
|
+
* tmux's `-l` and wezterm's `--percent`. The guard is the same one those two render helpers call: the
|
|
622
|
+
* seam refuses an out-of-range ratio here rather than pass `--ratio 5` (or `0`) through to a split herdr
|
|
623
|
+
* would then size wrong.
|
|
624
|
+
*/
|
|
625
|
+
function toHerdrRatio(ratio) {
|
|
626
|
+
assertRatioInRange(ratio);
|
|
627
|
+
return String(ratio);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
394
630
|
* Launch a command in a worktree's root pane, carrying env the worktree verb could not set at birth.
|
|
395
631
|
* The prefix-or-warn rule is the seam's (`env-fallback.ts`); this is the one route that invokes it,
|
|
396
632
|
* because it is the one route that loses env. With a command, env rides in as a prefix; with none and
|
|
@@ -437,6 +673,65 @@ function nonEmpty(value) {
|
|
|
437
673
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
438
674
|
}
|
|
439
675
|
/**
|
|
676
|
+
* The `code` of a herdr error envelope, when the runner captured one — how a wait's TIMEOUT (an answer)
|
|
677
|
+
* is told from any other failure (a throw). Read from `Exec.lastError` because that is where herdr's
|
|
678
|
+
* envelope lands: it is written to stderr with exit 1, so stdout is `null` for every failure alike and
|
|
679
|
+
* the code is the only thing that separates them. Defensive throughout — no reason, unparseable JSON, or
|
|
680
|
+
* a missing/non-string code all answer `undefined`, which routes to the throw rather than to a silent
|
|
681
|
+
* "timed out" the backend never said.
|
|
682
|
+
*/
|
|
683
|
+
/**
|
|
684
|
+
* How much of its own timeout a wait must actually spend before a failure is believed to BE that
|
|
685
|
+
* timeout. A fraction rather than the whole, because process start-up and clock granularity make an
|
|
686
|
+
* exact-or-greater comparison flaky on a real runner; wide enough that the case it exists to catch — a
|
|
687
|
+
* herdr with no `wait-output` subcommand, which returns in milliseconds — is nowhere near it.
|
|
688
|
+
*/
|
|
689
|
+
const HERDR_WAIT_ELAPSED_RATIO = .9;
|
|
690
|
+
/**
|
|
691
|
+
* Whether a failed `pane wait-output` was the DEADLINE passing rather than the wait breaking — the
|
|
692
|
+
* two-tier rule `waitForOutput` documents, kept out of the method so the tiers read as one decision.
|
|
693
|
+
*
|
|
694
|
+
* The envelope's code answers when the runner captured one. Otherwise the answer needs two facts, and
|
|
695
|
+
* neither alone is enough: the pane must be LIVE (a gone pane is a failure, `pollForOutput`'s rule) and
|
|
696
|
+
* the call must have SPENT the deadline (a wait that returned instantly never ran — herdr 0.7.4, whose
|
|
697
|
+
* usage text for an unknown subcommand is not an envelope to read a code from).
|
|
698
|
+
*/
|
|
699
|
+
function isHerdrWaitTimeout(exec, target, timeoutMs, elapsedMs) {
|
|
700
|
+
const code = herdrErrorCode(exec.lastError);
|
|
701
|
+
if (code != null) return code === "timeout";
|
|
702
|
+
if (elapsedMs < timeoutMs * HERDR_WAIT_ELAPSED_RATIO) return false;
|
|
703
|
+
return herdrMuxAdapter.paneExists(exec, target);
|
|
704
|
+
}
|
|
705
|
+
function herdrErrorCode(reason) {
|
|
706
|
+
if (!reason) return void 0;
|
|
707
|
+
try {
|
|
708
|
+
return nonEmpty(JSON.parse(reason)?.error?.code);
|
|
709
|
+
} catch {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* A successful `pane wait-output` envelope: the snapshot it matched in, and the line it matched on.
|
|
715
|
+
*
|
|
716
|
+
* `matched` is `true` by construction — herdr exits 0 only on a match, so reaching here IS the match;
|
|
717
|
+
* the parse only fills in the evidence. Defensive for the same reason `parsePaneRecord` is: a herdr
|
|
718
|
+
* build that reshapes the envelope degrades to a match with no snapshot, never to a failed wait.
|
|
719
|
+
*/
|
|
720
|
+
function parseWaitOutput(out) {
|
|
721
|
+
let text;
|
|
722
|
+
let line;
|
|
723
|
+
try {
|
|
724
|
+
const result = JSON.parse(out)?.result;
|
|
725
|
+
text = nonEmpty(result?.read?.text);
|
|
726
|
+
line = nonEmpty(result?.matched_line);
|
|
727
|
+
} catch {}
|
|
728
|
+
return {
|
|
729
|
+
matched: true,
|
|
730
|
+
output: text ?? "",
|
|
731
|
+
...line != null ? { matchedLine: line } : {}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
440
735
|
* The pane's workspace and tab, or a throw — so `focus` never issues a workspace/tab switch against a
|
|
441
736
|
* pane it couldn't actually resolve.
|
|
442
737
|
*/
|
|
@@ -778,6 +1073,9 @@ const tmuxMuxAdapter = {
|
|
|
778
1073
|
if (opts?.lines != null) args.push("-S", `-${opts.lines}`);
|
|
779
1074
|
return exec("tmux", args) ?? "";
|
|
780
1075
|
},
|
|
1076
|
+
waitForOutput(exec, target, opts) {
|
|
1077
|
+
return pollForOutput(tmuxMuxAdapter, exec, target, opts);
|
|
1078
|
+
},
|
|
781
1079
|
focus(exec, target) {
|
|
782
1080
|
const { sessionName, windowId } = parsePaneLocation(exec("tmux", [
|
|
783
1081
|
"list-panes",
|
|
@@ -1018,6 +1316,7 @@ function splitOpenReport(out, command) {
|
|
|
1018
1316
|
* the same thing without first querying the region's size.
|
|
1019
1317
|
*/
|
|
1020
1318
|
function toTmuxSize(ratio) {
|
|
1319
|
+
assertRatioInRange(ratio);
|
|
1021
1320
|
return `${Math.round((1 - ratio) * 100)}%`;
|
|
1022
1321
|
}
|
|
1023
1322
|
/**
|
|
@@ -1211,6 +1510,9 @@ function createWeztermAdapter(deps) {
|
|
|
1211
1510
|
if (opts?.lines != null) args.push("--start-line", String(-opts.lines));
|
|
1212
1511
|
return exec("wezterm", args) ?? "";
|
|
1213
1512
|
},
|
|
1513
|
+
waitForOutput(exec, target, opts) {
|
|
1514
|
+
return pollForOutput(adapter, exec, target, opts);
|
|
1515
|
+
},
|
|
1214
1516
|
focus(exec, target) {
|
|
1215
1517
|
exec("wezterm", [
|
|
1216
1518
|
"cli",
|
|
@@ -1326,6 +1628,7 @@ function weztermCwd(cwd) {
|
|
|
1326
1628
|
* probe note, #47) — the same inversion tmux's `-l` needs, unlike herdr's pass-through.
|
|
1327
1629
|
*/
|
|
1328
1630
|
function toWeztermSize(ratio) {
|
|
1631
|
+
assertRatioInRange(ratio);
|
|
1329
1632
|
return String(Math.round((1 - ratio) * 100));
|
|
1330
1633
|
}
|
|
1331
1634
|
/**
|
|
@@ -1535,6 +1838,9 @@ function createZellijAdapter(deps) {
|
|
|
1535
1838
|
target.id
|
|
1536
1839
|
]) ?? "";
|
|
1537
1840
|
},
|
|
1841
|
+
waitForOutput(exec, target, opts) {
|
|
1842
|
+
return pollForOutput(adapter, exec, target, opts);
|
|
1843
|
+
},
|
|
1538
1844
|
focus(exec, target) {
|
|
1539
1845
|
exec("zellij", [
|
|
1540
1846
|
"action",
|
|
@@ -1891,6 +2197,15 @@ function isStaged(visible, message) {
|
|
|
1891
2197
|
* A pane that no longer exists is rejected up front rather than retried: a gone pane and a booting
|
|
1892
2198
|
* one both read back empty, so without the liveness probe the retry loop reports a dead peer as
|
|
1893
2199
|
* "never took the turn" — a boot-race shape — and buries the real cause.
|
|
2200
|
+
*
|
|
2201
|
+
* **Not built on `waitForOutput`, deliberately.** The two look alike and wait on opposite conditions:
|
|
2202
|
+
* `waitForOutput` returns when a pattern APPEARS anywhere in the snapshot, while nudge returns when the
|
|
2203
|
+
* message DISAPPEARS from the input box at the bottom (`isStaged`) — a negative, position-sensitive
|
|
2204
|
+
* condition the wait primitive cannot express, and one that must not be satisfied by the same text
|
|
2205
|
+
* sitting up in the transcript, which is exactly where a submitted message ends up. Nor is the loop body
|
|
2206
|
+
* the same: nudge does not merely observe between polls, it re-submits, so its "poll" is a corrective
|
|
2207
|
+
* action with its own attempt budget rather than a read. What the two DO share is the liveness rule —
|
|
2208
|
+
* a gone pane throws instead of being reported as a quiet one — and `pollForOutput` adopts it from here.
|
|
1894
2209
|
*/
|
|
1895
2210
|
async function nudge(adapter, exec, target, message, opts = {}) {
|
|
1896
2211
|
const attempts = opts.attempts ?? DEFAULT_ATTEMPTS;
|
|
@@ -1978,6 +2293,7 @@ function resolveMux(env, deps) {
|
|
|
1978
2293
|
sendKeys: (target, keys, d) => raw.sendKeys(pick(d), target, keys),
|
|
1979
2294
|
submit: (target, text, d) => raw.submit(pick(d), target, text),
|
|
1980
2295
|
read: (target, opts, d) => raw.read(pick(d), target, opts),
|
|
2296
|
+
waitForOutput: (target, opts, d) => raw.waitForOutput(pick(d), target, opts),
|
|
1981
2297
|
focus: (target, d) => raw.focus(pick(d), target),
|
|
1982
2298
|
teardown: (target, d) => raw.teardown(pick(d), target),
|
|
1983
2299
|
paneExists: (target, d) => raw.paneExists(pick(d), target),
|
|
@@ -1985,9 +2301,13 @@ function resolveMux(env, deps) {
|
|
|
1985
2301
|
listPanes: (d) => raw.listPanes(pick(d)),
|
|
1986
2302
|
nudge: (target, message, opts, d) => nudge(raw, pick(d), target, message, opts),
|
|
1987
2303
|
...raw.worktree ? { worktree: bindWorktree(raw.worktree, pick) } : {},
|
|
1988
|
-
...raw.regions ? { regions: bindRegions(raw.regions, pick) } : {}
|
|
2304
|
+
...raw.regions ? { regions: bindRegions(raw.regions, pick) } : {},
|
|
2305
|
+
...raw.agentLifecycle ? { agentLifecycle: bindAgentLifecycle(raw.agentLifecycle, pick) } : {}
|
|
1989
2306
|
};
|
|
1990
2307
|
}
|
|
2308
|
+
function bindAgentLifecycle(agent, pick) {
|
|
2309
|
+
return { waitForState: (target, opts, d) => agent.waitForState(pick(d), target, opts) };
|
|
2310
|
+
}
|
|
1991
2311
|
function bindWorktree(wt, pick) {
|
|
1992
2312
|
return {
|
|
1993
2313
|
createInWorkspace: (opts, d) => wt.createInWorkspace(pick(d), opts),
|
|
@@ -2020,6 +2340,6 @@ function callerPane(adapter, env) {
|
|
|
2020
2340
|
return self && self.mux === adapter.name ? { id: self.pane } : void 0;
|
|
2021
2341
|
}
|
|
2022
2342
|
//#endregion
|
|
2023
|
-
export {
|
|
2343
|
+
export { DEFAULT_WAIT_POLL_MS as _, nudge as a, pollForOutput as b, createZellijAdapter as c, weztermMuxAdapter as d, nodeNewId as f, herdrMuxAdapter as g, tmuxMuxAdapter as h, isStaged as i, zellijMuxAdapter as l, TMUX_WORKSPACE_GROUP_OPTION as m, resolveMux as n, currentPane as o, TMUX_TAB_NAME_OPTION as p, resolveMuxAdapter as r, probeMultiplexer as s, callerPane as t, createWeztermAdapter as u, assertWaitPattern as v, envFallback as x, matchWaitPattern as y };
|
|
2024
2344
|
|
|
2025
|
-
//# sourceMappingURL=backend-
|
|
2345
|
+
//# sourceMappingURL=backend-Cif257ji.mjs.map
|