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.
- package/dist/capabilities.js +250 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/closing-line.js +83 -0
- package/dist/closing-line.js.map +1 -0
- package/dist/commit-cite.js +55 -0
- package/dist/commit-cite.js.map +1 -0
- package/dist/gated-head.js +177 -0
- package/dist/gated-head.js.map +1 -0
- package/dist/server-spread.js +195 -0
- package/dist/server-spread.js.map +1 -0
- package/dist/server.js +4 -2
- package/dist/server.js.map +1 -1
- package/dist/store.js +32 -0
- package/dist/store.js.map +1 -1
- package/dist/tools/away.js +67 -7
- package/dist/tools/away.js.map +1 -1
- package/dist/tools/board-ref.js +44 -4
- package/dist/tools/board-ref.js.map +1 -1
- package/dist/tools/event-kinds.js +5 -1
- package/dist/tools/event-kinds.js.map +1 -1
- package/dist/tools/events.js +31 -2
- package/dist/tools/events.js.map +1 -1
- package/dist/tools/messaging.js +64 -6
- package/dist/tools/messaging.js.map +1 -1
- package/dist/tools/queue-write.js +431 -0
- package/dist/tools/queue-write.js.map +1 -0
- package/dist/tools/record-events.js +85 -5
- package/dist/tools/record-events.js.map +1 -1
- package/dist/tools/records.js +384 -33
- package/dist/tools/records.js.map +1 -1
- package/dist/tools/registry.js +52 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/seat-build.js +173 -0
- package/dist/tools/seat-build.js.map +1 -0
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/stall.js +1095 -18
- package/dist/tools/stall.js.map +1 -1
- package/dist/tools/transport.js +21 -2
- 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/worktrees.js +14 -0
- package/dist/tools/worktrees.js.map +1 -1
- package/package.json +1 -1
- package/scripts/coord-attention-clock.mjs +2 -0
- package/scripts/coord-stall-clock.mjs +52 -11
- package/src/capabilities.ts +264 -2
- package/src/closing-line.ts +85 -0
- package/src/commit-cite.ts +58 -0
- package/src/gated-head.ts +236 -0
- package/src/server-spread.ts +233 -0
- package/src/server.ts +10 -2
- package/src/store.ts +32 -0
- package/src/tools/away.ts +82 -9
- package/src/tools/board-ref.ts +70 -3
- package/src/tools/event-kinds.ts +17 -1
- package/src/tools/events.ts +33 -2
- package/src/tools/messaging.ts +63 -6
- package/src/tools/queue-write.ts +485 -0
- package/src/tools/record-events.ts +78 -5
- package/src/tools/records.ts +423 -31
- package/src/tools/registry.ts +54 -3
- package/src/tools/seat-build.ts +194 -0
- package/src/tools/shared.ts +22 -0
- package/src/tools/stall.ts +1266 -23
- package/src/tools/transport.ts +21 -2
- package/src/tools/tree-provenance.ts +136 -0
- package/src/tools/worktrees.ts +13 -0
package/src/tools/transport.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/src/tools/worktrees.ts
CHANGED
|
@@ -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.
|