agent-coord-mcp 0.26.31 → 0.26.33

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.
@@ -0,0 +1,49 @@
1
+ /**
2
+ * ⟨q-927e28cd⟩ ADR-023 d4, NARROW SLICE — A CONSUMER READS A SEAT'S PANE THROUGH THE BUS.
3
+ *
4
+ * The console showed every herdr seat's pane as `410 "pane not found (session gone?)"` for a
5
+ * live seat, because it captured panes itself through tmux and a herdr pane is not a tmux
6
+ * pane. ADR-023 d4 says a consumer asks the bus rather than learning each transport; this is
7
+ * the one question it could not ask, because no bus verb returned what a pane shows.
8
+ *
9
+ * ⛔ READ THROUGH THE TRANSPORT, NEVER AROUND IT (the coordinator's GO, and ADR-023 d1). This
10
+ * module names no field of the marker: it hands the whole marker to `Transport.readPane`,
11
+ * which decides which pane it is, whether it is its own kind, and whether it is still there.
12
+ * qa counted 47 marker-internal reads across 5 modules outside src/transports/; this is not
13
+ * the 48th, and a test says so by reading this file.
14
+ *
15
+ * ⛔ READ-ONLY, INCLUDING OF THE STATE DIR. `loadLiveTransports` deletes markers it judges
16
+ * dead, which is a write; this reads the one marker file and deletes nothing. Whether the
17
+ * pane is gone is the transport's answer, and it answers UNKNOWN, never an empty screen.
18
+ */
19
+ import { z } from "zod";
20
+ import type { PaneRead, Transport, TransportMarker } from "../transports/types.js";
21
+ import { activeTransport } from "../transports/index.js";
22
+ import { readJson, transportFile } from "../store.js";
23
+
24
+ export const readPaneSchema = {
25
+ agentId: z.string().min(1),
26
+ /** The seat whose pane to read. */
27
+ targetAgentId: z.string().min(1),
28
+ lines: z.number().int().positive().max(2000).optional(),
29
+ /** Keep the terminal's styling (ANSI escapes) rather than plain text. */
30
+ colors: z.boolean().optional(),
31
+ };
32
+
33
+ export type ReadPaneResult = { ok: true; agentId: string } & PaneRead;
34
+
35
+ export async function readPaneTool(
36
+ args: { agentId: string; targetAgentId: string; lines?: number; colors?: boolean },
37
+ transport: Transport | null | undefined = activeTransport(),
38
+ ): Promise<ReadPaneResult> {
39
+ const agentId = args.targetAgentId;
40
+ const marker = await readJson<TransportMarker | null>(transportFile(agentId), null);
41
+ if (!marker) return { ok: true, agentId, state: "unknown", why: "no transport marker — the seat is not attached, so there is no pane on record to read" };
42
+ if (!transport) return { ok: true, agentId, state: "unknown", why: "no transport is wired in this server process, so nothing can read a pane" };
43
+ if (typeof transport.readPane !== "function") return { ok: true, agentId, state: "unknown", why: `the ${transport.kind} transport cannot read a pane from this host` };
44
+ try {
45
+ return { ok: true, agentId, ...(await transport.readPane(marker, { lines: args.lines, format: args.colors ? "ansi" : "text" })) };
46
+ } catch (e) {
47
+ return { ok: true, agentId, state: "unknown", why: `the transport threw while reading the pane: ${(e as Error).message}` };
48
+ }
49
+ }
@@ -34,7 +34,7 @@
34
34
  * same reason (identical evidence for a typo and a default).
35
35
  */
36
36
  import { spawnSync } from "node:child_process";
37
- import type { ControlCommand, Liveness, Transport, TransportKind, TransportMarker, TickReading, TickState } from "./types.js";
37
+ import type { ControlCommand, Liveness, PaneRead, Transport, TransportKind, TransportMarker, TickReading, TickState } from "./types.js";
38
38
  import { HERDR, TICK_READS_AS, TICK_STORED_AS, targetOf } from "./types.js";
39
39
  // ⟨q-dc83023a⟩ The chip grammar and the pane-unsafe byte class are single-sourced in hooks/, shared
40
40
  // with both pushers. `hooks/` ships beside `dist/`, so these resolve the same when installed.
@@ -587,6 +587,33 @@ export class HerdrTransport implements Transport {
587
587
  return { readable: false, why: `herdr reports agent_status "${status}" for pane ${target} — no agent state is published there, which is a MISSING signal, not a calm seat` };
588
588
  }
589
589
 
