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.
Files changed (56) hide show
  1. package/README.md +82 -0
  2. package/dist/capabilities.js +57 -1
  3. package/dist/capabilities.js.map +1 -1
  4. package/dist/gated-head.js +130 -0
  5. package/dist/gated-head.js.map +1 -0
  6. package/dist/server.js +23 -0
  7. package/dist/server.js.map +1 -1
  8. package/dist/tools/queue-write.js +431 -0
  9. package/dist/tools/queue-write.js.map +1 -0
  10. package/dist/tools/records.js +406 -70
  11. package/dist/tools/records.js.map +1 -1
  12. package/dist/tools/registry.js +34 -5
  13. package/dist/tools/registry.js.map +1 -1
  14. package/dist/tools/shared.js.map +1 -1
  15. package/dist/tools/stall.js +2 -1
  16. package/dist/tools/stall.js.map +1 -1
  17. package/dist/tools/transport.js +82 -42
  18. package/dist/tools/transport.js.map +1 -1
  19. package/dist/tools/tree-provenance.js +107 -0
  20. package/dist/tools/tree-provenance.js.map +1 -0
  21. package/dist/tools/work.js +95 -3
  22. package/dist/tools/work.js.map +1 -1
  23. package/dist/transports/config.js +82 -0
  24. package/dist/transports/config.js.map +1 -0
  25. package/dist/transports/index.js +113 -0
  26. package/dist/transports/index.js.map +1 -0
  27. package/dist/transports/tmux.js +140 -0
  28. package/dist/transports/tmux.js.map +1 -0
  29. package/dist/transports/types.js +86 -0
  30. package/dist/transports/types.js.map +1 -0
  31. package/hooks/peek-coord.mjs +0 -0
  32. package/hooks/tmux-pusher.mjs +33 -3
  33. package/package.json +14 -11
  34. package/scripts/coord-attention-clock.mjs +0 -0
  35. package/scripts/coord-node.sh +0 -0
  36. package/scripts/coord-stall-clock.mjs +0 -0
  37. package/scripts/coord-token.mjs +0 -0
  38. package/scripts/probe-tmux-liveness.sh +0 -0
  39. package/scripts/spawn-agent.sh +0 -0
  40. package/scripts/stop-agent.sh +0 -0
  41. package/scripts/typed-record-stats.mjs +0 -0
  42. package/src/capabilities.ts +104 -1
  43. package/src/gated-head.ts +134 -0
  44. package/src/server.ts +29 -0
  45. package/src/tools/queue-write.ts +485 -0
  46. package/src/tools/records.ts +409 -40
  47. package/src/tools/registry.ts +36 -5
  48. package/src/tools/shared.ts +12 -36
  49. package/src/tools/stall.ts +2 -1
  50. package/src/tools/transport.ts +96 -43
  51. package/src/tools/tree-provenance.ts +136 -0
  52. package/src/tools/work.ts +95 -3
  53. package/src/transports/config.ts +110 -0
  54. package/src/transports/index.ts +126 -0
  55. package/src/transports/tmux.ts +177 -0
  56. 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
+ }