agent-coord-mcp 0.26.20 → 0.26.22

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 (68) hide show
  1. package/dist/capabilities.js +250 -2
  2. package/dist/capabilities.js.map +1 -1
  3. package/dist/closing-line.js +83 -0
  4. package/dist/closing-line.js.map +1 -0
  5. package/dist/commit-cite.js +55 -0
  6. package/dist/commit-cite.js.map +1 -0
  7. package/dist/gated-head.js +177 -0
  8. package/dist/gated-head.js.map +1 -0
  9. package/dist/server-spread.js +195 -0
  10. package/dist/server-spread.js.map +1 -0
  11. package/dist/server.js +4 -2
  12. package/dist/server.js.map +1 -1
  13. package/dist/store.js +32 -0
  14. package/dist/store.js.map +1 -1
  15. package/dist/tools/away.js +67 -7
  16. package/dist/tools/away.js.map +1 -1
  17. package/dist/tools/board-ref.js +44 -4
  18. package/dist/tools/board-ref.js.map +1 -1
  19. package/dist/tools/event-kinds.js +5 -1
  20. package/dist/tools/event-kinds.js.map +1 -1
  21. package/dist/tools/events.js +31 -2
  22. package/dist/tools/events.js.map +1 -1
  23. package/dist/tools/messaging.js +64 -6
  24. package/dist/tools/messaging.js.map +1 -1
  25. package/dist/tools/queue-write.js +431 -0
  26. package/dist/tools/queue-write.js.map +1 -0
  27. package/dist/tools/record-events.js +85 -5
  28. package/dist/tools/record-events.js.map +1 -1
  29. package/dist/tools/records.js +384 -33
  30. package/dist/tools/records.js.map +1 -1
  31. package/dist/tools/registry.js +52 -2
  32. package/dist/tools/registry.js.map +1 -1
  33. package/dist/tools/seat-build.js +173 -0
  34. package/dist/tools/seat-build.js.map +1 -0
  35. package/dist/tools/shared.js.map +1 -1
  36. package/dist/tools/stall.js +1095 -18
  37. package/dist/tools/stall.js.map +1 -1
  38. package/dist/tools/transport.js +21 -2
  39. package/dist/tools/transport.js.map +1 -1
  40. package/dist/tools/tree-provenance.js +107 -0
  41. package/dist/tools/tree-provenance.js.map +1 -0
  42. package/dist/tools/worktrees.js +14 -0
  43. package/dist/tools/worktrees.js.map +1 -1
  44. package/package.json +1 -1
  45. package/scripts/coord-attention-clock.mjs +2 -0
  46. package/scripts/coord-stall-clock.mjs +52 -11
  47. package/src/capabilities.ts +264 -2
  48. package/src/closing-line.ts +85 -0
  49. package/src/commit-cite.ts +58 -0
  50. package/src/gated-head.ts +236 -0
  51. package/src/server-spread.ts +233 -0
  52. package/src/server.ts +10 -2
  53. package/src/store.ts +32 -0
  54. package/src/tools/away.ts +82 -9
  55. package/src/tools/board-ref.ts +70 -3
  56. package/src/tools/event-kinds.ts +17 -1
  57. package/src/tools/events.ts +33 -2
  58. package/src/tools/messaging.ts +63 -6
  59. package/src/tools/queue-write.ts +485 -0
  60. package/src/tools/record-events.ts +78 -5
  61. package/src/tools/records.ts +423 -31
  62. package/src/tools/registry.ts +54 -3
  63. package/src/tools/seat-build.ts +194 -0
  64. package/src/tools/shared.ts +22 -0
  65. package/src/tools/stall.ts +1266 -23
  66. package/src/tools/transport.ts +21 -2
  67. package/src/tools/tree-provenance.ts +136 -0
  68. package/src/tools/worktrees.ts +13 -0
@@ -33,6 +33,7 @@ import { promises as fsp } from "node:fs";
33
33
  import { spawn, spawnSync } from "node:child_process";
34
34
  import { fileURLToPath } from "node:url";
35
35
  import { z } from "zod";
36
+ import { seatBuildOf, installedFrom, psReader } from "./seat-build.js";
36
37
  import path from "node:path";
