agent-coord-mcp 0.26.6 → 0.26.8
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/server.js +4 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/attention.js +73 -0
- package/dist/tools/attention.js.map +1 -0
- package/dist/tools/events.js +171 -0
- package/dist/tools/events.js.map +1 -0
- package/dist/tools/logwatch.js +85 -0
- package/dist/tools/logwatch.js.map +1 -0
- package/dist/tools/messaging.js +30 -0
- package/dist/tools/messaging.js.map +1 -1
- package/dist/tools/records.js +107 -1
- package/dist/tools/records.js.map +1 -1
- package/dist/tools/registry.js +23 -3
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/stall.js +126 -11
- package/dist/tools/stall.js.map +1 -1
- package/dist/tools/transport.js +29 -5
- package/dist/tools/transport.js.map +1 -1
- package/dist/tools/worktrees.js +30 -0
- package/dist/tools/worktrees.js.map +1 -1
- package/hooks/push-cursor.mjs +78 -0
- package/hooks/tmux-pusher.mjs +35 -9
- package/package.json +2 -2
- package/scripts/check-test-count.mjs +1 -1
- package/scripts/coord-attention-clock.mjs +122 -0
- package/scripts/coord-stall-clock.mjs +125 -0
- package/src/server.ts +22 -0
- package/src/tools/attention.ts +91 -0
- package/src/tools/events.ts +199 -0
- package/src/tools/logwatch.ts +107 -0
- package/src/tools/messaging.ts +29 -0
- package/src/tools/records.ts +110 -2
- package/src/tools/registry.ts +23 -4
- package/src/tools/stall.ts +130 -13
- package/src/tools/transport.ts +31 -5
- package/src/tools/worktrees.ts +28 -0
package/dist/tools/worktrees.js
CHANGED
|
@@ -152,6 +152,30 @@ async function create(a) {
|
|
|
152
152
|
// made: a verb that silently returns a tree on a different branch than asked
|
|
153
153
|
// for is the adjacent-answer shape.
|
|
154
154
|
const head = git(existing.path, ["rev-parse", "HEAD"]);
|
|
155
|
+
// IS THE REUSED TREE ACTUALLY AT THE BASE IT CLAIMS?
|
|
156
|
+
//
|
|
157
|
+
// The verb warned when an existing tree was on a different BRANCH and said
|
|
158
|
+
// nothing about it being behind the BASE — so a tree left on last week's
|
|
159
|
+
// main was handed back as ready. Starting a new task there produces the
|
|
160
|
+
// "stale main -> confidently-wrong inventories" failure the worker card
|
|
161
|
+
// already warns about, and nothing about the result looks stale.
|
|
162
|
+
//
|
|
163
|
+
// Reported here, not refused: an existing tree legitimately holds
|
|
164
|
+
// in-progress work mid-slice (1.3). The refusal belongs to `claim`, which
|
|
165
|
+
// is the verb that means "start something new".
|
|
166
|
+
let atBase = false;
|
|
167
|
+
let behindBy = null;
|
|
168
|
+
try {
|
|
169
|
+
const baseSha = git(repo, ["rev-parse", "--verify", `${ref}^{commit}`]);
|
|
170
|
+
atBase = head === baseSha;
|
|
171
|
+
if (!atBase)
|
|
172
|
+
behindBy = Number(git(existing.path, ["rev-list", "--count", `HEAD..${ref}`])) || 0;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// NOT MEASURED IS NOT AT-BASE. Leaving `atBase` false is the safe
|
|
176
|
+
// direction: it makes `claim` ask rather than assume.
|
|
177
|
+
atBase = false;
|
|
178
|
+
}
|
|
155
179
|
return {
|
|
156
180
|
ok: true,
|
|
157
181
|
path: existing.path,
|
|
@@ -159,6 +183,8 @@ async function create(a) {
|
|
|
159
183
|
branch: existing.branch,
|
|
160
184
|
created: false,
|
|
161
185
|
base: ref,
|
|
186
|
+
atBase,
|
|
187
|
+
behindBy: behindBy ?? 0,
|
|
162
188
|
...(existing.branch !== branch
|
|
163
189
|
? { warning: `existing tree is on '${existing.branch}', not the '${branch}' this call would have created — reusing it, NOT re-pointing it` }
|
|
164
190
|
: {}),
|
|
@@ -177,6 +203,10 @@ async function create(a) {
|
|
|
177
203
|
branch,
|
|
178
204
|
created: true,
|
|
179
205
|
base: ref,
|
|
206
|
+
// Cut from `sha`, which IS origin/<base> — true by construction here, and
|
|
207
|
+
// stated so callers need not special-case "created" to know it.
|
|
208
|
+
atBase: true,
|
|
209
|
+
behindBy: 0,
|
|
180
210
|
...(a.ephemeral
|
|
181
211
|
? { ephemeral: true, removeWith: `git -C ${repo} worktree remove --force ${target} && git -C ${repo} branch -D ${branch}` }
|
|
182
212
|
: {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktrees.js","sourceRoot":"","sources":["../../src/tools/worktrees.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worktrees.js","sourceRoot":"","sources":["../../src/tools/worktrees.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A READER MUST NOT BE ABLE TO CONSUME.
|
|
3
|
+
*
|
|
4
|
+
* The read cursor records what the AGENT has taken delivery of. Only the agent
|
|
5
|
+
* reading may advance it. Anything else that merely LOOKS at the log — a pusher
|
|
6
|
+
* typing into a pane, a watcher waiting for traffic — is a reader, and a reader
|
|
7
|
+
* that advances the read cursor consumes on the agent's behalf without the
|
|
8
|
+
* agent having seen anything.
|
|
9
|
+
*
|
|
10
|
+
* This module is the pusher's instance of that rule. `wait_for_message` is the
|
|
11
|
+
* other one: it must observe the log without consuming from it. They are the
|
|
12
|
+
* same rule at two callers, not two fixes.
|
|
13
|
+
*
|
|
14
|
+
* THE PUSH CURSOR IS NOT THE READ CURSOR.
|
|
15
|
+
*
|
|
16
|
+
* These were one file. The pusher advanced `roomOffsets[chan]` the moment a
|
|
17
|
+
* paste was DEEMED submitted, and `read_messages` reads the same offsets — so a
|
|
18
|
+
* paste that never reached the pane still marked the message consumed. The
|
|
19
|
+
* message was then unreachable by every route: not in the pane, and skipped by
|
|
20
|
+
* the reader.
|
|
21
|
+
*
|
|
22
|
+
* That is the parent class of every defect found this week — a failure path
|
|
23
|
+
* returning a plausible success — with one difference that makes it worse:
|
|
24
|
+
* this one WRITES THE SUCCESS INTO DURABLE STATE. The others produced a wrong
|
|
25
|
+
* reading; this destroys the evidence that the reading was wrong.
|
|
26
|
+
*
|
|
27
|
+
* Measured on a consumer fleet: an agent's cursor sat past a peer's
|
|
28
|
+
* `DONE: merged #<n>` at line 1107 of that room's log — a ROOM post, so its
|
|
29
|
+
* inbox was correctly empty and its `gh` current. Everything looked fine.
|
|
30
|
+
*
|
|
31
|
+
* Separating them does NOT make delivery reliable. It converts SILENT LOSS into
|
|
32
|
+
* NON-DELIVERY: the message stays unread in the read cursor, so `read_messages`
|
|
33
|
+
* still serves it and the failure becomes visible.
|
|
34
|
+
*
|
|
35
|
+
* EXTRACTED SO IT CAN BE EXERCISED. Inline in the daemon, the only thing a test
|
|
36
|
+
* could reach was the source text — and a source-text assertion cannot tell a
|
|
37
|
+
* working separation from a broken one. The first version of this fix shipped
|
|
38
|
+
* with an "end to end" test that passed either way, because the daemon needs a
|
|
39
|
+
* live tmux pane and never reached the cursor at all.
|
|
40
|
+
*/
|
|
41
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
42
|
+
import path from "node:path";
|
|
43
|
+
|
|
44
|
+
export const readCursorFile = (file) => {
|
|
45
|
+
if (!existsSync(file)) return null;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
48
|
+
} catch {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const pushCursorPath = (root, safeId) => path.join(root, "cursors", `${safeId}.push.json`);
|
|
54
|
+
export const readCursorPath = (root, safeId) => path.join(root, "cursors", `${safeId}.json`);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* SEEDED FROM THE READ CURSOR ON FIRST RUN, once.
|
|
58
|
+
*
|
|
59
|
+
* Without this every existing pusher wakes with an empty push cursor and
|
|
60
|
+
* re-pushes its entire history into the pane — turning a data-loss fix into a
|
|
61
|
+
* flood. The seed is a COPY, not a link: after it, the two advance
|
|
62
|
+
* independently.
|
|
63
|
+
*/
|
|
64
|
+
export function readPushCursor(root, safeId, write = writePushCursor) {
|
|
65
|
+
const own = readCursorFile(pushCursorPath(root, safeId));
|
|
66
|
+
if (own !== null) return own;
|
|
67
|
+
const seed = readCursorFile(readCursorPath(root, safeId)) ?? {};
|
|
68
|
+
write(root, safeId, seed);
|
|
69
|
+
return seed;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Writes ONLY the push cursor. What the agent has READ is not the pusher's to say. */
|
|
73
|
+
export function writePushCursor(root, safeId, c) {
|
|
74
|
+
const file = pushCursorPath(root, safeId);
|
|
75
|
+
const tmp = `${file}.tmp`;
|
|
76
|
+
writeFileSync(tmp, JSON.stringify(c));
|
|
77
|
+
renameSync(tmp, file);
|
|
78
|
+
}
|
package/hooks/tmux-pusher.mjs
CHANGED
|
@@ -101,6 +101,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
101
101
|
import { effectiveTier, isGateRunnerRole, TierQueue, formatBatch } from "./tier.mjs";
|
|
102
102
|
import { isCi } from "./roles.mjs";
|
|
103
103
|
import { mergeTransportMarker } from "./marker.mjs";
|
|
104
|
+
import { readPushCursor, writePushCursor } from "./push-cursor.mjs";
|
|
104
105
|
import { pasteAndSubmit as sharedPasteAndSubmit, submitControl as sharedSubmitControl } from "./submit.mjs";
|
|
105
106
|
|
|
106
107
|
const AGENT_ID = process.env.AGENT_COORD_ID;
|
|
@@ -175,6 +176,36 @@ setInterval(refreshTierCtx, 30_000).unref();
|
|
|
175
176
|
const SAFE_ID = AGENT_ID.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
176
177
|
const INBOX_FILE = path.join(ROOT, "inbox", `${SAFE_ID}.jsonl`);
|
|
177
178
|
const CURSOR_FILE = path.join(ROOT, "cursors", `${SAFE_ID}.json`);
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* THE PUSH CURSOR IS NOT THE READ CURSOR.
|
|
182
|
+
*
|
|
183
|
+
* These were one file. The pusher advanced `roomOffsets[chan]` the moment a
|
|
184
|
+
* paste was DEEMED submitted, and `read_messages` reads the same offsets — so a
|
|
185
|
+
* paste that never reached the pane still marked the message consumed. The
|
|
186
|
+
* message was then unreachable by every route: not in the pane, and skipped by
|
|
187
|
+
* the reader.
|
|
188
|
+
*
|
|
189
|
+
* That is today's parent class — a failure path returning a plausible success —
|
|
190
|
+
* with one difference that makes it worse than every other instance: this one
|
|
191
|
+
* WRITES THE SUCCESS INTO DURABLE STATE. The others produced a wrong reading;
|
|
192
|
+
* this destroys the evidence that the reading was wrong.
|
|
193
|
+
*
|
|
194
|
+
* Measured on a consumer fleet: an agent's cursor sat past a peer's
|
|
195
|
+
* `DONE: merged #<n>` at line 1107 of that room's log — a ROOM post, so its
|
|
196
|
+
* inbox was correctly empty and its `gh` current. Everything looked fine.
|
|
197
|
+
*
|
|
198
|
+
* Separating them does NOT make delivery reliable. It converts SILENT LOSS into
|
|
199
|
+
* mere NON-DELIVERY: the message stays unread in the read cursor, so
|
|
200
|
+
* `read_messages` still serves it and the failure becomes visible. Cheapest
|
|
201
|
+
* first, and it is the half that stops data loss rather than a stall.
|
|
202
|
+
*
|
|
203
|
+
* The cost is duplication, deliberately: a message pasted into the pane may
|
|
204
|
+
* also be served by `read_messages`. At-least-once with a visible duplicate
|
|
205
|
+
* beats at-most-once with an invisible hole — the same ruling as the event
|
|
206
|
+
* subscriptions' delivery contract.
|
|
207
|
+
*/
|
|
208
|
+
const PUSH_CURSOR_FILE = path.join(ROOT, "cursors", `${SAFE_ID}.push.json`);
|
|
178
209
|
const TRANSPORT_FILE = path.join(ROOT, "transports", `${SAFE_ID}.json`);
|
|
179
210
|
// Out-of-band delivery receipts: stamped AFTER a message is typed into the
|
|
180
211
|
// pane, so the sender can poll for proof of delivery without the receipt ever
|
|
@@ -228,19 +259,14 @@ let debounceTimer = null;
|
|
|
228
259
|
let sending = false;
|
|
229
260
|
let targetMisses = 0;
|
|
230
261
|
|
|
262
|
+
// Cursor IO lives in ./push-cursor.mjs so it can be EXERCISED — inline here,
|
|
263
|
+
// the only thing a test could reach was the source text.
|
|
231
264
|
function readCursor() {
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
return JSON.parse(readFileSync(CURSOR_FILE, "utf8"));
|
|
235
|
-
} catch {
|
|
236
|
-
return {};
|
|
237
|
-
}
|
|
265
|
+
return readPushCursor(ROOT, SAFE_ID);
|
|
238
266
|
}
|
|
239
267
|
|
|
240
268
|
function writeCursor(c) {
|
|
241
|
-
|
|
242
|
-
writeFileSync(tmp, JSON.stringify(c));
|
|
243
|
-
renameSync(tmp, CURSOR_FILE);
|
|
269
|
+
writePushCursor(ROOT, SAFE_ID, c);
|
|
244
270
|
}
|
|
245
271
|
|
|
246
272
|
function readJsonl(file) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-coord-mcp",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.8",
|
|
4
4
|
"description": "File-backed MCP server for coordinating multiple AI coding agents (Claude Code, Cursor, Cline, etc.). Local stdio or networked over Streamable HTTP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
47
47
|
"proper-lockfile": "^4.1.2",
|
|
48
48
|
"zod": "^4.4.3",
|
|
49
|
-
"@davidbalzan/groundwork-seam": "0.1.
|
|
49
|
+
"@davidbalzan/groundwork-seam": "0.1.10"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^26.2.0",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { spawn } from "node:child_process";
|
|
24
24
|
|
|
25
|
-
const EXPECTED_TESTS =
|
|
25
|
+
const EXPECTED_TESTS = 520;
|
|
26
26
|
|
|
27
27
|
const expected = Number(process.env.AGENT_COORD_EXPECTED_TESTS ?? EXPECTED_TESTS);
|
|
28
28
|
// Same glob the suite always used — `--test test/` would recurse differently
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Emit on MERGEABLE-AND-UNATTENDED.
|
|
4
|
+
*
|
|
5
|
+
* Two mergeable, CI-green PRs sat 17 hours; two more sat 40 minutes and 1h48 on
|
|
6
|
+
* the day the coordinator said it was checking for that. Nothing in the room
|
|
7
|
+
* said so — a worker went looking. This is the mechanism that was missing.
|
|
8
|
+
*
|
|
9
|
+
* A TIMER, NOT A TRIGGER, for the reason the stall clock is: a mechanism
|
|
10
|
+
* without a schedule measures inbound traffic rather than elapsed time, and
|
|
11
|
+
* cannot fire during exactly the quiet it exists to cover.
|
|
12
|
+
*
|
|
13
|
+
* Named unit: com.davidbalzan.coord-attention-clock (see --install).
|
|
14
|
+
*
|
|
15
|
+
* UNKNOWN IS NOT UNATTENDED. `mergeable` is UNKNOWN for a while after every
|
|
16
|
+
* push and an empty rollup is ambiguous; both are reported as unknown, never as
|
|
17
|
+
* an alert. Inventing an alert from a question GitHub has not answered is the
|
|
18
|
+
* fabrication class this fleet has spent three days removing.
|
|
19
|
+
*
|
|
20
|
+
* REPORTED ONCE, not every tick: delivery goes through the same subscription
|
|
21
|
+
* machinery as record events, so the idempotency key suppresses a repeat. A DM
|
|
22
|
+
* every 30 minutes about the same PR trains its own dismissal.
|
|
23
|
+
*/
|
|
24
|
+
import { execFileSync } from "node:child_process";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
27
|
+
import { homedir } from "node:os";
|
|
28
|
+
|
|
29
|
+
const argv = Object.fromEntries(
|
|
30
|
+
process.argv.slice(2).flatMap((a) => {
|
|
31
|
+
const m = /^--([^=]+)(?:=(.*))?$/.exec(a);
|
|
32
|
+
return m ? [[m[1], m[2] ?? true]] : [];
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const dist = path.join(here, "..", "dist");
|
|
37
|
+
const LABEL = "com.davidbalzan.coord-attention-clock";
|
|
38
|
+
|
|
39
|
+
if (argv.install) {
|
|
40
|
+
const repo = String(argv.repo ?? process.cwd());
|
|
41
|
+
const to = String(argv.to ?? "<coordinator-agent-id>");
|
|
42
|
+
const every = Number(argv.every ?? 15);
|
|
43
|
+
const self = path.join(here, "coord-attention-clock.mjs");
|
|
44
|
+
console.log(`# macOS launchd — Label: ${LABEL}
|
|
45
|
+
# launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/${LABEL}.plist
|
|
46
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
47
|
+
<plist version="1.0"><dict>
|
|
48
|
+
<key>Label</key><string>${LABEL}</string>
|
|
49
|
+
<key>ProgramArguments</key><array>
|
|
50
|
+
<string>${process.execPath}</string><string>${self}</string>
|
|
51
|
+
<string>--repo=${repo}</string><string>--to=${to}</string>
|
|
52
|
+
</array>
|
|
53
|
+
<key>StartInterval</key><integer>${every * 60}</integer>
|
|
54
|
+
<key>RunAtLoad</key><true/>
|
|
55
|
+
<key>StandardErrorPath</key><string>${path.join(homedir(), "agent-coord", "logs", "attention-clock.err")}</string>
|
|
56
|
+
</dict></plist>
|
|
57
|
+
|
|
58
|
+
# Linux cron:
|
|
59
|
+
# */${every} * * * * ${process.execPath} ${self} --repo=${repo} --to=${to}
|
|
60
|
+
`);
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const repo = String(argv.repo ?? process.cwd());
|
|
65
|
+
const to = argv.to ? String(argv.to) : null;
|
|
66
|
+
const from = String(argv.from ?? "coord-attention-clock");
|
|
67
|
+
const quietMinutes = Number(argv["quiet-minutes"] ?? 20);
|
|
68
|
+
|
|
69
|
+
const { partition } = await import(path.join(dist, "tools/attention.js"));
|
|
70
|
+
const { readSubs, evaluate, commitEvaluation } = await import(path.join(dist, "tools/events.js"));
|
|
71
|
+
const { sendMessageTool } = await import(path.join(dist, "tools/messaging.js"));
|
|
72
|
+
|
|
73
|
+
let prs;
|
|
74
|
+
try {
|
|
75
|
+
const out = execFileSync(
|
|
76
|
+
"gh",
|
|
77
|
+
["pr", "list", "--state", "open", "--json", "number,state,mergeable,updatedAt,statusCheckRollup"],
|
|
78
|
+
{ cwd: repo, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
|
|
79
|
+
);
|
|
80
|
+
prs = JSON.parse(out).map((p) => ({ ...p, checks: p.statusCheckRollup ?? [] }));
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// NOT FETCHED IS NOT NOTHING-TO-REPORT. A silent failure here is exactly the
|
|
83
|
+
// quiet that let the PRs sit.
|
|
84
|
+
console.error(`[attention-clock] FAILED to list PRs: ${(e && e.message) || e}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
const { unattended, unknown, attended } = partition(prs, now, quietMinutes * 60_000);
|
|
90
|
+
|
|
91
|
+
let delivered = 0;
|
|
92
|
+
if (to) {
|
|
93
|
+
const subs = readSubs();
|
|
94
|
+
let next = subs;
|
|
95
|
+
const fresh = [];
|
|
96
|
+
for (const u of unattended) {
|
|
97
|
+
const ev = { kind: "pr", target: "mergeable-unattended", ref: `#${u.pr.number}`, summary: u.why };
|
|
98
|
+
const r = evaluate(next, ev, now);
|
|
99
|
+
next = r.subs;
|
|
100
|
+
if (r.deliveries.some((d) => d.status === "delivered")) fresh.push(u);
|
|
101
|
+
}
|
|
102
|
+
commitEvaluation(next);
|
|
103
|
+
if (fresh.length) {
|
|
104
|
+
await sendMessageTool({
|
|
105
|
+
from,
|
|
106
|
+
to,
|
|
107
|
+
text:
|
|
108
|
+
`AGENT_ACTION: ${fresh.length} PR(s) MERGEABLE AND UNATTENDED.\n` +
|
|
109
|
+
fresh.map((u) => `- ${u.why}`).join("\n") +
|
|
110
|
+
`\n\nScheduled check (${LABEL}), not a person. Verify before merging: this reports GATE-READINESS, never that the change is correct.`,
|
|
111
|
+
});
|
|
112
|
+
delivered = fresh.length;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The null result every run: a check that only speaks when it fires cannot be
|
|
117
|
+
// told from a broken one. `unknown` is printed separately from `attended`
|
|
118
|
+
// because collapsing them is the defect this script refuses to commit.
|
|
119
|
+
console.log(
|
|
120
|
+
`[attention-clock] ${prs.length} open · unattended ${unattended.length} (DM'd ${delivered}) · unknown ${unknown.length} · attended ${attended}` +
|
|
121
|
+
(unknown.length ? `\n unknown:\n${unknown.map((u) => ` ${u.why}`).join("\n")}` : ""),
|
|
122
|
+
);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* THE CLOCK. Runs `stall_check` on a schedule and DMs the duty officer on HIT.
|
|
4
|
+
*
|
|
5
|
+
* WHY A SCHEDULER AND NOT AN AGENT LOOP: a mechanism without a schedule is
|
|
6
|
+
* REACTIVE, and its null reports measure inbound traffic rather than elapsed
|
|
7
|
+
* time. console-worker-1's standing trigger ran zero times in seventeen hours
|
|
8
|
+
* because it only fired when a message woke it — so it could not run during
|
|
9
|
+
* exactly the period it existed to cover, a fleet gone quiet. `wait_for_message`
|
|
10
|
+
* is disqualified for the same reason (Task 3.3).
|
|
11
|
+
*
|
|
12
|
+
* THE UNIT IS NAMED, because "standing" without a named scheduler is a habit:
|
|
13
|
+
* macOS launchd com.davidbalzan.coord-stall-clock (see --install)
|
|
14
|
+
* Linux cron the line --install prints
|
|
15
|
+
*
|
|
16
|
+
* WHAT IT MEASURES WHEN NOBODY IS WATCHING: that is the whole point, and it is
|
|
17
|
+
* why this is a timer rather than a trigger.
|
|
18
|
+
*
|
|
19
|
+
* MISS IS SILENT TO THE DUTY OFFICER AND NEVER SILENT TO THE RECORD. A DM every
|
|
20
|
+
* 30 minutes trains its own dismissal; a missing run mark makes a dead clock
|
|
21
|
+
* look like a healthy fleet. `stall_check` writes the mark itself; this script
|
|
22
|
+
* additionally records FAILURES, without which a clock that throws every time
|
|
23
|
+
* leaves no marks at all and is identical on disk to one never installed.
|
|
24
|
+
*
|
|
25
|
+
* It does NOT speak MCP: `stallCheckTool` is a plain function over
|
|
26
|
+
* ~/agent-coord and the repo's board, so a timer needs no server and no
|
|
27
|
+
* transport. One less thing that can be up while the thing it watches is down.
|
|
28
|
+
*/
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
|
|
33
|
+
const argv = Object.fromEntries(
|
|
34
|
+
process.argv.slice(2).flatMap((a) => {
|
|
35
|
+
const m = /^--([^=]+)(?:=(.*))?$/.exec(a);
|
|
36
|
+
return m ? [[m[1], m[2] ?? true]] : [];
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const dist = path.join(here, "..", "dist");
|
|
42
|
+
|
|
43
|
+
const LABEL = "com.davidbalzan.coord-stall-clock";
|
|
44
|
+
|
|
45
|
+
if (argv.install) {
|
|
46
|
+
const repo = String(argv.repo ?? process.cwd());
|
|
47
|
+
const duty = String(argv.duty ?? "<duty-officer-agent-id>");
|
|
48
|
+
const every = Number(argv.every ?? 30);
|
|
49
|
+
const self = path.join(here, "coord-stall-clock.mjs");
|
|
50
|
+
const node = process.execPath;
|
|
51
|
+
console.log(`# macOS — launchd. Label: ${LABEL}
|
|
52
|
+
# Write to ~/Library/LaunchAgents/${LABEL}.plist, then:
|
|
53
|
+
# launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/${LABEL}.plist
|
|
54
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
55
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
56
|
+
<plist version="1.0"><dict>
|
|
57
|
+
<key>Label</key><string>${LABEL}</string>
|
|
58
|
+
<key>ProgramArguments</key><array>
|
|
59
|
+
<string>${node}</string><string>${self}</string>
|
|
60
|
+
<string>--repo=${repo}</string><string>--duty=${duty}</string>
|
|
61
|
+
</array>
|
|
62
|
+
<key>StartInterval</key><integer>${every * 60}</integer>
|
|
63
|
+
<key>RunAtLoad</key><true/>
|
|
64
|
+
<key>StandardErrorPath</key><string>${path.join(homedir(), "agent-coord", "logs", "stall-clock.err")}</string>
|
|
65
|
+
</dict></plist>
|
|
66
|
+
|
|
67
|
+
# Linux — cron:
|
|
68
|
+
# */${every} * * * * ${node} ${self} --repo=${repo} --duty=${duty}
|
|
69
|
+
`);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const repo = String(argv.repo ?? process.cwd());
|
|
74
|
+
const duty = argv.duty ? String(argv.duty) : null;
|
|
75
|
+
const from = String(argv.from ?? "coord-stall-clock");
|
|
76
|
+
const stallMinutes = argv["stall-minutes"] ? Number(argv["stall-minutes"]) : undefined;
|
|
77
|
+
|
|
78
|
+
const { stallCheckTool, markRunFailure } = await import(path.join(dist, "tools/stall.js"));
|
|
79
|
+
const { sendMessageTool } = await import(path.join(dist, "tools/messaging.js"));
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const r = await stallCheckTool({ repo, ...(stallMinutes ? { stallMinutes } : {}) });
|
|
83
|
+
|
|
84
|
+
// `ok:false` is a FAILURE, not a quiet fleet — e.g. no board at that path.
|
|
85
|
+
// Recorded, or the next `stall_clock_status` reads a stopped clock as absent
|
|
86
|
+
// rather than broken.
|
|
87
|
+
if (!r.ok) {
|
|
88
|
+
markRunFailure(r.error ?? "stall_check returned ok:false with no reason");
|
|
89
|
+
console.error(`[stall-clock] FAILED: ${r.error}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (r.hits.length && duty) {
|
|
94
|
+
const lines = r.hits.map((h) =>
|
|
95
|
+
h.kind === "no-heartbeat"
|
|
96
|
+
? `- ${h.agentId}: no heartbeat for ${h.minutes}m (${h.stream})`
|
|
97
|
+
: `- ${h.agentId}: no commits on ${h.branch} for ${h.minutes}m`,
|
|
98
|
+
);
|
|
99
|
+
await sendMessageTool({
|
|
100
|
+
from,
|
|
101
|
+
to: duty,
|
|
102
|
+
text: `AGENT_ACTION: stall_check HIT — ${r.hits.length} of ${r.checked} in-flight row(s) stalled.\n${lines.join("\n")}\n\nThis is a scheduled check (${LABEL}), not a person watching. Verify before acting: a stall is a claim about the BOARD, not about the agent.`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Null result to stdout every run: the log is the record a human reads, and a
|
|
107
|
+
// trigger that only speaks when it fires cannot be told from a broken one.
|
|
108
|
+
const um = r.unmeasurable?.length ? `, ${r.unmeasurable.length} unmeasurable` : "";
|
|
109
|
+
console.log(
|
|
110
|
+
r.hits.length
|
|
111
|
+
? `[stall-clock] HIT ${r.hits.length}/${r.checked}${um}${duty ? ` — DM sent to ${duty}` : " — NO DUTY OFFICER SET, no DM"}`
|
|
112
|
+
: `[stall-clock] MISS 0/${r.checked}${um} — no DM, run recorded`,
|
|
113
|
+
);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// THE CLAUSE MOST EASILY SKIPPED. A clock that throws every time leaves no
|
|
116
|
+
// marks, which on disk is identical to a clock that was never installed.
|
|
117
|
+
const reason = (e && e.message) || String(e);
|
|
118
|
+
try {
|
|
119
|
+
markRunFailure(reason);
|
|
120
|
+
} catch {
|
|
121
|
+
/* if even the mark cannot be written, stderr is all that is left */
|
|
122
|
+
}
|
|
123
|
+
console.error(`[stall-clock] FAILED: ${reason}`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { unlinkSync, writeFileSync } from "node:fs";
|
|
|
8
8
|
import { z, type ZodRawShape } from "zod";
|
|
9
9
|
import { coordAwaySchema, coordAwayTool, readAway, awayRefusal, secondCoordinatorRefusal } from "./tools/away.js";
|
|
10
10
|
import { rotateSchema, rotateTool, rotateReconcileSchema, rotateReconcileTool } from "./tools/rotate.js";
|
|
11
|
+
import { subscribeSchema, subscribeTool, unsubscribeSchema, unsubscribeTool, listSubscriptionsSchema, listSubscriptionsTool } from "./tools/events.js";
|
|
11
12
|
import {
|
|
12
13
|
ensureDirs,
|
|
13
14
|
getTokenMap,
|
|
@@ -626,6 +627,27 @@ function buildServer(initialBound?: string, opts: { trackSession?: boolean } = {
|
|
|
626
627
|
gate("agentId", rotateReconcileTool as (a: Record<string, unknown>) => Promise<unknown>),
|
|
627
628
|
);
|
|
628
629
|
|
|
630
|
+
addTool(
|
|
631
|
+
"subscribe",
|
|
632
|
+
"Register for a record event: a task completing, a phase completing, or a queue item closing. Events are DERIVED from the record \u2014 emitted by `land` after the DONE entry is written, and refused if the ref is not in the record \u2014 so the stream can never claim something the authoritative markdown does not. Re-subscribing returns the existing registration rather than a duplicate.",
|
|
633
|
+
subscribeSchema,
|
|
634
|
+
gate("agentId", subscribeTool as (a: Record<string, unknown>) => Promise<unknown>),
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
addTool(
|
|
638
|
+
"unsubscribe",
|
|
639
|
+
"Remove one of YOUR subscriptions. Refuses another agent's: silently dropping someone else's notification is how a miss is manufactured.",
|
|
640
|
+
unsubscribeSchema,
|
|
641
|
+
gate("agentId", unsubscribeTool as (a: Record<string, unknown>) => Promise<unknown>),
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
addTool(
|
|
645
|
+
"list_subscriptions",
|
|
646
|
+
"List subscriptions with their health. A subscription NEVER EVALUATED is an ERROR, not a quiet zero \u2014 'no events yet' and 'never ran' are the same output and only one is healthy. Carries its population, because 'no subscriptions' and 'none listed for you' are different claims.",
|
|
647
|
+
listSubscriptionsSchema,
|
|
648
|
+
gate(null, listSubscriptionsTool as (a: Record<string, unknown>) => Promise<unknown>),
|
|
649
|
+
);
|
|
650
|
+
|
|
629
651
|
addTool(
|
|
630
652
|
"export_work",
|
|
631
653
|
"Render a project's work documents back out of the store, reproducing the pinned glyph contract exactly (ref after the last ' \u2014 ', date after a trailing ' \u00b7 '). Reports by default; pass write:true to rewrite the files. Refuses to export from an empty store rather than blanking a document. Refuses write:true when that write would emit a new 5-col lanes-v0 table (parse-only; write grammar is workstreams.v1). Any declared Task 4 write scope is REPORTED alongside the write, never enforced.",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Emit on the state that has twice cost hours: a PR that is MERGEABLE and
|
|
3
|
+
* UNATTENDED.
|
|
4
|
+
*
|
|
5
|
+
* Two mergeable, CI-green PRs sat 17 hours; two more sat 40 minutes and 1h48
|
|
6
|
+
* on the day the coordinator said it was checking for exactly that. Nothing in
|
|
7
|
+
* the room said so — a worker went looking and found it. There is no mechanism
|
|
8
|
+
* that reports "these PRs are mergeable and nobody is acting".
|
|
9
|
+
*
|
|
10
|
+
* 6.2 APPLIES HERE TOO: the event is DERIVED from observable state, never a
|
|
11
|
+
* parallel claim. Everything below is computed from what GitHub actually
|
|
12
|
+
* reports, and anything it has not answered yet is `unknown`.
|
|
13
|
+
*/
|
|
14
|
+
import { normalizeChecks } from "./records.js";
|
|
15
|
+
|
|
16
|
+
export type PrFactsForAttention = {
|
|
17
|
+
number: number;
|
|
18
|
+
state: string;
|
|
19
|
+
mergeable: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
checks: unknown[];
|
|
22
|
+
repo?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type Attention =
|
|
26
|
+
| { state: "unattended"; why: string }
|
|
27
|
+
| { state: "unknown"; why: string }
|
|
28
|
+
| { state: "attended"; why: string };
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* UNKNOWN IS NOT UNATTENDED, and this is the clause the coordinator and I have
|
|
32
|
+
* both been bitten by today.
|
|
33
|
+
*
|
|
34
|
+
* `mergeable` is `UNKNOWN` for a while after every push — GitHub computes it
|
|
35
|
+
* asynchronously — and an empty check rollup is ambiguous between "not yet" and
|
|
36
|
+
* "never will be". Reading either as "nobody is acting" invents an alert out of
|
|
37
|
+
* a question GitHub has not answered, which is the fabrication class this fleet
|
|
38
|
+
* has spent three days removing.
|
|
39
|
+
*/
|
|
40
|
+
export function classifyPr(pr: PrFactsForAttention, nowMs: number, quietMs: number): Attention {
|
|
41
|
+
if (pr.state !== "OPEN") return { state: "attended", why: `#${pr.number} is ${pr.state}` };
|
|
42
|
+
|
|
43
|
+
if (!pr.mergeable || pr.mergeable === "UNKNOWN")
|
|
44
|
+
return {
|
|
45
|
+
state: "unknown",
|
|
46
|
+
why: `#${pr.number}: GitHub has not computed mergeability yet (frequently the case right after a push). UNKNOWN is not unattended — reporting it would invent an alert from an unanswered question.`,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (pr.mergeable === "CONFLICTING")
|
|
50
|
+
return { state: "attended", why: `#${pr.number} is CONFLICTING — it needs its author, not a gate` };
|
|
51
|
+
|
|
52
|
+
const checks = normalizeChecks(pr.checks ?? []);
|
|
53
|
+
if (checks.length === 0)
|
|
54
|
+
return {
|
|
55
|
+
state: "unknown",
|
|
56
|
+
why: `#${pr.number} reports ZERO checks — ambiguous between "not started" and "never will". No checks is not passing checks, and it is not unattended either.`,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const pending = checks.filter((c) => c.verdict === "pending");
|
|
60
|
+
if (pending.length)
|
|
61
|
+
return { state: "unknown", why: `#${pr.number}: ${pending.length} check(s) still running — not yet a gate's turn` };
|
|
62
|
+
|
|
63
|
+
const failed = checks.filter((c) => c.verdict === "fail");
|
|
64
|
+
if (failed.length)
|
|
65
|
+
return { state: "attended", why: `#${pr.number} has failing checks (${failed.map((c) => c.name).join(", ")}) — the author's move, not the gate's` };
|
|
66
|
+
|
|
67
|
+
// Quiet is measured from the PR's own last update. A PR touched a minute ago
|
|
68
|
+
// is not unattended; its author may still be pushing.
|
|
69
|
+
const quietFor = nowMs - Date.parse(pr.updatedAt);
|
|
70
|
+
if (!Number.isFinite(quietFor))
|
|
71
|
+
return { state: "unknown", why: `#${pr.number}: unreadable updatedAt '${pr.updatedAt}' — cannot say how long it has waited` };
|
|
72
|
+
if (quietFor < quietMs)
|
|
73
|
+
return { state: "attended", why: `#${pr.number} was updated ${Math.round(quietFor / 60000)}m ago — still moving` };
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
state: "unattended",
|
|
77
|
+
why: `#${pr.number} is MERGEABLE, all ${checks.length} check(s) pass, and nothing has touched it for ${Math.round(quietFor / 60000)}m`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The three states are distinct on purpose; a caller must not collapse them. */
|
|
82
|
+
export function partition(prs: PrFactsForAttention[], nowMs: number, quietMs: number) {
|
|
83
|
+
const out = { unattended: [] as { pr: PrFactsForAttention; why: string }[], unknown: [] as { pr: PrFactsForAttention; why: string }[], attended: 0 };
|
|
84
|
+
for (const pr of prs) {
|
|
85
|
+
const c = classifyPr(pr, nowMs, quietMs);
|
|
86
|
+
if (c.state === "unattended") out.unattended.push({ pr, why: c.why });
|
|
87
|
+
else if (c.state === "unknown") out.unknown.push({ pr, why: c.why });
|
|
88
|
+
else out.attended++;
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|