agent-coord-mcp 0.26.19 → 0.26.21
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/gated-head.js +130 -0
- package/dist/gated-head.js.map +1 -0
- package/dist/server.js +23 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/queue-write.js +431 -0
- package/dist/tools/queue-write.js.map +1 -0
- package/dist/tools/records.js +406 -70
- 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/tree-provenance.js +107 -0
- package/dist/tools/tree-provenance.js.map +1 -0
- 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/gated-head.ts +134 -0
- package/src/server.ts +29 -0
- package/src/tools/queue-write.ts +485 -0
- package/src/tools/records.ts +409 -40
- 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/tree-provenance.ts +136 -0
- 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,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
|
+
}
|