590
+ /**
591
+ * ⟨q-927e28cd⟩ THE SCREEN, FOR A CONSUMER — herdr's own `pane read`, the same read `push`
592
+ * verifies with. Same guards, in the same order, as `readTick`: a marker that is not herdr's,
593
+ * one with no target, or herdr absent answers UNKNOWN naming which; a pane herdr cannot find
594
+ * is UNKNOWN too — it is gone, and a gone pane has no screen, not an empty one.
595
+ */
596
+ async readPane(marker: TransportMarker, opts: { lines?: number; format?: "text" | "ansi" } = {}): Promise<PaneRead> {
597
+ return this.readPaneNow(marker, opts);
598
+ }
599
+
600
+ /** The whole rule, synchronous — `readPane` and the capabilities probe both call THIS, so the probe cannot pass while the verb behaves differently. */
601
+ readPaneNow(marker: TransportMarker, opts: { lines?: number; format?: "text" | "ansi" } = {}): PaneRead {
602
+ if (marker.transport !== HERDR) return { state: "unknown", why: `transport "${marker.transport}" is not herdr — this transport reads only its own panes` };
603
+ const target = targetOf(marker);
604
+ if (!target) return { state: "unknown", why: "no target recorded on the marker" };
605
+ const avail = this.availability();
606
+ if (!avail.available) return { state: "unknown", why: avail.reason };
607
+ const lines = Math.min(Math.max(Math.trunc(opts.lines ?? 200), 1), 2000);
608
+ const format = opts.format === "ansi" ? "ansi" : "text";
609
+ const read = this.#run(["pane", "read", target, "--source", "visible", "--lines", String(lines), "--format", format]);
610
+ if (!read.ok) {
611
+ if (read.error?.code === "pane_not_found") return { state: "unknown", why: `herdr reports pane ${target} not found — a gone pane has no screen to read` };
612
+ return { state: "unknown", why: `herdr could not read pane ${target}: ${read.error?.message ?? read.stderr}` };
613
+ }
614
+ return { state: "read", text: read.stdout, lines, format, source: `herdr pane ${target}` };
615
+ }
616
+
590
617
  /**
591
618
  * ⟨q-1c95f7d4⟩ Task 1's open question — PUBLISH, and it needs a receipt it does not get.
592
619
  *
@@ -249,6 +249,18 @@ export type TickReading =
249
249
  | { readable: true; state: TickState; source: string }
250
250
  | { readable: false; why: string };
251
251
 
252
+ /**
253
+ * ⟨q-927e28cd⟩ ADR-023 d4, narrow slice — WHAT A PANE SHOWS, READ THROUGH THE TRANSPORT.
254
+ *
255
+ * Two answers and no third: the screen, or UNKNOWN with the reason. A pane that is gone, a
256
+ * binary that is absent, a marker of another kind — each is `unknown`, never an empty screen
257
+ * and never a stale one: an empty string would read as "the seat is showing nothing", which
258
+ * is a claim about the seat that nothing measured.
259
+ */
260
+ export type PaneRead =
261
+ | { state: "read"; text: string; lines: number; format: "text" | "ansi"; source: string }
262
+ | { state: "unknown"; why: string };
263
+
252
264
  /** Keystroke-shaped commands a transport must deliver to an interactive pane. */
253
265
  export const CONTROL_COMMANDS = ["clear", "compact", "reload-skills"] as const;
254
266
  export type ControlCommand = (typeof CONTROL_COMMANDS)[number];
@@ -313,6 +325,18 @@ export interface Transport {
313
325
  */
314
326
  publishTick?(marker: TransportMarker, state: TickState, opts?: { message?: string; source?: string }): Promise<{ ok: boolean; error?: string }>;
315
327
 
328
+ /**
329
+ * ⟨q-927e28cd⟩ OPTIONAL, AND ITS ABSENCE IS AN ANSWER: a transport that cannot see the
330
+ * seat's screen from this host (tmux-push-remote: the pane is on another machine) does not
331
+ * implement it, and every caller reports `unknown` for it rather than guessing.
332
+ *
333
+ * ⛔ THE ONE PLACE A PANE IS READ FOR A CONSUMER. Callers pass the WHOLE marker and read no
334
+ * field of it — which pane, which kind, whether it is this transport's to read are all
335
+ * decided in here (ADR-023 d1). A caller that needed `target` to use this would be the
336
+ * forty-eighth marker-internal read qa counted outside src/transports/.
337
+ */
338
+ readPane?(marker: TransportMarker, opts?: { lines?: number; format?: "text" | "ansi" }): Promise<PaneRead>;
339
+
316
340
  /**
317
341
  * ⟨q-ec020f6a⟩(A) — OPTIONAL, and its absence is the honest answer for tmux (a pane's live
318
342
  * "session" concept is a herdr feature; tmux has no equivalent to re-check). Declaring the