agent-coord-mcp 0.26.18 → 0.26.20
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/build.js +113 -0
- package/dist/build.js.map +1 -0
- package/dist/capabilities.js +214 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/prefix.js +64 -0
- package/dist/prefix.js.map +1 -0
- package/dist/roles.js +132 -0
- package/dist/roles.js.map +1 -0
- package/dist/server-identity.js +82 -0
- package/dist/server-identity.js.map +1 -0
- package/dist/server.js +668 -0
- package/dist/server.js.map +1 -0
- package/dist/store.js +553 -0
- package/dist/store.js.map +1 -0
- package/dist/tools/admin.js +323 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/attention.js +73 -0
- package/dist/tools/attention.js.map +1 -0
- package/dist/tools/away.js +285 -0
- package/dist/tools/away.js.map +1 -0
- package/dist/tools/board-ref.js +208 -0
- package/dist/tools/board-ref.js.map +1 -0
- package/dist/tools/event-kinds.js +39 -0
- package/dist/tools/event-kinds.js.map +1 -0
- package/dist/tools/events.js +234 -0
- package/dist/tools/events.js.map +1 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/logwatch.js +85 -0
- package/dist/tools/logwatch.js.map +1 -0
- package/dist/tools/messaging.js +706 -0
- package/dist/tools/messaging.js.map +1 -0
- package/dist/tools/record-events.js +380 -0
- package/dist/tools/record-events.js.map +1 -0
- package/dist/tools/records.js +1205 -0
- package/dist/tools/records.js.map +1 -0
- package/dist/tools/registry.js +529 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/render.js +2 -0
- package/dist/tools/render.js.map +1 -0
- package/dist/tools/rooms.js +210 -0
- package/dist/tools/rooms.js.map +1 -0
- package/dist/tools/rotate.js +143 -0
- package/dist/tools/rotate.js.map +1 -0
- package/dist/tools/scopes.js +126 -0
- package/dist/tools/scopes.js.map +1 -0
- package/dist/tools/shared.js +104 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/stall.js +415 -0
- package/dist/tools/stall.js.map +1 -0
- package/dist/tools/transport.js +1918 -0
- package/dist/tools/transport.js.map +1 -0
- package/dist/tools/work.js +465 -0
- package/dist/tools/work.js.map +1 -0
- package/dist/tools/worktrees.js +581 -0
- package/dist/tools/worktrees.js.map +1 -0
- 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/dist/typed-records.js +174 -0
- package/dist/typed-records.js.map +1 -0
- package/dist/work.js +2 -0
- package/dist/work.js.map +1 -0
- package/hooks/tmux-pusher.mjs +33 -3
- package/package.json +1 -1
- package/src/capabilities.ts +104 -1
- package/src/server.ts +30 -1
- package/src/tools/records.ts +221 -34
- 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/work.ts +95 -3
- package/src/tools/worktrees.ts +234 -32
- 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
package/README.md
CHANGED
|
@@ -157,6 +157,88 @@ The registry auto-evicts agents whose last heartbeat is older than 24h on every
|
|
|
157
157
|
|
|
158
158
|
Set `AGENT_COORD_DIR=/some/other/path` in the MCP server's env to relocate state. (`CLAUDE_COORD_DIR` is also honored as a legacy alias.) Useful if you want different agent groups isolated, or to put the dir on a synced volume so agents on different machines can collaborate (caveat above).
|
|
159
159
|
|
|
160
|
+
## Selecting the transport
|
|
161
|
+
|
|
162
|
+
The transport is **fleet-wide and resolved once, at server startup**. There is no
|
|
163
|
+
per-seat branching in `send_command` or `attach`: every seat in a fleet uses the
|
|
164
|
+
same transport, so the answer is a property of the fleet rather than of a call.
|
|
165
|
+
|
|
166
|
+
**Precedence: config file > env > built-in default.**
|
|
167
|
+
|
|
168
|
+
| source | where | use it for |
|
|
169
|
+
| --- | --- | --- |
|
|
170
|
+
| `config` | `$AGENT_COORD_DIR/config.json` → `{ "transport": "tmux-push" }` | stating what the **fleet** does — on disk, reviewable, same bytes for every seat |
|
|
171
|
+
| `env` | `AGENT_COORD_TRANSPORT=tmux-push` | one seat deviating **deliberately** (a test, a bisect) |
|
|
172
|
+
| `default` | `tmux-push` | no config and no env |
|
|
173
|
+
|
|
174
|
+
The file outranks the env var because it is the broader, visible statement: an env
|
|
175
|
+
var is invisible from outside the process that has it, so it cannot describe a
|
|
176
|
+
fleet. `capabilities` reports which source won, so a surprising answer can be
|
|
177
|
+
traced rather than guessed at.
|
|
178
|
+
|
|
179
|
+
**An unrecognised value refuses at startup — the server exits non-zero and says
|
|
180
|
+
so.** It does not fall back to `tmux-push`. That is deliberate: a silent fallback
|
|
181
|
+
and a correct default leave *identical evidence*, so `AGENT_COORD_TRANSPORT=heardr`
|
|
182
|
+
would give you a working tmux fleet, nothing in any log, and an afternoon spent
|
|
183
|
+
asking why the transport change did nothing. An unreadable `config.json` refuses
|
|
184
|
+
for the same reason — a file that exists and cannot be parsed is not the same as
|
|
185
|
+
no file.
|
|
186
|
+
|
|
187
|
+
### Asking what is actually running
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
capabilities → transport: { configured, running, agrees, evidence, configuredSource, disagreeingAgents }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`configured` is the label. **`running` is answered by calling the transport in
|
|
194
|
+
this process** — `available()`, then a `probe()` of a deliberately bogus target —
|
|
195
|
+
and `evidence` carries what those calls returned. The two are separate fields
|
|
196
|
+
because they are separate facts, and `agrees` exists so that reading one cannot be
|
|
197
|
+
mistaken for knowing the other.
|
|
198
|
+
|
|
199
|
+
`running` is `undefined` when no transport is wired. That is not "tmux by
|
|
200
|
+
default": a process with no transport delivers nothing, and naming a default there
|
|
201
|
+
would be the same substitution one layer down.
|
|
202
|
+
|
|
203
|
+
`disagreeingAgents` lists, by agent, every marker on disk whose `transport`
|
|
204
|
+
differs from the running one — a **mixed fleet**, which whole-fleet configuration
|
|
205
|
+
says should not exist. Markers are read without being modified, and a stale marker
|
|
206
|
+
is reported (tagged `[stale]`) rather than filtered out: a seat that may come back
|
|
207
|
+
on the wrong transport is more alarming than a live mismatch, not less.
|
|
208
|
+
|
|
209
|
+
## Migrating a fleet to a new transport
|
|
210
|
+
|
|
211
|
+
⚠ **A broken transport is the one defect that cannot report itself.** Agents go
|
|
212
|
+
quiet, and on a coordination bus quiet is indistinguishable from thinking. So the
|
|
213
|
+
order below is not ceremony — it is the only way to learn that a transport change
|
|
214
|
+
failed from something other than silence.
|
|
215
|
+
|
|
216
|
+
A change to transport code reaches a fleet through five states —
|
|
217
|
+
**merged · published · installed · restarted · observed** — and a merge is the
|
|
218
|
+
first of them. Every seat keeps running the code it loaded at startup until its
|
|
219
|
+
own process restarts, so:
|
|
220
|
+
|
|
221
|
+
1. **Publish and install.** Until the new version is installed, `capabilities`
|
|
222
|
+
keeps answering honestly about the *old* code. Check `answeredBy.module` to see
|
|
223
|
+
which copy a seat actually loaded — a global install and a checkout can both be
|
|
224
|
+
present, and only one is running.
|
|
225
|
+
2. **Restart ONE seat first, and verify it before the rest.** Call `capabilities`
|
|
226
|
+
on it: `transport.running` must be what you configured and `agrees` must be
|
|
227
|
+
true. Then exercise delivery — send that seat a message and confirm it arrives.
|
|
228
|
+
A staged restart turns a fleet-wide risk into a single-seat one.
|
|
229
|
+
3. **Watch `disagreeingAgents` while the fleet is mixed.** During a staged restart
|
|
230
|
+
it is *expected* to be non-empty; it should reach `[]` when the last seat is
|
|
231
|
+
restarted. A value that never empties is the signal that a seat did not come
|
|
232
|
+
back on the new transport.
|
|
233
|
+
4. **Restart the rest**, then confirm `agrees: true` and an empty
|
|
234
|
+
`disagreeingAgents` on every seat. A fleet where one seat still answers the old
|
|
235
|
+
way is the state this check exists to make visible.
|
|
236
|
+
|
|
237
|
+
**Roll back by reverting the config value, not by reverting markers.** Marker
|
|
238
|
+
writes carry both the generic `target` and the transport-specific `tmuxTarget`, so
|
|
239
|
+
a marker written by a newer server stays readable by an older one — a revert does
|
|
240
|
+
not orphan the fleet.
|
|
241
|
+
|
|
160
242
|
## Realtime vs. polling
|
|
161
243
|
|
|
162
244
|
`wait_for_message` is the cheap path: one tool call, server-side `fs.watch` + 500ms poll, capped at 60s. The model only pays for one round-trip per wait.
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Build identity of the RUNNING server process.
|
|
2
|
+
//
|
|
3
|
+
// The principle is the #28 pusher-freshness fix carried one layer up: stamp
|
|
4
|
+
// what you LOADED at init, compare against what's on disk NOW, and resolve the
|
|
5
|
+
// measured artifact from the code that is actually executing
|
|
6
|
+
// (import.meta.url), never from configuration — the thing that measures must
|
|
7
|
+
// be the thing that ran. A server process outlives `npm run build`; without a
|
|
8
|
+
// load-time sample there is nothing truthful to compare the on-disk build to,
|
|
9
|
+
// and a server running pre-rebuild code stamps transport markers with logic
|
|
10
|
+
// the rebuild replaced (observed live 2026-07-29: post-#28 attach spawned a
|
|
11
|
+
// pusher with no `--agent` argv and a single-file freshness stamp, agreeing
|
|
12
|
+
// with the new on-disk check only by coincidence).
|
|
13
|
+
import { readdirSync, statSync, readFileSync } from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
// Newest mtime (epoch ms) across every file under `dir` (recursive) whose
|
|
17
|
+
// name ends with one of `exts`. Returns undefined when the dir is missing or
|
|
18
|
+
// unreadable — callers skip their check rather than guess. Pure over its
|
|
19
|
+
// arguments so tests exercise it on temp trees instead of touching real
|
|
20
|
+
// sources (a utimes on a shared checkout flips every live pusher's freshness
|
|
21
|
+
// while the suite runs files in parallel).
|
|
22
|
+
export function newestMtimeUnder(dir, exts) {
|
|
23
|
+
try {
|
|
24
|
+
let newest;
|
|
25
|
+
for (const rel of readdirSync(dir, { recursive: true })) {
|
|
26
|
+
const name = String(rel);
|
|
27
|
+
if (!exts.some((e) => name.endsWith(e)))
|
|
28
|
+
continue;
|
|
29
|
+
let m;
|
|
30
|
+
try {
|
|
31
|
+
m = statSync(path.join(dir, name)).mtimeMs;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
continue; // deleted mid-scan
|
|
35
|
+
}
|
|
36
|
+
if (newest === undefined || m > newest)
|
|
37
|
+
newest = m;
|
|
38
|
+
}
|
|
39
|
+
return newest;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// The dir this module was loaded FROM: dist/ in production, src/ under tsx.
|
|
46
|
+
// Either way it is the code actually running, which is the point.
|
|
47
|
+
export const BUILD_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
48
|
+
// .js for the compiled build, .ts for dev-mode (tsx src/server.ts) — both
|
|
49
|
+
// sides of every comparison use the same list, so the two modes are each
|
|
50
|
+
// self-consistent and can never be compared across.
|
|
51
|
+
const BUILD_EXTS = [".js", ".ts"];
|
|
52
|
+
// Sampled ONCE at module load: the newest mtime across the build this server
|
|
53
|
+
// process actually imported. A later `npm run build` rewrites dist/ under a
|
|
54
|
+
// still-running server; this value stays behind, which is exactly what
|
|
55
|
+
// doctor's server-build-drift check compares against.
|
|
56
|
+
export const SERVER_BUILD_MTIME = newestMtimeUnder(BUILD_DIR, BUILD_EXTS);
|
|
57
|
+
// The on-disk side of the comparison, statted fresh per call.
|
|
58
|
+
// AGENT_COORD_DIST_DIR is a test seam only: it redirects what doctor
|
|
59
|
+
// MEASURES so tests can stage a newer/older build in a temp dir — it never
|
|
60
|
+
// changes what the server loads.
|
|
61
|
+
export function onDiskBuildMtime() {
|
|
62
|
+
const dir = process.env.AGENT_COORD_DIST_DIR ?? BUILD_DIR;
|
|
63
|
+
return newestMtimeUnder(dir, BUILD_EXTS);
|
|
64
|
+
}
|
|
65
|
+
// The uncompiled side of the dist-behind-source comparison: newest mtime
|
|
66
|
+
// across src/**/*.ts, resolved as BUILD_DIR's sibling. undefined on a
|
|
67
|
+
// packaged install with no src/ (callers report "nothing to compare", never
|
|
68
|
+
// warn). Under tsx dev-mode BUILD_DIR *is* src/, so the comparison degrades
|
|
69
|
+
// to src-vs-src and reads ok — dev-mode has no build to fall behind.
|
|
70
|
+
// AGENT_COORD_SRC_DIR is the same test seam as AGENT_COORD_DIST_DIR:
|
|
71
|
+
// it redirects measurement only.
|
|
72
|
+
export function onDiskSourceMtime() {
|
|
73
|
+
const dir = process.env.AGENT_COORD_SRC_DIR ?? path.resolve(BUILD_DIR, "..", "src");
|
|
74
|
+
return newestMtimeUnder(dir, [".ts"]);
|
|
75
|
+
}
|
|
76
|
+
// Best-effort checkout identity, report-only: lets doctor NAME the build
|
|
77
|
+
// (`branch@sha` would be nicer, but HEAD's sha alone already makes
|
|
78
|
+
// mutable-checkout drift visible, which is all this claims). undefined when
|
|
79
|
+
// not a git checkout (npm install) — never an error.
|
|
80
|
+
export const SERVER_BUILD_SHA = (() => {
|
|
81
|
+
try {
|
|
82
|
+
let gitDir = path.resolve(BUILD_DIR, "..", ".git");
|
|
83
|
+
const st = statSync(gitDir);
|
|
84
|
+
if (st.isFile()) {
|
|
85
|
+
// A worktree's .git is a pointer file: "gitdir: <real dir>".
|
|
86
|
+
const ptr = readFileSync(gitDir, "utf8").trim();
|
|
87
|
+
if (!ptr.startsWith("gitdir:"))
|
|
88
|
+
return undefined;
|
|
89
|
+
gitDir = ptr.slice("gitdir:".length).trim();
|
|
90
|
+
}
|
|
91
|
+
const head = readFileSync(path.join(gitDir, "HEAD"), "utf8").trim();
|
|
92
|
+
if (!head.startsWith("ref:"))
|
|
93
|
+
return head.slice(0, 12); // detached
|
|
94
|
+
const ref = head.slice(4).trim();
|
|
95
|
+
try {
|
|
96
|
+
return readFileSync(path.join(gitDir, ref), "utf8").trim().slice(0, 12);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Ref may be packed. commondir handling is deliberately out of scope —
|
|
100
|
+
// best-effort means undefined beats wrong.
|
|
101
|
+
const packed = readFileSync(path.join(gitDir, "packed-refs"), "utf8");
|
|
102
|
+
for (const line of packed.split("\n")) {
|
|
103
|
+
if (line.endsWith(` ${ref}`))
|
|
104
|
+
return line.slice(0, 12);
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Phase 5.1 Task 11 — CLOSE THE LOOP: answer a capability question by
|
|
3
|
+
* EXERCISING the capability, in the process that would serve it.
|
|
4
|
+
*
|
|
5
|
+
* 11.1: NO TIMESTAMP ON THIS BOX CAN ANSWER A CAPABILITY QUESTION. Every
|
|
6
|
+
* artefact people reach for is a proxy that fails differently:
|
|
7
|
+
*
|
|
8
|
+
* an mtime tracks WRITES. A reinstall of byte-identical code moves
|
|
9
|
+
* it, and an edit that never got built does not.
|
|
10
|
+
* serverBuildMtime is stamped at ATTACH, so it tells you when a transport
|
|
11
|
+
* started, not what the running process contains.
|
|
12
|
+
* a version string is a LABEL someone typed into package.json. Measured
|
|
13
|
+
* today: a tarball published as 0.5.23 contained a change
|
|
14
|
+
* the version numbering said it could not, and the number
|
|
15
|
+
* was believed over the artefact for an hour.
|
|
16
|
+
* `npm view` answers from a cache, and told this fleet the wrong
|
|
17
|
+
* published version twice in one morning.
|
|
18
|
+
*
|
|
19
|
+
* Each is honest about something and dishonest about capability, and the
|
|
20
|
+
* failure is always the same shape: an ANSWER ABOUT A LABEL read as an answer
|
|
21
|
+
* about behaviour.
|
|
22
|
+
*
|
|
23
|
+
* 11.2: A RELEASE IS NOT DELIVERED UNTIL A SERVER THAT RESTARTED ANSWERS.
|
|
24
|
+
* `merged · published · installed · restarted · observed` — five states, and
|
|
25
|
+
* the last two are the ones that keep being skipped. A probe run INSIDE the
|
|
26
|
+
* server process is the only artefact that speaks for the loaded code: it
|
|
27
|
+
* cannot be satisfied by a file that exists, a version that matches, or a
|
|
28
|
+
* package that installed, because it calls the code and reports what happened.
|
|
29
|
+
*
|
|
30
|
+
* WHY A PROBE MAY NEVER READ A VERSION: if a probe branched on a version
|
|
31
|
+
* string it would inherit that string's dishonesty, and a fleet would then
|
|
32
|
+
* have a capability check that passes on a restarted-but-not-upgraded server.
|
|
33
|
+
* Probes call behaviour. The version travels beside the answer as CONTEXT and
|
|
34
|
+
* is labelled as such.
|
|
35
|
+
*/
|
|
36
|
+
import { prRefsIn } from "./tools/record-events.js";
|
|
37
|
+
import { EVENT_KIND_IDS } from "./tools/event-kinds.js";
|
|
38
|
+
import { suggestRecordType, typedRecordMode } from "./typed-records.js";
|
|
39
|
+
import { LEAD_REFUSED, PARKED_CATEGORIES } from "./tools/away.js";
|
|
40
|
+
import { recordAuthorityFor } from "./roles.js";
|
|
41
|
+
import { subscriptionHealth } from "./tools/events.js";
|
|
42
|
+
import { configuredTransport, runningTransport, isTmuxKind, targetOf, } from "./transports/index.js";
|
|
43
|
+
import { readAllTransportMarkers } from "./tools/registry.js";
|
|
44
|
+
/**
|
|
45
|
+
* Each probe calls a behaviour that did not exist before its release and
|
|
46
|
+
* reports what it observed. A probe that could pass without the code being
|
|
47
|
+
* loaded is not a probe — it is a restatement of the version.
|
|
48
|
+
*/
|
|
49
|
+
const PROBES = [
|
|
50
|
+
{
|
|
51
|
+
id: "typed-records-obligatory",
|
|
52
|
+
since: "0.26.10",
|
|
53
|
+
run: () => {
|
|
54
|
+
const s = suggestRecordType("DONE: shipped it", []);
|
|
55
|
+
const mode = typedRecordMode();
|
|
56
|
+
return {
|
|
57
|
+
present: s.type === "done" && (mode === "warn" || mode === "refuse"),
|
|
58
|
+
evidence: `suggestRecordType("DONE: …") -> '${s.type}', policy mode '${mode}'`,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "record-events-all-kinds",
|
|
64
|
+
since: "0.26.12",
|
|
65
|
+
run: () => {
|
|
66
|
+
const kinds = [...EVENT_KIND_IDS].sort();
|
|
67
|
+
const present = ["item", "phase", "pr", "task"].every((k) => kinds.includes(k));
|
|
68
|
+
return { present, evidence: `subscribable kinds: ${kinds.join(", ")}` };
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "multi-pr-citation",
|
|
73
|
+
since: "0.26.14",
|
|
74
|
+
run: () => {
|
|
75
|
+
const refs = prRefsIn("owner/repo#170, #173");
|
|
76
|
+
return {
|
|
77
|
+
present: refs.length === 2 && refs[1] === "owner/repo#173",
|
|
78
|
+
evidence: `prRefsIn("owner/repo#170, #173") -> [${refs.join(", ")}]`,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "away-means-david-away",
|
|
84
|
+
since: "0.26.13",
|
|
85
|
+
run: () => {
|
|
86
|
+
const present = "merge" in LEAD_REFUSED && PARKED_CATEGORIES.includes("licence");
|
|
87
|
+
return {
|
|
88
|
+
present,
|
|
89
|
+
evidence: `coord_away refuses [${Object.keys(LEAD_REFUSED).join(", ")}] for the lead; parks ${PARKED_CATEGORIES.length} categories`,
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "subscription-scanned-vs-evaluated",
|
|
95
|
+
since: "0.26.15",
|
|
96
|
+
run: () => {
|
|
97
|
+
// The capability is that "the machinery ran" and "your kind fired" are
|
|
98
|
+
// separable. Probed by asking for the state that used to be reported as
|
|
99
|
+
// broken: scanned, never evaluated — a healthy idle watch.
|
|
100
|
+
const base = { id: "p", agentId: "p", kind: "item", target: "t", createdAt: 0, lastEvaluatedAt: null, lastEventAt: null, delivered: [] };
|
|
101
|
+
const quiet = subscriptionHealth({ ...base, lastScannedAt: Date.now() });
|
|
102
|
+
const unscanned = subscriptionHealth({ ...base, lastScannedAt: null });
|
|
103
|
+
return {
|
|
104
|
+
present: quiet.level === "ok" && unscanned.level === "error",
|
|
105
|
+
evidence: `scanned+quiet -> '${quiet.level}', never-scanned -> '${unscanned.level}'`,
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "record-authority",
|
|
111
|
+
since: "0.24.0",
|
|
112
|
+
run: () => {
|
|
113
|
+
const worker = recordAuthorityFor({ roleId: "worker" });
|
|
114
|
+
return {
|
|
115
|
+
present: worker.mayNotEmit.includes("verdict"),
|
|
116
|
+
evidence: `a worker mayNotEmit: [${worker.mayNotEmit.join(", ")}]`,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
export function probeCapabilities(context) {
|
|
122
|
+
const probes = PROBES.map((p) => {
|
|
123
|
+
try {
|
|
124
|
+
const r = p.run();
|
|
125
|
+
return { id: p.id, since: p.since, present: r.present, evidence: r.evidence };
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
// A THROWN PROBE IS AN ABSENT CAPABILITY, NOT A BROKEN CHECK. Older code
|
|
129
|
+
// that lacks the symbol throws exactly here, and reporting that as an
|
|
130
|
+
// error rather than an absence would make the common case look like a
|
|
131
|
+
// malfunction.
|
|
132
|
+
return { id: p.id, since: p.since, present: false, evidence: `probe threw: ${e.message}` };
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const missing = probes.filter((p) => !p.present).map((p) => p.id);
|
|
136
|
+
return {
|
|
137
|
+
answeredBy: {
|
|
138
|
+
pid: process.pid,
|
|
139
|
+
startedAtIso: new Date(Date.now() - Math.round(process.uptime() * 1000)).toISOString(),
|
|
140
|
+
module: context.module,
|
|
141
|
+
versionLabel: context.versionLabel,
|
|
142
|
+
},
|
|
143
|
+
probes,
|
|
144
|
+
missing,
|
|
145
|
+
ok: missing.length === 0,
|
|
146
|
+
note: "Every line above was produced by CALLING the code in this process. `versionLabel` is a label someone typed " +
|
|
147
|
+
"into package.json and is context, never evidence — a published tarball has already been observed carrying a " +
|
|
148
|
+
"change its version said it could not. THIS ANSWER IS ABOUT ONE PROCESS: a release is delivered when every " +
|
|
149
|
+
"live agent's OWN server answers, which is the fifth state (merged · published · installed · restarted · observed). " +
|
|
150
|
+
"A server that has not restarted answers honestly about the old code it is still running.",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/* ── the verb ──────────────────────────────────────────────────────────────── */
|
|
154
|
+
import { resolveServerIdentity } from "./server-identity.js";
|
|
155
|
+
export const capabilitiesSchema = {};
|
|
156
|
+
/**
|
|
157
|
+
* Build the transport capability by CALLING things, then compare.
|
|
158
|
+
*
|
|
159
|
+
* Deliberately async and deliberately separate from `probeCapabilities`, which
|
|
160
|
+
* is synchronous: the transport answer requires I/O (a `tmux -V`, a pane probe),
|
|
161
|
+
* and making the sync report do I/O to obtain it would have meant reading the
|
|
162
|
+
* config instead — which is the substitution this whole field exists to refuse.
|
|
163
|
+
*/
|
|
164
|
+
export async function probeTransport() {
|
|
165
|
+
const conf = configuredTransport();
|
|
166
|
+
const running = await runningTransport();
|
|
167
|
+
// 3.4 — a mixed fleet must be detectable and LOUD. Every marker on disk is
|
|
168
|
+
// read and any that names a different transport is listed by agent, because a
|
|
169
|
+
// count alone tells you something is wrong and not where to look.
|
|
170
|
+
const disagreeingAgents = [];
|
|
171
|
+
try {
|
|
172
|
+
// READ-ONLY on purpose. The reaping loader would delete markers it judged
|
|
173
|
+
// not-live, so a diagnostic would mutate the fleet it is describing — and it
|
|
174
|
+
// would hide the remote kind, whose liveness needs a registry heartbeat.
|
|
175
|
+
for (const { marker, live } of await readAllTransportMarkers()) {
|
|
176
|
+
if (running.kind !== undefined && marker.transport !== running.kind) {
|
|
177
|
+
const where = isTmuxKind(marker.transport) ? targetOf(marker) : undefined;
|
|
178
|
+
disagreeingAgents.push({
|
|
179
|
+
agentId: marker.agentId,
|
|
180
|
+
marker: `${marker.transport}${where ? ` (${where})` : ""}${live ? "" : " [stale]"}`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// An unreadable transports dir is not evidence of a uniform fleet. Left
|
|
187
|
+
// empty, and the caller can see `running` was still answered.
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
configured: conf.kind,
|
|
191
|
+
running: running.kind,
|
|
192
|
+
agrees: running.kind === conf.kind,
|
|
193
|
+
evidence: running.evidence,
|
|
194
|
+
configuredSource: conf.source,
|
|
195
|
+
disagreeingAgents,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
export async function capabilitiesTool() {
|
|
199
|
+
const id = resolveServerIdentity();
|
|
200
|
+
const report = probeCapabilities({ module: id.path, versionLabel: id.version });
|
|
201
|
+
try {
|
|
202
|
+
return { ...report, transport: await probeTransport() };
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
// A THROWN TRANSPORT PROBE IS NOT A BROKEN VERB. An unknown configured value
|
|
206
|
+
// refuses at startup by design, and this verb is exactly what an operator
|
|
207
|
+
// reaches for to find out why — so it must still answer, and say what threw.
|
|
208
|
+
return {
|
|
209
|
+
...report,
|
|
210
|
+
transportError: `transport probe threw: ${e.message}`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,UAAU,EACV,QAAQ,GAET,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAa9D;;;;GAIG;AACH,MAAM,MAAM,GAAY;IACtB;QACE,EAAE,EAAE,0BAA0B;QAC9B,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,CAAC,GAAG,iBAAiB,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;YACpD,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;YAC/B,OAAO;gBACL,OAAO,EAAE,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,QAAQ,CAAC;gBACpE,QAAQ,EAAE,oCAAoC,CAAC,CAAC,IAAI,mBAAmB,IAAI,GAAG;aAC/E,CAAC;QACJ,CAAC;KACF;IACD;QACE,EAAE,EAAE,yBAAyB;QAC7B,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAU,CAAC,CAAC,CAAC;YACzF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,uBAAuB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAC1E,CAAC;KACF;IACD;QACE,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,IAAI,GAAG,QAAQ,CAAC,sBAAsB,CAAC,CAAC;YAC9C,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,gBAAgB;gBAC1D,QAAQ,EAAE,wCAAwC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;aACrE,CAAC;QACJ,CAAC;KACF;IACD;QACE,EAAE,EAAE,uBAAuB;QAC3B,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,OAAO,GAAG,OAAO,IAAI,YAAY,IAAI,iBAAiB,CAAC,QAAQ,CAAC,SAAkB,CAAC,CAAC;YAC1F,OAAO;gBACL,OAAO;gBACP,QAAQ,EAAE,uBAAuB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,iBAAiB,CAAC,MAAM,aAAa;aACpI,CAAC;QACJ,CAAC;KACF;IACD;QACE,EAAE,EAAE,mCAAmC;QACvC,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,GAAG,EAAE;YACR,uEAAuE;YACvE,wEAAwE;YACxE,2DAA2D;YAC3D,MAAM,IAAI,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAW,CAAC;YAClJ,MAAM,KAAK,GAAG,kBAAkB,CAAC,EAAE,GAAI,IAAe,EAAE,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,EAAW,CAAC,CAAC;YAC9F,MAAM,SAAS,GAAG,kBAAkB,CAAC,EAAE,GAAI,IAAe,EAAE,aAAa,EAAE,IAAI,EAAW,CAAC,CAAC;YAC5F,OAAO;gBACL,OAAO,EAAE,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,SAAS,CAAC,KAAK,KAAK,OAAO;gBAC5D,QAAQ,EAAE,qBAAqB,KAAK,CAAC,KAAK,wBAAwB,SAAS,CAAC,KAAK,GAAG;aACrF,CAAC;QACJ,CAAC;KACF;IACD;QACE,EAAE,EAAE,kBAAkB;QACtB,KAAK,EAAE,QAAQ;QACf,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;YACxD,OAAO;gBACL,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAC9C,QAAQ,EAAE,yBAAyB,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;aACnE,CAAC;QACJ,CAAC;KACF;CACF,CAAC;AAwDF,MAAM,UAAU,iBAAiB,CAAC,OAAiD;IACjF,MAAM,MAAM,GAAkB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC7C,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YAClB,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,yEAAyE;YACzE,sEAAsE;YACtE,sEAAsE;YACtE,eAAe;YACf,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,gBAAiB,CAAW,CAAC,OAAO,EAAE,EAAE,CAAC;QACxG,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClE,OAAO;QACL,UAAU,EAAE;YACV,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,YAAY,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE;YACtF,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,YAAY,EAAE,OAAO,CAAC,YAAY;SACnC;QACD,MAAM;QACN,OAAO;QACP,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;QACxB,IAAI,EACF,6GAA6G;YAC7G,8GAA8G;YAC9G,4GAA4G;YAC5G,qHAAqH;YACrH,0FAA0F;KAC7F,CAAC;AACJ,CAAC;AAED,kFAAkF;AAElF,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAW,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAEzC,2EAA2E;IAC3E,8EAA8E;IAC9E,kEAAkE;IAClE,MAAM,iBAAiB,GAA0C,EAAE,CAAC;IACpE,IAAI,CAAC;QACH,0EAA0E;QAC1E,6EAA6E;QAC7E,yEAAyE;QACzE,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,MAAM,uBAAuB,EAAE,EAAE,CAAC;YAC/D,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;gBACpE,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC1E,iBAAiB,CAAC,IAAI,CAAC;oBACrB,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE;iBACpF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,8DAA8D;IAChE,CAAC;IAED,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,IAAI;QACrB,OAAO,EAAE,OAAO,CAAC,IAAI;QACrB,MAAM,EAAE,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;QAClC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,gBAAgB,EAAE,IAAI,CAAC,MAAM;QAC7B,iBAAiB;KAClB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,EAAE,GAAG,qBAAqB,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAChF,IAAI,CAAC;QACH,OAAO,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,cAAc,EAAE,EAAE,CAAC;IAC1D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,6EAA6E;QAC7E,0EAA0E;QAC1E,6EAA6E;QAC7E,OAAO;YACL,GAAG,MAAM;YACT,cAAc,EAAE,0BAA2B,CAAW,CAAC,OAAO,EAAE;SACjE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/dist/prefix.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHERE WILL A NEW GLOBAL INSTALL LAND, AND IS IT WHERE THE FLEET LOADS FROM?
|
|
3
|
+
*
|
|
4
|
+
* These are two different questions and neither answers the other. The
|
|
5
|
+
* `server-build-drift` check names which copy is RUNNING; this names where the
|
|
6
|
+
* NEXT copy will be written. A run of `npm i -g agent-coord-mcp` that prints
|
|
7
|
+
* "added 1 package" is not evidence it landed anywhere that runs.
|
|
8
|
+
*
|
|
9
|
+
* MEASURED ON THIS BOX, 2026-08-28 — the divergence is not hypothetical:
|
|
10
|
+
* npm prefix -g -> .../node/v22.22.2
|
|
11
|
+
* every live fleet server-> .../node/v22.21.1/lib/node_modules/agent-coord-mcp
|
|
12
|
+
* Three prefixes exist here (two nvm, one /opt/homebrew), `npm prefix -g` is
|
|
13
|
+
* PATH-dependent, and nvm switches it per shell. So the install target and the
|
|
14
|
+
* load target had silently diverged, and a successful install would have
|
|
15
|
+
* updated a copy nothing loads. That gap cost the fleet a day.
|
|
16
|
+
*/
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
/**
|
|
19
|
+
* The global root a module path sits under, or null if it is not in one.
|
|
20
|
+
* `/p/lib/node_modules/agent-coord-mcp/dist` -> `/p`
|
|
21
|
+
*/
|
|
22
|
+
export function prefixOf(modulePath) {
|
|
23
|
+
if (!modulePath)
|
|
24
|
+
return null;
|
|
25
|
+
// Split on the LAST occurrence: a global prefix can itself live under a path
|
|
26
|
+
// containing `node_modules`, and taking the first match would name an
|
|
27
|
+
// ancestor that installs nothing.
|
|
28
|
+
const marker = `${path.sep}lib${path.sep}node_modules${path.sep}`;
|
|
29
|
+
const i = modulePath.lastIndexOf(marker);
|
|
30
|
+
if (i === -1)
|
|
31
|
+
return null;
|
|
32
|
+
return modulePath.slice(0, i);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `loadPrefix` is where THIS server was loaded from; `installPrefix` is what
|
|
36
|
+
* `npm prefix -g` answered, and `npmPath` is which npm answered it — because a
|
|
37
|
+
* prefix without the binary that reported it cannot be reproduced by anyone.
|
|
38
|
+
*/
|
|
39
|
+
export function prefixVerdict(loadPrefix, installPrefix, npmPath) {
|
|
40
|
+
const via = npmPath ? ` (asked: ${npmPath})` : "";
|
|
41
|
+
// NOT DETERMINED IS NOT MATCHING. Both unknown branches are warnings that say
|
|
42
|
+
// what could not be established, never an "ok" over an unasked question.
|
|
43
|
+
// NOT APPLICABLE IS NOT THE SAME AS UNCHECKED, and conflating them is how a
|
|
44
|
+
// check earns its way into being ignored. A dev checkout has no load prefix
|
|
45
|
+
// BY CONSTRUCTION: this process is not the copy the fleet loads, so there is
|
|
46
|
+
// no divergence for it to have. Warning on every dev run would fire on every
|
|
47
|
+
// test run and every local session — noise, which is what a denylist does.
|
|
48
|
+
//
|
|
49
|
+
// The question is still ASKED where it can be answered: a session running
|
|
50
|
+
// from a global install has a load prefix, and that is where the fleet lives.
|
|
51
|
+
if (!loadPrefix)
|
|
52
|
+
return { level: "ok", detail: `not applicable: this server runs from a dev checkout, not a global install${via}, so it is not the copy the fleet loads and has no prefix to diverge from. Run \`doctor\` in an installed session to compare install target against load target.` };
|
|
53
|
+
if (!installPrefix)
|
|
54
|
+
return { level: "warn", detail: `could not determine the global install prefix${via} — \`npm prefix -g\` gave no answer, so where a new copy would land is UNKNOWN. Running from ${loadPrefix}.` };
|
|
55
|
+
if (path.resolve(loadPrefix) === path.resolve(installPrefix))
|
|
56
|
+
return { level: "ok", detail: `a global install would land where this server loads from (${loadPrefix})${via}` };
|
|
57
|
+
return {
|
|
58
|
+
level: "error",
|
|
59
|
+
detail: `INSTALL PREFIX AND LOAD PREFIX DIVERGE. A global install from this shell writes to ${installPrefix}${via}, but this server is running from ${loadPrefix}. ` +
|
|
60
|
+
`A successful "added 1 package" would update a copy nothing loads, and every check that reads a VERSION would keep reporting the old one truthfully. ` +
|
|
61
|
+
`Install with an explicit prefix (\`npm i -g --prefix ${loadPrefix} <pkg>\`) or switch node/nvm to the version owning ${loadPrefix} before installing.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=prefix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefix.js","sourceRoot":"","sources":["../src/prefix.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,UAA8B;IACrD,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,6EAA6E;IAC7E,sEAAsE;IACtE,kCAAkC;IAClC,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,eAAe,IAAI,CAAC,GAAG,EAAE,CAAC;IAClE,MAAM,CAAC,GAAG,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC;AAOD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAAyB,EAAE,aAA4B,EAAE,OAAgB;IACrG,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,YAAY,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAElD,8EAA8E;IAC9E,yEAAyE;IACzE,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,EAAE;IACF,0EAA0E;IAC1E,8EAA8E;IAC9E,IAAI,CAAC,UAAU;QACb,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,6EAA6E,GAAG,kKAAkK,EAAE,CAAC;IACrR,IAAI,CAAC,aAAa;QAChB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,gDAAgD,GAAG,gGAAgG,UAAU,GAAG,EAAE,CAAC;IAErM,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;QAC1D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,6DAA6D,UAAU,IAAI,GAAG,EAAE,EAAE,CAAC;IAEnH,OAAO;QACL,KAAK,EAAE,OAAO;QACd,MAAM,EACJ,sFAAsF,aAAa,GAAG,GAAG,qCAAqC,UAAU,IAAI;YAC5J,sJAAsJ;YACtJ,wDAAwD,UAAU,sDAAsD,UAAU,qBAAqB;KAC1J,CAAC;AACJ,CAAC"}
|
package/dist/roles.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Canonical role identity, server side (Phase 8 Task 4).
|
|
2
|
+
//
|
|
3
|
+
// MIRROR of hooks/roles.mjs — tsconfig's rootDir is `src`, so this cannot
|
|
4
|
+
// import the hook copy, and the hook copy must stay build-free (the pusher
|
|
5
|
+
// loads it from a bare checkout). test/roles.test.mjs asserts the two agree;
|
|
6
|
+
// change one, change the other.
|
|
7
|
+
//
|
|
8
|
+
// See hooks/roles.mjs for the rationale behind `explicit` and the word-match
|
|
9
|
+
// fallback.
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
export function slugifyRole(role) {
|
|
12
|
+
return String(role ?? "")
|
|
13
|
+
.trim()
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
16
|
+
.replace(/^-+|-+$/g, "");
|
|
17
|
+
}
|
|
18
|
+
export const GATE_RUNNER_ROLE_IDS = new Set(["qa", "quality", "coordinator", "gate"]);
|
|
19
|
+
export const COORDINATOR_ROLE_IDS = new Set(["coordinator"]);
|
|
20
|
+
// CI receives express-lane DONE: wake. Not a gate-runner — must not grant verdict.
|
|
21
|
+
export const CI_ROLE_IDS = new Set(["ci"]);
|
|
22
|
+
// Console / watchers. May send prefixed messages. Not a gate-runner.
|
|
23
|
+
export const AUTOMATION_ROLE_IDS = new Set(["automation"]);
|
|
24
|
+
// A HUMAN READER. The one carve-out in the typed-record rule (Phase 5.1 Task
|
|
25
|
+
// 12.3): canon already rules "David-facing messages may use normal prose", and
|
|
26
|
+
// a blanket requirement would break the single channel whose reader is a
|
|
27
|
+
// person. `david` is here as well as `human` because the registry entry that
|
|
28
|
+
// exists on this bus today declares role "David" — the id derives to `david`,
|
|
29
|
+
// and a rule that only recognised the canonical spelling would have refused
|
|
30
|
+
// the exact traffic it is written to protect.
|
|
31
|
+
export const HUMAN_ROLE_IDS = new Set(["human", "david"]);
|
|
32
|
+
export function resolveRole(role) {
|
|
33
|
+
if (role === null || role === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (typeof role === "string") {
|
|
36
|
+
const roleId = slugifyRole(role);
|
|
37
|
+
return roleId ? { roleId, displayName: role, explicit: false } : undefined;
|
|
38
|
+
}
|
|
39
|
+
if (typeof role === "object") {
|
|
40
|
+
const declared = typeof role.roleId === "string" ? slugifyRole(role.roleId) : "";
|
|
41
|
+
const name = typeof role.displayName === "string" && role.displayName
|
|
42
|
+
? role.displayName
|
|
43
|
+
: typeof role.role === "string" && role.role
|
|
44
|
+
? role.role
|
|
45
|
+
: undefined;
|
|
46
|
+
if (declared)
|
|
47
|
+
return { roleId: declared, displayName: name ?? declared, explicit: true };
|
|
48
|
+
if (name)
|
|
49
|
+
return resolveRole(name);
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
export function roleMatches(role, allowedIds) {
|
|
54
|
+
const resolved = resolveRole(role);
|
|
55
|
+
if (!resolved)
|
|
56
|
+
return false;
|
|
57
|
+
if (allowedIds.has(resolved.roleId))
|
|
58
|
+
return true;
|
|
59
|
+
// Word-split applies to DECLARED ids too. It used to be derived-only ("frozen
|
|
60
|
+
// ids match exactly"), which made precision cost authority: {roleId:"coord-qa"}
|
|
61
|
+
// was refused `verdict` while the vaguer bare string "coord-qa" passed via the
|
|
62
|
+
// split — a consumer fleet's QA verdicts were never machine-authorized (2026-08-21).
|
|
63
|
+
// Freezing still governs identity (the id cannot change); it must never grant
|
|
64
|
+
// LESS than the derived twin of the same spelling.
|
|
65
|
+
return resolved.roleId.split("-").some((word) => allowedIds.has(word));
|
|
66
|
+
}
|
|
67
|
+
// The canonical role vocabulary — every authority set plus the classes the
|
|
68
|
+
// cards register. Not an allowlist: any roleId may register; a non-canonical
|
|
69
|
+
// one gets a warning naming this set so the next card uses the exact spelling.
|
|
70
|
+
export const CANONICAL_ROLE_IDS = new Set([
|
|
71
|
+
"coordinator",
|
|
72
|
+
"worker",
|
|
73
|
+
"aide",
|
|
74
|
+
"qa",
|
|
75
|
+
"quality",
|
|
76
|
+
"gate",
|
|
77
|
+
"ci",
|
|
78
|
+
"audit",
|
|
79
|
+
"automation",
|
|
80
|
+
"human",
|
|
81
|
+
]);
|
|
82
|
+
export function isGateRunner(role) {
|
|
83
|
+
return roleMatches(role, GATE_RUNNER_ROLE_IDS);
|
|
84
|
+
}
|
|
85
|
+
export function isCoordinator(role) {
|
|
86
|
+
return roleMatches(role, COORDINATOR_ROLE_IDS);
|
|
87
|
+
}
|
|
88
|
+
export function isCi(role) {
|
|
89
|
+
return roleMatches(role, CI_ROLE_IDS);
|
|
90
|
+
}
|
|
91
|
+
export function isAutomation(role) {
|
|
92
|
+
return roleMatches(role, AUTOMATION_ROLE_IDS);
|
|
93
|
+
}
|
|
94
|
+
export function isHuman(role) {
|
|
95
|
+
return roleMatches(role, HUMAN_ROLE_IDS);
|
|
96
|
+
}
|
|
97
|
+
// Which roles may emit which record.type at the send path. Enforcement lives
|
|
98
|
+
// in messaging.ts (checkRecordAuthority); the table lives here so register/join
|
|
99
|
+
// can ECHO the consequence back at onboarding — a role that cannot emit `go`
|
|
100
|
+
// should learn it when it registers, not when it sends its first work order.
|
|
101
|
+
//
|
|
102
|
+
// NOT A TRUST BOUNDARY — roles are self-declared. See checkRecordAuthority.
|
|
103
|
+
export const RECORD_AUTHORITY = {
|
|
104
|
+
verdict: { roles: GATE_RUNNER_ROLE_IDS, label: "gate-runner" },
|
|
105
|
+
go: { roles: COORDINATOR_ROLE_IDS, label: "coordinator" },
|
|
106
|
+
scope: { roles: COORDINATOR_ROLE_IDS, label: "coordinator" },
|
|
107
|
+
};
|
|
108
|
+
// Split the restricted record types into what this role may and may not emit.
|
|
109
|
+
// Unrestricted types are omitted from both lists — they are nobody's business.
|
|
110
|
+
export function recordAuthorityFor(role) {
|
|
111
|
+
const mayEmit = [];
|
|
112
|
+
const mayNotEmit = [];
|
|
113
|
+
for (const [type, rule] of Object.entries(RECORD_AUTHORITY)) {
|
|
114
|
+
(roleMatches(role, rule.roles) ? mayEmit : mayNotEmit).push(type);
|
|
115
|
+
}
|
|
116
|
+
return { mayEmit, mayNotEmit };
|
|
117
|
+
}
|
|
118
|
+
// Wire shape for `role` on register/join. Either free text (v1: `role: "qa
|
|
119
|
+
// lead"`) or a declared identity (`{roleId: "qa", displayName: "QA gate"}`).
|
|
120
|
+
// Both are supported forever — the string form is not deprecated, it just
|
|
121
|
+
// leaves the id derived rather than frozen.
|
|
122
|
+
//
|
|
123
|
+
// Lives here rather than in tools/ because registry.ts and transport.ts import
|
|
124
|
+
// each other; a schema in either would be a temporal-dead-zone hazard.
|
|
125
|
+
export const roleInputSchema = z.union([
|
|
126
|
+
z.string(),
|
|
127
|
+
z.object({
|
|
128
|
+
roleId: z.string().min(1).optional(),
|
|
129
|
+
displayName: z.string().min(1).optional(),
|
|
130
|
+
}),
|
|
131
|
+
]);
|
|
132
|
+
//# sourceMappingURL=roles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roles.js","sourceRoot":"","sources":["../src/roles.ts"],"names":[],"mappings":"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"}
|