37
38
  import {
38
39
  AGENTS_FILE,
@@ -157,12 +158,17 @@ export async function pingTool(args: { from: string; to: string; echo?: boolean
157
158
 
158
159
  let echoSent = false;
159
160
  if (args.echo && alive) {
160
- await sendMessageTool({
161
+ // ⟨q-138f4b78⟩ — the echo is an agent→agent DM, so past the typed-record
162
+ // cutover it MUST carry a record or the send is refused; and `echoSent`
163
+ // reports what the send SAID, never that a call was made — measured on the
164
+ // cutover morning: this reported true while nothing was written.
165
+ const sent = await sendMessageTool({
161
166
  from: args.from,
162
167
  to: args.to,
163
168
  text: `PING: echo requested by ${args.from} — DM back if responsive.`,
169
+ record: { type: "fyi", payload: { summary: `PING echo requested by ${args.from}` } },
164
170
  });
165
- echoSent = true;
171
+ echoSent = sent.ok === true;
166
172
  }
167
173
 
168
174
  return {
@@ -899,9 +905,22 @@ export async function statusTool(args: { agentId: string }) {
899
905
  // is the case every other instrument here reports as if the fleet were one
900
906
  // thing.
901
907
  const capabilities = await capabilitiesTool();
908
+ // ⟨q-8a3f1c05⟩ — THIS SEAT'S BUILD STATE, both halves: the pusher from its marker's
909
+ // pid via ps (keyed by --agent), the server from THIS process — status is called
910
+ // by the seat about itself, which is the one case the server half is knowable.
911
+ const id = resolveServerIdentity();
912
+ const installed = installedFrom(id.path);
913
+ const build = seatBuildOf({
914
+ agentId: args.agentId,
915
+ marker: transport,
916
+ installed,
917
+ ps: psReader,
918
+ server: { pid: process.pid, startedAt: Date.now() - Math.round(process.uptime() * 1000), buildMtime: installed.buildMtime },
919
+ });
902
920
 
903
921
  return {
904
922
  agentId: args.agentId,
923
+ build,
905
924
  registered: !!entry,
906
925
  entry,
907
926
  attached: !!transport,
@@ -0,0 +1,136 @@
1
+ /**
2
+ * ⟨q-c1af2db3⟩ — WHICH TREE DID THIS VERB JUST READ, AND HOW OLD IS IT?
3
+ *
4
+ * ⛔ THE BUG THIS EXISTS FOR ROUTED THE FLEET. `next_unblocked` reads
5
+ * `docs/QUEUE.md` out of whatever working tree it is handed, and that tree
6
+ * belongs to nobody: its freshness is a property of whoever last worked in it.
7
+ * Measured 2026-09-12 16:36 — the primary checkout sat FOUR commits behind
8
+ * `origin/main`, its `QUEUE.md` had zero hits for a tag committed six minutes
9
+ * earlier, and the verb returned the PRE-EDIT text as `next`.
10
+ *
11
+ * ⛆ AND THE HARM LANDED IN BOTH DIRECTIONS INSIDE TEN MINUTES: a row was ruled
12
+ * un-claimable from an observation that was actually a stale file, and a worker
13
+ * was told to hold on a row that was already split. ⭐ ***A true conclusion
14
+ * reached through an untrue reading is the shape, and here the INSTRUMENT
15
+ * supplied the untrue reading — the answer was well-formed, internally
16
+ * consistent, `accounting.reconciles` was TRUE, and nothing in it said which
17
+ * tree it read.***
18
+ *
19
+ * ⛔⛆⛆ THE TRAP THAT BREAKS THE OBVIOUS FIX, AND IT IS WHY THIS FETCHES:
20
+ *
21
+ * a checkout that has not fetched CANNOT REPORT that it has not fetched.
22
+ *
23
+ * `git rev-list --left-right --count origin/main...HEAD` in an unfetched clone
24
+ * compares HEAD against a STALE `origin/main` ref and returns `0 0` — *"I am
25
+ * perfectly current"* — while the real remote has moved. Measured: HEAD
26
+ * `1cab5c2`, its own `origin/main` also `1cab5c2`, true `origin/main` `558365d`,
27
+ * three commits behind, reported as ZERO. ⭐⭐ ***A distance of `0` from an
28
+ * unfetched ref and a distance of `0` from a fetched one are BYTE-IDENTICAL and
29
+ * mean opposite things. That distinction is the whole point of this module: the
30
+ * ref is fetched IN THIS CALL, and when the fetch fails the answer is
31
+ * `"unknown"` and never `0`.***
32
+ *
33
+ * ⚠ AND FETCHING IS NOT THE FIX — that is the row's named anti-control.
34
+ * Fetching updates REFS; the verb reads the TREE, which is still stale
35
+ * afterwards. So a fetch only makes the staleness *legible*; the caller must
36
+ * still refuse or flag. This module reports; it never silently repairs, because
37
+ * making someone else's working tree current is a destructive write into a
38
+ * checkout a seat may be mid-work in.
39
+ */
40
+ import { execFileSync } from "node:child_process";
41
+ import path from "node:path";
42
+
43
+ export type TreeProvenance = {
44
+ /** The tree actually read, resolved — not the string the caller passed. */
45
+ path: string;
46
+ head: string | null;
47
+ /** The ref the distance is measured against, e.g. `origin/main`. */
48
+ base: string;
49
+ baseSha: string | null;
50
+ /**
51
+ * Commits the tree is behind `base`.
52
+ *
53
+ * ⛔ `"unknown"` WHEN THE REF COULD NOT BE FRESHENED IN THIS CALL. Never `0`
54
+ * in that case: `0` is a claim of currency and an unfetched ref cannot make it.
55
+ */
56
+ behind: number | "unknown";
57
+ /** Whether the ref this distance rests on was freshened in THIS call. */
58
+ fetched: boolean;
59
+ fetchError?: string;
60
+ dirty: boolean | "unknown";
61
+ /** True only when the tree is MEASURABLY behind a ref fetched in this call. */
62
+ stale: boolean;
63
+ /** Present whenever the answer cannot be trusted as current. */
64
+ warning?: string;
65
+ };
66
+
67
+ const run = (repo: string, args: string[]): string | null => {
68
+ try {
69
+ return execFileSync("git", args, {
70
+ cwd: repo,
71
+ encoding: "utf8",
72
+ stdio: ["ignore", "pipe", "ignore"],
73
+ timeout: 20_000,
74
+ }).trim();
75
+ } catch {
76
+ return null;
77
+ }
78
+ };
79
+
80
+ /**
81
+ * What the caller needs to say out loud about the tree it just read.
82
+ *
83
+ * @param repo the working tree a verb was handed
84
+ * @param baseBranch the branch the tree should be current with
85
+ */
86
+ export function treeProvenance(repo: string, baseBranch = "main"): TreeProvenance {
87
+ const resolved = run(repo, ["rev-parse", "--show-toplevel"]) ?? path.resolve(repo);
88
+ const base = `origin/${baseBranch}`;
89
+ const head = run(resolved, ["rev-parse", "HEAD"]);
90
+ const status = run(resolved, ["status", "--porcelain"]);
91
+
92
+ // ⛔ FETCH FIRST, AND THE ORDER IS THE POINT. Every number below is worthless
93
+ // if it rests on a ref the tree last updated at some unknown past moment.
94
+ const fetched = run(resolved, ["fetch", "--quiet", "origin", baseBranch]) !== null;
95
+
96
+ const out: TreeProvenance = {
97
+ path: resolved,
98
+ head,
99
+ base,
100
+ baseSha: null,
101
+ behind: "unknown",
102
+ fetched,
103
+ dirty: status === null ? "unknown" : status.length > 0,
104
+ stale: false,
105
+ };
106
+
107
+ if (!fetched) {
108
+ out.fetchError = `could not fetch origin/${baseBranch} — the distance cannot be computed`;
109
+ out.warning =
110
+ `TREE FRESHNESS UNKNOWN: ${resolved} could not reach origin/${baseBranch} in this call, so its ` +
111
+ `distance is UNKNOWN rather than 0. A checkout that has not fetched cannot report that it has not ` +
112
+ `fetched — an unfetched ref answers "0 behind" and means nothing.`;
113
+ return out;
114
+ }
115
+
116
+ out.baseSha = run(resolved, ["rev-parse", base]);
117
+ const counts = run(resolved, ["rev-list", "--left-right", "--count", `${base}...HEAD`]);
118
+ const behind = counts ? Number(counts.split(/\s+/)[0]) : NaN;
119
+ if (!Number.isFinite(behind)) {
120
+ out.warning = `TREE FRESHNESS UNKNOWN: could not count ${base}...HEAD in ${resolved}.`;
121
+ return out;
122
+ }
123
+
124
+ out.behind = behind;
125
+ out.stale = behind > 0;
126
+ if (out.stale) {
127
+ // ⚠ FETCHING DID NOT FIX THE TREE — it only made this sentence possible.
128
+ // The file the verb read is still the old one.
129
+ out.warning =
130
+ `STALE TREE: ${resolved} is ${behind} commit(s) behind ${base} (HEAD ${head?.slice(0, 7)}, ` +
131
+ `${base} ${out.baseSha?.slice(0, 7)}). The answer above was read from THAT tree's files, so a row ` +
132
+ `filed or amended on ${base} since is invisible here and a superseded row can be offered as current. ` +
133
+ `Fetching refreshed the REF, not the working tree.`;
134
+ }
135
+ return out;
136
+ }
@@ -215,12 +215,25 @@ async function create(a: {
215
215
  } catch (e) {
216
216
  return { ok: false as const, error: `git worktree add failed: ${String((e as Error).message).split("\n")[0]}` };
217
217
  }
218
+ // ⟨q-7b2f6c04⟩ — THE EMPTY PUSH AT CLAIM. The branch goes to origin at the
219
+ // base's tip, so the lane has a ref on the remote from its first minute: the
220
+ // VCS axis sees it, and the stall clock scores it on the CLAIM axis (a ref
221
+ // sitting on the base has no commits of its own to date). Best-effort and
222
+ // REPORTED, never fatal — a tree cut offline is still a tree.
223
+ let pushed: { ok: true; ref: string } | { ok: false; why: string };
224
+ try {
225
+ execFileSync("git", ["push", "-q", "-u", "origin", `${branch}:refs/heads/${branch}`], { cwd: repo, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 20_000 });
226
+ pushed = { ok: true, ref: `origin/${branch}` };
227
+ } catch (e) {
228
+ pushed = { ok: false, why: `the empty claim-time push did not land: ${String((e as Error).message).split("\n").find((l) => l.trim()) ?? "unknown"} — the lane is observable on the claim axis from the board's history only` };
229
+ }
218
230
  return {
219
231
  ok: true as const,
220
232
  path: target,
221
233
  sha,
222
234
  branch,
223
235
  created: true,
236
+ pushed,
224
237
  base: ref,
225
238
  // Cut from `sha`, which IS origin/<base> — true by construction here, and
226
239
  // stated so callers need not special-case "created" to know it.