agent-coord-mcp 0.26.26 → 0.26.28
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 +18 -12
- package/dist/bind.js +40 -0
- package/dist/bind.js.map +1 -0
- package/dist/capabilities.js +72 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/server.js +48 -15
- package/dist/server.js.map +1 -1
- package/dist/tools/herdr-tail.js +7 -1
- package/dist/tools/herdr-tail.js.map +1 -1
- package/dist/tools/stall.js +29 -1
- package/dist/tools/stall.js.map +1 -1
- package/dist/tools/transport.js +68 -10
- package/dist/tools/transport.js.map +1 -1
- package/dist/transports/config.js +42 -25
- package/dist/transports/config.js.map +1 -1
- package/dist/transports/herdr.js +2 -2
- package/dist/transports/herdr.js.map +1 -1
- package/hooks/push-cursor.mjs +22 -6
- package/hooks/submit.mjs +12 -1
- package/package.json +2 -1
- package/scripts/coord-stall-clock.mjs +26 -2
- package/src/bind.ts +40 -0
- package/src/capabilities.ts +77 -3
- package/src/server.ts +57 -18
- package/src/tools/herdr-tail.ts +18 -1
- package/src/tools/stall.ts +48 -3
- package/src/tools/transport.ts +62 -10
- package/src/transports/config.ts +56 -38
- package/src/transports/herdr.ts +9 -3
package/src/transports/config.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WHICH TRANSPORT THIS
|
|
2
|
+
* WHICH TRANSPORT A SEAT ON THIS MACHINE IS CONFIGURED TO USE (Phase 5.4 Task 3.1–3.2).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* ⟨q-d404a6f6⟩ THIS IS MACHINE-SCOPED, NOT FLEET-WIDE — the file lives at
|
|
5
|
+
* `$AGENT_COORD_DIR/config.json`, a path local to ONE box. A fleet now spans
|
|
6
|
+
* machines: `tmux-push-remote` exists precisely for a seat on another host, and
|
|
7
|
+
* that seat never reads THIS machine's config.json — it has (or lacks) its own.
|
|
8
|
+
* Calling this "the fleet's statement" was false and load-bearing: it read as
|
|
9
|
+
* an argument against ANY compiled default, when the argument that actually
|
|
10
|
+
* holds (below) is narrower — against a default picked HERE being assumed
|
|
11
|
+
* correct on a DIFFERENT machine. Per-process (read ONCE at startup, no
|
|
12
|
+
* per-seat branching in `send_command` or `attach` for the seats ON THIS BOX)
|
|
13
|
+
* is still exactly right; "whole-fleet" was the wrong word for it.
|
|
9
14
|
*
|
|
10
15
|
* ⛔ AN UNKNOWN VALUE REFUSES AT STARTUP. It does not fall back to anything.
|
|
11
16
|
*
|
|
12
17
|
* The reason is not tidiness. A SILENT FALLBACK AND A CORRECT DEFAULT PRODUCE
|
|
13
|
-
* IDENTICAL EVIDENCE: both give you a
|
|
18
|
+
* IDENTICAL EVIDENCE: both give you a seat on some transport with nothing in any
|
|
14
19
|
* log, so a typo in the config reads exactly like a deliberate default, and the
|
|
15
20
|
* person who typed `heardr` spends the afternoon asking why their transport
|
|
16
21
|
* change did nothing. Refusing is louder than the bug it prevents.
|
|
@@ -20,7 +25,7 @@
|
|
|
20
25
|
* removing the fallback would have meant every unconfigured session refused
|
|
21
26
|
* with "unknown transport 'tmux-push'" — a config-file bug wearing a runtime
|
|
22
27
|
* bug's clothes. Picking a new implicit default (herdr, say) would be correct
|
|
23
|
-
* on THIS
|
|
28
|
+
* on THIS machine today and wrong on the next one that doesn't run herdr —
|
|
24
29
|
* the exact silent-guess failure ⟨q-e439e4ad⟩ spent a day fixing, moved one
|
|
25
30
|
* layer up. So: zero configuration REFUSES, naming the kinds that exist.
|
|
26
31
|
*/
|
|
@@ -29,26 +34,27 @@ import path from "node:path";
|
|
|
29
34
|
import { ROOT } from "../store.js";
|
|
30
35
|
import { TRANSPORT_KINDS, type TransportKind } from "./types.js";
|
|
31
36
|
|
|
32
|
-
/** `$AGENT_COORD_DIR/config.json
|
|
37
|
+
/** `$AGENT_COORD_DIR/config.json` — MACHINE-scoped, not fleet-wide: see the file header. */
|
|
33
38
|
export const TRANSPORT_CONFIG_FILE = path.join(ROOT, "config.json");
|
|
34
39
|
export const TRANSPORT_ENV_VAR = "AGENT_COORD_TRANSPORT";
|
|
35
40
|
|
|
36
41
|
/**
|
|
37
42
|
* PRECEDENCE, documented here because 3.2 asks for a decision and not a
|
|
38
|
-
* preference: **config file > env
|
|
43
|
+
* preference: **machine config file > env**. (⟨q-ec020f6a⟩: no default follows
|
|
44
|
+
* either — see the file header.)
|
|
39
45
|
*
|
|
40
|
-
* The file wins because it is
|
|
41
|
-
* on disk where every seat reads the same bytes, and a wrong
|
|
42
|
-
* corrected in one place. An env var is per-process: it is the
|
|
43
|
-
* one seat to deviate deliberately (a test, a bisect), and the
|
|
44
|
-
*
|
|
45
|
-
* process. So the narrower, less visible
|
|
46
|
-
* `source` is reported so a surprising
|
|
47
|
-
* rather than guessed at.
|
|
46
|
+
* The file wins because it is this MACHINE's statement and is reviewable — it
|
|
47
|
+
* sits on disk where every seat ON THIS BOX reads the same bytes, and a wrong
|
|
48
|
+
* value can be corrected in one place. An env var is per-process: it is the
|
|
49
|
+
* right tool for one seat to deviate deliberately (a test, a bisect), and the
|
|
50
|
+
* wrong tool for stating what this machine's seats do by default, because
|
|
51
|
+
* nothing can see it from outside that process. So the narrower, less visible
|
|
52
|
+
* source loses to the broader one, and `source` is reported so a surprising
|
|
53
|
+
* answer can be traced to its origin rather than guessed at.
|
|
48
54
|
*/
|
|
49
55
|
export type ConfiguredTransport = {
|
|
50
56
|
kind: TransportKind;
|
|
51
|
-
source: "config" | "env";
|
|
57
|
+
source: "machine-config" | "env";
|
|
52
58
|
/** Where the value came from, for an error message a human can act on. */
|
|
53
59
|
origin: string;
|
|
54
60
|
};
|
|
@@ -73,12 +79,38 @@ function asKind(value: unknown, origin: string): TransportKind {
|
|
|
73
79
|
let cached: ConfiguredTransport | undefined;
|
|
74
80
|
|
|
75
81
|
/**
|
|
76
|
-
*
|
|
82
|
+
* ⭐ ⟨q-9e0072b3⟩ THE PRECEDENCE DECISION, PULLED OUT SO IT CAN BE CALLED WITHOUT TOUCHING
|
|
83
|
+
* DISK OR `process.env`. `configuredTransport()` below is the only caller that supplies real
|
|
84
|
+
* values read from THIS machine; anything else — a test, or a capability probe that must prove
|
|
85
|
+
* a property of the CODE rather than a fact about one machine's config.json — can hand this
|
|
86
|
+
* CONSTRUCTED values instead. That is what makes it possible to probe "does the machine-config/
|
|
87
|
+
* env vocabulary exist and resolve correctly" without the probe itself becoming an undeclared
|
|
88
|
+
* read of a mutable, machine-local baseline (`check-baseline-declared` correctly caught the
|
|
89
|
+
* first version of that probe calling `configuredTransport()` directly for exactly this reason).
|
|
90
|
+
*/
|
|
91
|
+
export function resolveConfiguredTransport(input: { fileHasTransport: boolean; fileTransport: unknown; env: string | undefined }): ConfiguredTransport {
|
|
92
|
+
if (input.fileHasTransport) {
|
|
93
|
+
return { kind: asKind(input.fileTransport, `${TRANSPORT_CONFIG_FILE} ("transport")`), source: "machine-config", origin: TRANSPORT_CONFIG_FILE };
|
|
94
|
+
}
|
|
95
|
+
if (input.env !== undefined && input.env !== "") {
|
|
96
|
+
return { kind: asKind(input.env, `$${TRANSPORT_ENV_VAR}`), source: "env", origin: `$${TRANSPORT_ENV_VAR}` };
|
|
97
|
+
}
|
|
98
|
+
throw new Error(
|
|
99
|
+
`[agent-coord-mcp] no transport configured — set $${TRANSPORT_ENV_VAR} or ${TRANSPORT_CONFIG_FILE} ` +
|
|
100
|
+
`("transport") to one of: ${TRANSPORT_KINDS.join(", ")}. ` +
|
|
101
|
+
`REFUSING rather than picking one: a default that is right for this machine today is wrong for the ` +
|
|
102
|
+
`next one that doesn't have it, and the two failures leave identical evidence.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolve THIS MACHINE's configured transport. Throws on an unknown value — call it once at
|
|
77
108
|
* startup so the refusal lands before any agent attaches.
|
|
78
109
|
*/
|
|
79
110
|
export function configuredTransport(): ConfiguredTransport {
|
|
80
111
|
if (cached) return cached;
|
|
81
112
|
|
|
113
|
+
let fileTransport: unknown;
|
|
82
114
|
if (existsSync(TRANSPORT_CONFIG_FILE)) {
|
|
83
115
|
let parsed: unknown;
|
|
84
116
|
try {
|
|
@@ -90,28 +122,14 @@ export function configuredTransport(): ConfiguredTransport {
|
|
|
90
122
|
throw new Error(
|
|
91
123
|
`[agent-coord-mcp] ${TRANSPORT_CONFIG_FILE} is unreadable (${(e as Error).message}). ` +
|
|
92
124
|
`REFUSING rather than treating it as absent: a file that exists and cannot be read is not the ` +
|
|
93
|
-
`same as no file, and defaulting here would hide a stated intent behind a
|
|
125
|
+
`same as no file, and defaulting here would hide a stated intent behind a seat that looked healthy.`,
|
|
94
126
|
);
|
|
95
127
|
}
|
|
96
|
-
|
|
97
|
-
if (raw !== undefined) {
|
|
98
|
-
cached = { kind: asKind(raw, `${TRANSPORT_CONFIG_FILE} ("transport")`), source: "config", origin: TRANSPORT_CONFIG_FILE };
|
|
99
|
-
return cached;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const env = process.env[TRANSPORT_ENV_VAR];
|
|
104
|
-
if (env !== undefined && env !== "") {
|
|
105
|
-
cached = { kind: asKind(env, `$${TRANSPORT_ENV_VAR}`), source: "env", origin: `$${TRANSPORT_ENV_VAR}` };
|
|
106
|
-
return cached;
|
|
128
|
+
fileTransport = (parsed as { transport?: unknown } | null)?.transport;
|
|
107
129
|
}
|
|
108
130
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
`("transport") to one of: ${TRANSPORT_KINDS.join(", ")}. ` +
|
|
112
|
-
`REFUSING rather than picking one: a default that is right for this fleet today is wrong for the ` +
|
|
113
|
-
`next machine that doesn't have it, and the two failures leave identical evidence.`,
|
|
114
|
-
);
|
|
131
|
+
cached = resolveConfiguredTransport({ fileHasTransport: fileTransport !== undefined, fileTransport, env: process.env[TRANSPORT_ENV_VAR] });
|
|
132
|
+
return cached;
|
|
115
133
|
}
|
|
116
134
|
|
|
117
135
|
/**
|
package/src/transports/herdr.ts
CHANGED
|
@@ -136,7 +136,13 @@ const LIVE_STATUSES = new Set(["idle", "working", "blocked", "done"]);
|
|
|
136
136
|
* held — NO key was sent (no ready box, a draft, a menu, or the pane unreadable).
|
|
137
137
|
* Safe to retry.
|
|
138
138
|
* typed-unconfirmed — the text was typed but did not show in the box; Enter was NOT sent.
|
|
139
|
-
* pending — the text is still in the box after every Enter.
|
|
139
|
+
* pending — the text is still in the box after every Enter. ⟨q-1ce5bc97⟩ step 1: this
|
|
140
|
+
* is the MOST recoverable non-delivery there is — the payload is KNOWN to be
|
|
141
|
+
* sitting, verified, in the target's own box, not lost and not ambiguous.
|
|
142
|
+
* `safeToRetry: false` here turned a transient misread into a PERMANENT drop:
|
|
143
|
+
* the tail's held-path (`herdr-tail.ts`) is what retries safely (it advances
|
|
144
|
+
* nothing on a hold, and stops the source rather than typing past it), so
|
|
145
|
+
* refusing that path was refusing the one recovery this outcome has. NOW true.
|
|
140
146
|
* unverified — Enter was sent and the screen after it does not prove submission.
|
|
141
147
|
*/
|
|
142
148
|
export type PushOutcome = "delivered" | "held" | "typed-unconfirmed" | "pending" | "unverified";
|
|
@@ -354,7 +360,7 @@ export class HerdrTransport implements Transport {
|
|
|
354
360
|
if (boxHoldsPayload(box, text)) continue;
|
|
355
361
|
return { delivered: false, verified: false, outcome: "unverified", safeToRetry: false, enters, error: `enter sent to ${target}, but the screen after it does not show the message submitted (${box.ready ? (box.draft ? "the box holds other text" : "the box is empty and the transcript gained no line carrying it") : box.reason})` };
|
|
356
362
|
}
|
|
357
|
-
return { delivered: false, verified: true, outcome: "pending", safeToRetry:
|
|
363
|
+
return { delivered: false, verified: true, outcome: "pending", safeToRetry: true, enters, error: `text still sitting unsubmitted in ${target}'s input after ${enters} enters` };
|
|
358
364
|
}
|
|
359
365
|
|
|
360
366
|
/** The screen, styled, so the ready-box reader can tell a ghost suggestion from a draft. null = unreadable. */
|
|
@@ -380,7 +386,7 @@ export class HerdrTransport implements Transport {
|
|
|
380
386
|
if (pending === false) return { delivered: true, verified: true, unguarded: true, outcome: "delivered", safeToRetry: false, enters };
|
|
381
387
|
if (pending === null) return { delivered: false, verified: false, unguarded: true, outcome: "unverified", safeToRetry: false, enters, error: `enter sent to ${target}, but the pane could not be read back to verify it` };
|
|
382
388
|
}
|
|
383
|
-
return { delivered: false, verified: true, unguarded: true, outcome: "pending", safeToRetry:
|
|
389
|
+
return { delivered: false, verified: true, unguarded: true, outcome: "pending", safeToRetry: true, enters, error: `text still sitting unsubmitted in ${target}'s input after ${enters} enters` };
|
|
384
390
|
}
|
|
385
391
|
|
|
386
392
|
/**
|