agent-coord-mcp 0.26.19 → 0.26.20
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/README.md +82 -0
- package/dist/capabilities.js +57 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/server.js +21 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/records.js +242 -64
- package/dist/tools/records.js.map +1 -1
- package/dist/tools/registry.js +34 -5
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/stall.js +2 -1
- package/dist/tools/stall.js.map +1 -1
- package/dist/tools/transport.js +82 -42
- package/dist/tools/transport.js.map +1 -1
- package/dist/tools/work.js +95 -3
- package/dist/tools/work.js.map +1 -1
- package/dist/transports/config.js +82 -0
- package/dist/transports/config.js.map +1 -0
- package/dist/transports/index.js +113 -0
- package/dist/transports/index.js.map +1 -0
- package/dist/transports/tmux.js +140 -0
- package/dist/transports/tmux.js.map +1 -0
- package/dist/transports/types.js +86 -0
- package/dist/transports/types.js.map +1 -0
- package/hooks/peek-coord.mjs +0 -0
- package/hooks/tmux-pusher.mjs +33 -3
- package/package.json +14 -11
- package/scripts/coord-attention-clock.mjs +0 -0
- package/scripts/coord-node.sh +0 -0
- package/scripts/coord-stall-clock.mjs +0 -0
- package/scripts/coord-token.mjs +0 -0
- package/scripts/probe-tmux-liveness.sh +0 -0
- package/scripts/spawn-agent.sh +0 -0
- package/scripts/stop-agent.sh +0 -0
- package/scripts/typed-record-stats.mjs +0 -0
- package/src/capabilities.ts +104 -1
- package/src/server.ts +21 -0
- package/src/tools/records.ts +221 -34
- package/src/tools/registry.ts +36 -5
- package/src/tools/shared.ts +12 -36
- package/src/tools/stall.ts +2 -1
- package/src/tools/transport.ts +96 -43
- package/src/tools/work.ts +95 -3
- package/src/transports/config.ts +110 -0
- package/src/transports/index.ts +126 -0
- package/src/transports/tmux.ts +177 -0
- package/src/transports/types.ts +201 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE TMUX TRANSPORT — every `tmux` shell-out in this package, in one file.
|
|
3
|
+
*
|
|
4
|
+
* Before this, `spawnSync("tmux", …)` appeared at 7 sites in `tools/transport.ts`
|
|
5
|
+
* and the `has-session` caveat below was written out TWICE, verbatim, at two of
|
|
6
|
+
* them. A rule duplicated in two comments is a rule that will be re-derived
|
|
7
|
+
* wrongly at the third site somebody adds.
|
|
8
|
+
*
|
|
9
|
+
* WHAT THIS FILE DOES NOT DO, on purpose: spawn or reap the pusher process,
|
|
10
|
+
* read receipts, or schedule reminders. Delivery is a second process for THIS
|
|
11
|
+
* transport and will not be for a socket one, so that machinery stays with the
|
|
12
|
+
* tools that own it and reaches this file through `TmuxHost` below. An interface
|
|
13
|
+
* that baked in a pusher could not host an implementation that has none.
|
|
14
|
+
*/
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import type { ControlCommand, Liveness, Transport, TransportMarker, TransportKind } from "./types.js";
|
|
17
|
+
import { TMUX_PUSH, isLocallyProbeable, isTmuxKind, targetOf } from "./types.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The parts of delivery that belong to the PROCESS layer, not to tmux.
|
|
21
|
+
*
|
|
22
|
+
* Injected rather than imported to keep the dependency pointing one way:
|
|
23
|
+
* `tools/transport.ts` owns pusher spawn, receipts and marker files, and hands
|
|
24
|
+
* them here. Importing them back would make this module and that one mutually
|
|
25
|
+
* dependent, and a cycle is how "one place for the literal" quietly becomes two.
|
|
26
|
+
*/
|
|
27
|
+
export type TmuxHost = {
|
|
28
|
+
attach(args: {
|
|
29
|
+
agentId: string;
|
|
30
|
+
target?: string;
|
|
31
|
+
includeRoom?: boolean;
|
|
32
|
+
allowlist?: string[];
|
|
33
|
+
debounceMs?: number;
|
|
34
|
+
}): Promise<TransportMarker>;
|
|
35
|
+
detach(agentId: string): Promise<void>;
|
|
36
|
+
push(marker: TransportMarker, text: string): Promise<{ delivered: boolean; error?: string }>;
|
|
37
|
+
sendControl(marker: TransportMarker, cmd: ControlCommand): Promise<{ ok: boolean; error?: string }>;
|
|
38
|
+
/** Is the pusher process behind this marker still running on this host? */
|
|
39
|
+
pusherAlive(marker: TransportMarker): boolean;
|
|
40
|
+
/** Stop a wedged pusher. Returns false if it could not be signalled. */
|
|
41
|
+
killPusher(marker: TransportMarker): boolean;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** `tmux -V` — is tmux on this host at all? */
|
|
45
|
+
export function tmuxAvailable(): boolean {
|
|
46
|
+
return spawnSync("tmux", ["-V"]).status === 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* DOES THIS TARGET EXIST? The only tmux probe with discriminating power.
|
|
51
|
+
*
|
|
52
|
+
* `has-session` VALIDATES THE TARGET; `display-message -p -t <target> "ok"`
|
|
53
|
+
* DOES NOT — tmux exits 0 for any target, including a pane killed a moment ago,
|
|
54
|
+
* so that probe had ZERO discriminating power and reported every dead pane
|
|
55
|
+
* alive. Pinned to the BEHAVIOUR, not a version: measured identical on tmux
|
|
56
|
+
* 3.6b, 3.7b. A version-pinned claim rots on the next upgrade.
|
|
57
|
+
*
|
|
58
|
+
* Positive control, both directions, re-run on tmux 3.7b while extracting this:
|
|
59
|
+
* bogus target `%99999` -> has-session exit 1, display-message exit 0; live pane
|
|
60
|
+
* `%8` -> both exit 0. The wrong probe is wrong in only one direction, which is
|
|
61
|
+
* why it survived: it never reports a live pane dead.
|
|
62
|
+
*/
|
|
63
|
+
export function paneExists(target: string): boolean {
|
|
64
|
+
return spawnSync("tmux", ["has-session", "-t", target]).status === 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `has-session` with the failure text, for the one caller that reports it.
|
|
69
|
+
*
|
|
70
|
+
* `paneExists` is the boolean most sites want; attach quotes tmux's own stderr
|
|
71
|
+
* back to the user, and losing that text would make a refusal less useful while
|
|
72
|
+
* still typechecking — the exact class of silent regression this task guards.
|
|
73
|
+
*/
|
|
74
|
+
export function probePane(target: string): { exists: boolean; stderr: string } {
|
|
75
|
+
const probe = spawnSync("tmux", ["has-session", "-t", target]);
|
|
76
|
+
return { exists: probe.status === 0, stderr: (probe.stderr ?? "").toString().trim() };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** `tmux -V` output, or undefined when tmux is absent. */
|
|
80
|
+
export function tmuxVersion(): string | undefined {
|
|
81
|
+
const probe = spawnSync("tmux", ["-V"]);
|
|
82
|
+
if (probe.status !== 0) return undefined;
|
|
83
|
+
return (probe.stdout ?? "").toString().trim() || undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class TmuxTransport implements Transport {
|
|
87
|
+
readonly kind: TransportKind;
|
|
88
|
+
#host: TmuxHost;
|
|
89
|
+
|
|
90
|
+
constructor(host: TmuxHost, kind: TransportKind = TMUX_PUSH) {
|
|
91
|
+
this.#host = host;
|
|
92
|
+
this.kind = kind;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
available(): boolean {
|
|
96
|
+
return tmuxAvailable();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
attach(args: {
|
|
100
|
+
agentId: string;
|
|
101
|
+
target?: string;
|
|
102
|
+
includeRoom?: boolean;
|
|
103
|
+
allowlist?: string[];
|
|
104
|
+
debounceMs?: number;
|
|
105
|
+
}): Promise<TransportMarker> {
|
|
106
|
+
return this.#host.attach(args);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
detach(agentId: string): Promise<void> {
|
|
110
|
+
return this.#host.detach(agentId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
push(marker: TransportMarker, text: string): Promise<{ delivered: boolean; error?: string }> {
|
|
114
|
+
return this.#host.push(marker, text);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
sendControl(marker: TransportMarker, cmd: ControlCommand): Promise<{ ok: boolean; error?: string }> {
|
|
118
|
+
return this.#host.sendControl(marker, cmd);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* THREE ANSWERS, AND THE THIRD IS LOAD-BEARING.
|
|
123
|
+
*
|
|
124
|
+
* Each `unknown` below was previously a `continue` with a comment. The states
|
|
125
|
+
* are preserved exactly, because "we could not look" is not evidence of death
|
|
126
|
+
* and a reaper that cannot tell them apart kills live sessions:
|
|
127
|
+
*
|
|
128
|
+
* · not a tmux marker -> unknown (this transport cannot speak for it)
|
|
129
|
+
* · remote (foreign host) -> unknown (no local pane; heartbeat decides)
|
|
130
|
+
* · no target recorded -> unknown (nothing to probe)
|
|
131
|
+
* · tmux missing on host -> unknown (the instrument is absent, not the pane)
|
|
132
|
+
* · target absent -> dead
|
|
133
|
+
* · pusher gone, pane alive -> dead, and it says which half failed
|
|
134
|
+
*/
|
|
135
|
+
async probe(marker: TransportMarker): Promise<Liveness> {
|
|
136
|
+
if (!isTmuxKind(marker.transport)) {
|
|
137
|
+
return { state: "unknown", reason: `transport "${marker.transport}" is not tmux` };
|
|
138
|
+
}
|
|
139
|
+
if (!isLocallyProbeable(marker.transport)) {
|
|
140
|
+
return {
|
|
141
|
+
state: "unknown",
|
|
142
|
+
reason: `${marker.transport} runs on another host${marker.host ? ` (${marker.host})` : ""} — no local pane to probe; liveness is heartbeat-based`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const target = targetOf(marker);
|
|
146
|
+
if (!target) return { state: "unknown", reason: "no target recorded on the marker" };
|
|
147
|
+
if (!tmuxAvailable()) return { state: "unknown", reason: "tmux is not available on this host" };
|
|
148
|
+
if (!paneExists(target)) return { state: "dead", reason: `pane ${target} does not exist` };
|
|
149
|
+
if (!this.#host.pusherAlive(marker)) {
|
|
150
|
+
return { state: "dead", reason: `pane ${target} is alive but its pusher (pid ${marker.pid}) is gone` };
|
|
151
|
+
}
|
|
152
|
+
return { state: "live" };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Reap pushers whose pane has gone, and say which ones could not be judged.
|
|
157
|
+
*
|
|
158
|
+
* `unprobeable` is NOT a residual list nobody reads — it is the distinction
|
|
159
|
+
* PRODUCTION_ROADMAP Phase 5.3 requires between "dead" and "cannot probe".
|
|
160
|
+
* An agent whose liveness is unknown is left alone and reported, never reaped.
|
|
161
|
+
*/
|
|
162
|
+
async reapWedged(markers: TransportMarker[]): Promise<{ reaped: string[]; unprobeable: string[] }> {
|
|
163
|
+
const reaped: string[] = [];
|
|
164
|
+
const unprobeable: string[] = [];
|
|
165
|
+
for (const marker of markers) {
|
|
166
|
+
const live = await this.probe(marker);
|
|
167
|
+
if (live.state === "unknown") {
|
|
168
|
+
unprobeable.push(marker.agentId);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (live.state === "live") continue;
|
|
172
|
+
if (this.#host.killPusher(marker)) reaped.push(marker.agentId);
|
|
173
|
+
else unprobeable.push(marker.agentId);
|
|
174
|
+
}
|
|
175
|
+
return { reaped, unprobeable };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE TRANSPORT SEAM (Phase 5.4 Task 2).
|
|
3
|
+
*
|
|
4
|
+
* This interface is DERIVED from the 12 `"tmux-push"` literals and the 7
|
|
5
|
+
* `spawnSync("tmux", …)` shell-outs that were spread across `tools/`, not
|
|
6
|
+
* designed top-down. Where the phase doc's sketch and the code disagreed, the
|
|
7
|
+
* code won — three times, each recorded below, because the gate for this task is
|
|
8
|
+
* that a running fleet cannot tell the difference.
|
|
9
|
+
*
|
|
10
|
+
* 1. `tmux-push-remote` IS A LIVE KIND. The doc's sketch had two kinds; the code
|
|
11
|
+
* has three. `registry.ts` gives remote markers heartbeat-based liveness
|
|
12
|
+
* because their pid is 0 on a foreign host, and `server.ts` documents the
|
|
13
|
+
* value as the wire contract for `scripts/coord-pusher.mjs`. A union without
|
|
14
|
+
* it silently reclassifies every remote agent as not-tmux.
|
|
15
|
+
* 2. THE MARKER HAS NINE FIELDS, NOT SIX. `scriptMtime`, `serverBuildMtime` and
|
|
16
|
+
* `rooms` are absent from the sketch and each one closes a defect that
|
|
17
|
+
* actually shipped. They are kept verbatim, including the rule they share:
|
|
18
|
+
* ABSENT MEANS UNKNOWN, NEVER "ON" — the same principle as `Liveness.unknown`
|
|
19
|
+
* below, one field deeper.
|
|
20
|
+
* 3. `target` IS ADDITIVE, NOT A RENAME. See `targetOf`.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Every transport value that can appear in a marker on disk.
|
|
25
|
+
*
|
|
26
|
+
* Not `string`: the whole defect being fixed is code branching on a literal
|
|
27
|
+
* instead of reading the field, and a `string` discriminant cannot tell the
|
|
28
|
+
* compiler that a branch was missed. Not a two-member union either — see (1).
|
|
29
|
+
*/
|
|
30
|
+
export const TMUX_PUSH = "tmux-push" as const;
|
|
31
|
+
export const TMUX_PUSH_REMOTE = "tmux-push-remote" as const;
|
|
32
|
+
export const HERDR = "herdr" as const;
|
|
33
|
+
|
|
34
|
+
export type TransportKind = typeof TMUX_PUSH | typeof TMUX_PUSH_REMOTE | typeof HERDR;
|
|
35
|
+
|
|
36
|
+
export const TRANSPORT_KINDS: readonly TransportKind[] = [TMUX_PUSH, TMUX_PUSH_REMOTE, HERDR] as const;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The tmux family. Both members are delivered by a pusher process typing into a
|
|
40
|
+
* pane; they differ in WHERE that pane is, which is why liveness splits below.
|
|
41
|
+
*
|
|
42
|
+
* THIS IS THE ONE PLACE THE TMUX LITERALS LIVE. Twelve call sites used to spell
|
|
43
|
+
* them; they now ask.
|
|
44
|
+
*/
|
|
45
|
+
const TMUX_KINDS = new Set<string>([TMUX_PUSH, TMUX_PUSH_REMOTE]);
|
|
46
|
+
|
|
47
|
+
/** Is this marker carried by the tmux family (local OR remote)? */
|
|
48
|
+
export function isTmuxKind(transport: string | undefined): boolean {
|
|
49
|
+
return transport !== undefined && TMUX_KINDS.has(transport);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Is this marker's pane on THIS host, so that a local probe means anything?
|
|
54
|
+
*
|
|
55
|
+
* The distinction every `marker.transport !== "tmux-push"` site was making by
|
|
56
|
+
* hand, with a comment explaining "remote = can't verify". Naming it stops the
|
|
57
|
+
* next person re-deriving it — and re-deriving it wrongly is how a remote agent
|
|
58
|
+
* gets reported dead because a pane that was never local did not answer.
|
|
59
|
+
*/
|
|
60
|
+
export function isLocallyProbeable(transport: string | undefined): boolean {
|
|
61
|
+
return transport === TMUX_PUSH;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Is liveness for this marker decided by the registry heartbeat rather than by a
|
|
66
|
+
* local pid? True only for the remote kind, whose pid is 0 on a foreign host.
|
|
67
|
+
*/
|
|
68
|
+
export function isRemoteTmuxKind(transport: string | undefined): boolean {
|
|
69
|
+
return transport === TMUX_PUSH_REMOTE;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type TransportMarker = {
|
|
73
|
+
agentId: string;
|
|
74
|
+
transport: string;
|
|
75
|
+
pid: number;
|
|
76
|
+
/**
|
|
77
|
+
* Transport-agnostic address: a tmux pane id, later a herdr session id.
|
|
78
|
+
*
|
|
79
|
+
* ADDITIVE AND DUAL-WRITTEN, not a rename of `tmuxTarget`. Measured before
|
|
80
|
+
* choosing: every marker on the live fleet's disk carries `tmuxTarget` and
|
|
81
|
+
* none carries `target`. The phase doc asks for a read-migration and its own
|
|
82
|
+
* Rollback Plan asks for no format change until the migration is proven on a
|
|
83
|
+
* live fleet; writing both satisfies each. A marker written with `target`
|
|
84
|
+
* alone is unreadable to the code a merge-revert restores, and that failure
|
|
85
|
+
* does not degrade — it silences every lane at once.
|
|
86
|
+
*/
|
|
87
|
+
target?: string;
|
|
88
|
+
/** The original field. Still written. See `target`. */
|
|
89
|
+
tmuxTarget?: string;
|
|
90
|
+
since: number;
|
|
91
|
+
/**
|
|
92
|
+
* Remote pushers run on a different machine; the local pid is meaningless,
|
|
93
|
+
* so we tag the host and use heartbeat-based liveness instead of pidAlive.
|
|
94
|
+
*/
|
|
95
|
+
host?: string;
|
|
96
|
+
/**
|
|
97
|
+
* mtime of the pusher script the daemon loaded into memory at spawn time
|
|
98
|
+
* (epoch ms). When the on-disk script is upgraded but the daemon isn't
|
|
99
|
+
* restarted, doctor() compares this to the current mtime to flag a stale
|
|
100
|
+
* pusher — the class of bug that silently dropped /clear /compact in v0.8.1.
|
|
101
|
+
* Absent on markers written by older versions (treated as "unknown, skip").
|
|
102
|
+
*/
|
|
103
|
+
scriptMtime?: number;
|
|
104
|
+
/**
|
|
105
|
+
* Build identity of the MCP server whose attach_agent stamped this marker.
|
|
106
|
+
* A marker stamped by a server predating the current on-disk build was
|
|
107
|
+
* written by attach/stamp logic the rebuild replaced. Absent means unknown.
|
|
108
|
+
*/
|
|
109
|
+
serverBuildMtime?: number;
|
|
110
|
+
/**
|
|
111
|
+
* Does this transport carry ROOM traffic, or DMs only?
|
|
112
|
+
*
|
|
113
|
+
* ABSENT MEANS UNKNOWN, NEVER "ON". A `--no-room` pusher used to look
|
|
114
|
+
* identical to a full one, so `status` said `attached: true` while an agent
|
|
115
|
+
* sat with its room feed off — that is how a worker missed its channel
|
|
116
|
+
* traffic. A marker from an older pusher cannot tell us, and reporting an
|
|
117
|
+
* unasked question as full capability is the defect this field removes.
|
|
118
|
+
*/
|
|
119
|
+
rooms?: boolean;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Read a marker's address, preferring the generic field and falling back to the
|
|
124
|
+
* tmux-specific one.
|
|
125
|
+
*
|
|
126
|
+
* This is the read half of the dual-write. Call it rather than touching either
|
|
127
|
+
* field: a consumer that reads only `tmuxTarget` goes blind the day a herdr
|
|
128
|
+
* marker appears, and one that reads only `target` is blind to every marker on
|
|
129
|
+
* disk today.
|
|
130
|
+
*/
|
|
131
|
+
export function targetOf(marker: Pick<TransportMarker, "target" | "tmuxTarget">): string | undefined {
|
|
132
|
+
return marker.target ?? marker.tmuxTarget;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Stamp an address into BOTH fields, so the marker is legible to the current
|
|
137
|
+
* code and to whatever a revert restores.
|
|
138
|
+
*/
|
|
139
|
+
export function withTarget<T extends object>(marker: T, target: string | undefined): T & { target?: string; tmuxTarget?: string } {
|
|
140
|
+
if (target === undefined) return marker;
|
|
141
|
+
return { ...marker, target, tmuxTarget: target };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* THE THIRD STATE IS NOT A COURTESY, IT IS THE POINT.
|
|
146
|
+
*
|
|
147
|
+
* "Cannot probe" must stay expressible and distinct from "dead": a transport
|
|
148
|
+
* that cannot answer is not evidence of a dead agent, and collapsing the two
|
|
149
|
+
* reaps live sessions. PRODUCTION_ROADMAP Phase 5.3 states the rule; the
|
|
150
|
+
* wedged-pusher reaping in `doctor` depends on it.
|
|
151
|
+
*/
|
|
152
|
+
export type Liveness =
|
|
153
|
+
| { state: "live" }
|
|
154
|
+
| { state: "dead"; reason: string }
|
|
155
|
+
| { state: "unknown"; reason: string };
|
|
156
|
+
|
|
157
|
+
/** Keystroke-shaped commands a transport must deliver to an interactive pane. */
|
|
158
|
+
export const CONTROL_COMMANDS = ["clear", "compact", "reload-skills"] as const;
|
|
159
|
+
export type ControlCommand = (typeof CONTROL_COMMANDS)[number];
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* What a transport must be able to do, stated as what the call sites ask for.
|
|
163
|
+
*
|
|
164
|
+
* TWO THINGS DELIBERATELY NOT DESIGNED OUT, both established by the Task 1
|
|
165
|
+
* spike and both invisible in a type:
|
|
166
|
+
*
|
|
167
|
+
* · KEY NAMES ARE NOT PORTABLE. `ctrl+u` is accepted by herdr while `C-u` and
|
|
168
|
+
* `ctrl-u` are rejected. `sendControl` therefore takes a ControlCommand — an
|
|
169
|
+
* INTENT — and never a key string. A transport that received opaque key names
|
|
170
|
+
* would mistranslate them silently, which is the one failure mode a keystroke
|
|
171
|
+
* API cannot report.
|
|
172
|
+
* · DELIVERY IS A SECOND PROCESS, BUT ONLY FOR SOME TRANSPORTS. tmux spawns a
|
|
173
|
+
* pusher per agent; a socket transport replaces both the pusher and the pane.
|
|
174
|
+
* So nothing here mentions a pusher, a pid or a pane: `attach` returns a
|
|
175
|
+
* marker and `probe` interprets one. An interface that assumed a pusher could
|
|
176
|
+
* not host an implementation that has none.
|
|
177
|
+
*/
|
|
178
|
+
export interface Transport {
|
|
179
|
+
readonly kind: TransportKind;
|
|
180
|
+
|
|
181
|
+
/** Is this transport usable on this host at all? (`tmux -V` for the family.) */
|
|
182
|
+
available(): boolean;
|
|
183
|
+
|
|
184
|
+
attach(args: {
|
|
185
|
+
agentId: string;
|
|
186
|
+
target?: string;
|
|
187
|
+
includeRoom?: boolean;
|
|
188
|
+
allowlist?: string[];
|
|
189
|
+
debounceMs?: number;
|
|
190
|
+
}): Promise<TransportMarker>;
|
|
191
|
+
|
|
192
|
+
detach(agentId: string): Promise<void>;
|
|
193
|
+
|
|
194
|
+
push(marker: TransportMarker, text: string): Promise<{ delivered: boolean; error?: string }>;
|
|
195
|
+
|
|
196
|
+
probe(marker: TransportMarker): Promise<Liveness>;
|
|
197
|
+
|
|
198
|
+
sendControl(marker: TransportMarker, cmd: ControlCommand): Promise<{ ok: boolean; error?: string }>;
|
|
199
|
+
|
|
200
|
+
reapWedged(markers: TransportMarker[]): Promise<{ reaped: string[]; unprobeable: string[] }>;
|
|
201
|
+
}
|