agent-coord-mcp 0.26.17 → 0.26.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/package.json +1 -1
  2. package/src/server.ts +2 -2
  3. package/src/tools/admin.ts +8 -1
  4. package/src/tools/away.ts +48 -4
  5. package/src/tools/records.ts +274 -18
  6. package/src/tools/rooms.ts +2 -1
  7. package/src/tools/shared.ts +18 -0
  8. package/src/tools/stall.ts +29 -1
  9. package/src/tools/transport.ts +12 -4
  10. package/src/tools/work.ts +70 -4
  11. package/src/tools/worktrees.ts +64 -2
  12. package/src/work.ts +4 -0
  13. package/dist/build.js +0 -113
  14. package/dist/build.js.map +0 -1
  15. package/dist/capabilities.js +0 -158
  16. package/dist/capabilities.js.map +0 -1
  17. package/dist/prefix.js +0 -64
  18. package/dist/prefix.js.map +0 -1
  19. package/dist/roles.js +0 -132
  20. package/dist/roles.js.map +0 -1
  21. package/dist/server-identity.js +0 -82
  22. package/dist/server-identity.js.map +0 -1
  23. package/dist/server.js +0 -646
  24. package/dist/server.js.map +0 -1
  25. package/dist/store.js +0 -553
  26. package/dist/store.js.map +0 -1
  27. package/dist/tools/admin.js +0 -317
  28. package/dist/tools/admin.js.map +0 -1
  29. package/dist/tools/attention.js +0 -73
  30. package/dist/tools/attention.js.map +0 -1
  31. package/dist/tools/away.js +0 -243
  32. package/dist/tools/away.js.map +0 -1
  33. package/dist/tools/board-ref.js +0 -208
  34. package/dist/tools/board-ref.js.map +0 -1
  35. package/dist/tools/event-kinds.js +0 -39
  36. package/dist/tools/event-kinds.js.map +0 -1
  37. package/dist/tools/events.js +0 -234
  38. package/dist/tools/events.js.map +0 -1
  39. package/dist/tools/index.js +0 -14
  40. package/dist/tools/index.js.map +0 -1
  41. package/dist/tools/logwatch.js +0 -85
  42. package/dist/tools/logwatch.js.map +0 -1
  43. package/dist/tools/messaging.js +0 -706
  44. package/dist/tools/messaging.js.map +0 -1
  45. package/dist/tools/record-events.js +0 -380
  46. package/dist/tools/record-events.js.map +0 -1
  47. package/dist/tools/records.js +0 -778
  48. package/dist/tools/records.js.map +0 -1
  49. package/dist/tools/registry.js +0 -500
  50. package/dist/tools/registry.js.map +0 -1
  51. package/dist/tools/render.js +0 -2
  52. package/dist/tools/render.js.map +0 -1
  53. package/dist/tools/rooms.js +0 -210
  54. package/dist/tools/rooms.js.map +0 -1
  55. package/dist/tools/rotate.js +0 -143
  56. package/dist/tools/rotate.js.map +0 -1
  57. package/dist/tools/scopes.js +0 -126
  58. package/dist/tools/scopes.js.map +0 -1
  59. package/dist/tools/shared.js +0 -86
  60. package/dist/tools/shared.js.map +0 -1
  61. package/dist/tools/stall.js +0 -387
  62. package/dist/tools/stall.js.map +0 -1
  63. package/dist/tools/transport.js +0 -1871
  64. package/dist/tools/transport.js.map +0 -1
  65. package/dist/tools/work.js +0 -319
  66. package/dist/tools/work.js.map +0 -1
  67. package/dist/tools/worktrees.js +0 -339
  68. package/dist/tools/worktrees.js.map +0 -1
  69. package/dist/typed-records.js +0 -174
  70. package/dist/typed-records.js.map +0 -1
  71. package/dist/work.js +0 -2
  72. package/dist/work.js.map +0 -1
@@ -79,6 +79,7 @@ import {
79
79
  setOffset,
80
80
  sysMsg,
81
81
  moveFile,
82
+ agentIdFromCursorFilename,
82
83
  STALE_MS,
83
84
  EVICT_MS,
84
85
  MAX_WAIT_MS,
@@ -1623,7 +1624,7 @@ export async function doctorTool(args: { fix?: boolean; maxFileBytes?: number })
1623
1624
  }
1624
1625
  const orphanCursor: string[] = [];
1625
1626
  for (const fname of await listCursorFiles()) {
1626
- const id = fname.replace(/\.json$/, "");
1627
+ const { id } = agentIdFromCursorFilename(fname);
1627
1628
  if (!known.has(id)) {
1628
1629
  orphanCursor.push(id);
1629
1630
  if (fix) {
@@ -1652,10 +1653,17 @@ export async function doctorTool(args: { fix?: boolean; maxFileBytes?: number })
1652
1653
  };
1653
1654
 
1654
1655
  // 4. Cursor offsets past end-of-file (would return [] forever).
1656
+ //
1657
+ // q-40449919: `id` must be the AGENT's id, not the cursor filename minus
1658
+ // one extension — a push cursor's filename is `<agent>.push.json`, and
1659
+ // `inboxFile(id)` on the unfixed `<agent>.push` resolves to a file that
1660
+ // never existed, so `inboxMax` reads 0 and the cursor's real, correct
1661
+ // offset compares as past-EOF against an empty file that was never the
1662
+ // agent's inbox in the first place.
1655
1663
  {
1656
1664
  const broken: string[] = [];
1657
1665
  for (const fname of await listCursorFiles()) {
1658
- const id = fname.replace(/\.json$/, "");
1666
+ const { id, kind } = agentIdFromCursorFilename(fname);
1659
1667
  const cursorPath = path.join(CURSOR_DIR, fname);
1660
1668
  const cursor = await readJson<Cursor>(cursorPath, {});
1661
1669
  const overflow: string[] = [];
@@ -1670,7 +1678,7 @@ export async function doctorTool(args: { fix?: boolean; maxFileBytes?: number })
1670
1678
  if (off > max) overflow.push(`roomOffsets[${chan}] ${off}>${max}`);
1671
1679
  }
1672
1680
  if (overflow.length) {
1673
- broken.push(`${id}: ${overflow.join(", ")}`);
1681
+ broken.push(`${id}${kind === "push" ? " (push)" : ""}: ${overflow.join(", ")}`);
1674
1682
  if (fix) {
1675
1683
  await updateJson<Cursor>(cursorPath, {}, (c) => {
1676
1684
  if ((c.inboxOffset ?? 0) > inboxMax) c.inboxOffset = inboxMax;
@@ -1684,7 +1692,7 @@ export async function doctorTool(args: { fix?: boolean; maxFileBytes?: number })
1684
1692
  }
1685
1693
  return c;
1686
1694
  });
1687
- fixed.push(`clamped cursor offsets for ${id}`);
1695
+ fixed.push(`clamped cursor offsets for ${id}${kind === "push" ? " (push)" : ""}`);
1688
1696
  }
1689
1697
  }
1690
1698
  }
package/src/tools/work.ts CHANGED
@@ -13,6 +13,8 @@ import {
13
13
  projectV1ToLanes,
14
14
  queueItemsOf,
15
15
  renderWorkDoc,
16
+ renderWorkDocForWrite,
17
+ zeroIsUnparsed,
16
18
  workDocIssues,
17
19
  workDocLegacyWriteIssues,
18
20
  LANES_V0_WRITE_ISSUE,
@@ -55,6 +57,13 @@ export type StoredWorkDoc = {
55
57
  kind: "queue" | "done" | "board" | "legacy";
56
58
  path: string;
57
59
  doc: WorkDoc;
60
+ /**
61
+ * The bytes as read. Kept because a COUNT OF ZERO cannot be interpreted
62
+ * without them: `zeroIsUnparsed` needs to know whether the document had
63
+ * content the parser did not understand, and the parsed doc has already
64
+ * discarded that distinction (q-3e7c81a5).
65
+ */
66
+ source: string;
58
67
  };
59
68
 
60
69
  export type StoredFactsDoc = {
@@ -77,7 +86,7 @@ async function loadDoc(repo: string, f: { kind: WorkFileKind; path: string }): P
77
86
  const parsed = parseFactsDoc(source);
78
87
  return { kind: "facts", path: f.path, source, entries: parsed.entries, issues: parsed.issues };
79
88
  }
80
- return { kind: f.kind, path: f.path, doc: parseWorkDoc(source) };
89
+ return { kind: f.kind, path: f.path, doc: parseWorkDoc(source), source };
81
90
  }
82
91
 
83
92
  function docIssues(d: StoredDoc): string[] {
@@ -97,12 +106,56 @@ function importedSummary(d: StoredDoc) {
97
106
  };
98
107
  }
99
108
  const issues = [...workDocIssues(d.doc), ...workDocLegacyWriteIssues(d.doc)];
100
- return {
101
- path: d.path,
102
- kind: d.kind,
109
+ const counts = {
103
110
  queue: queueItemsOf(d.doc).length,
104
111
  done: doneEntriesOf(d.doc).length,
105
112
  board: listWorkBoardOf(d.doc).length,
113
+ };
114
+ // A ZERO THAT CANNOT BE TRUSTED IS NAMED, NOT RETURNED BARE.
115
+ //
116
+ // These three were plain `.length`, so a document the parser did not
117
+ // understand reported the same zero as an empty one. A consumer's dashboard
118
+ // read that and showed a fleet as IDLE on a night it merged 27 PRs; they
119
+ // declined to enable these verbs rather than ship a display they could not
120
+ // trust (q-3e7c81a5).
121
+ //
122
+ // The counts keep their shape and meaning — a reader that only wants numbers
123
+ // is unaffected — and `unparsed` says which of them are unanswered questions
124
+ // rather than answers. The predicate is the SEAM's, shared with `doctor`'s
125
+ // queue-done-loop rather than restated here.
126
+ // ONLY THE AXES THIS DOCUMENT CLAIMS TO CARRY.
127
+ //
128
+ // Caught by the test's own output rather than by review: qualifying all three
129
+ // for every file reported `docs/QUEUE.md` as having unparsed `done` and `board`
130
+ // axes, which is nonsense — a queue document is not expected to hold done
131
+ // entries, so its zero there is not an unanswered question, it is a category
132
+ // error on my part. Scoping the qualification to the document's KIND is the
133
+ // same population discipline the count itself needed: a claim about an axis a
134
+ // file never carried is noise, and noise is what teaches a reader to skip the
135
+ // real one. `legacy` is the single-file BACKLOG.md, which carries both regions.
136
+ const AXES_BY_KIND = {
137
+ queue: ["queue"],
138
+ done: ["done"],
139
+ board: ["board"],
140
+ legacy: ["queue", "done"],
141
+ } as const;
142
+ const unparsed = (AXES_BY_KIND[d.kind] as readonly (keyof typeof counts)[]).filter((axis) =>
143
+ zeroIsUnparsed(counts[axis], d.source),
144
+ );
145
+ return {
146
+ path: d.path,
147
+ kind: d.kind,
148
+ ...counts,
149
+ ...(unparsed.length
150
+ ? {
151
+ unparsed,
152
+ unparsedNote:
153
+ `${d.path} has content the parser did not understand: ${unparsed.join(", ")} parsed to ZERO. ` +
154
+ `That is an unanswered question, not an empty document — do not render it as idle, empty or done. ` +
155
+ `Either the file uses a shape this grammar does not accept, or it is malformed; both need a human, and ` +
156
+ `neither is "nothing there".`,
157
+ }
158
+ : {}),
106
159
  ...(issues.length ? { issues } : {}),
107
160
  };
108
161
  }
@@ -359,6 +412,19 @@ export async function exportWorkTool(args: {
359
412
  });
360
413
  continue;
361
414
  }
415
+ // DELIBERATELY THE PURE RENDER, NOT THE STAMPING ONE (q-c50e9b83).
416
+ //
417
+ // I routed this through `renderWorkDocForWrite` first and it broke three
418
+ // pre-existing tests that assert `import → export` writes the real documents
419
+ // back BYTE-IDENTICALLY. They were right and the change was wrong: this is a
420
+ // TRANSPORT, not an authoring path. Its contract is to reproduce a document,
421
+ // and a transport that silently edits content the caller never asked it to
422
+ // touch is a worse defect than the one being fixed — it would also mean
423
+ // exporting into ANOTHER project's documents mutates them.
424
+ //
425
+ // So the primary is scoped to the AUTHORING writer (`land`, via records.ts),
426
+ // and an unstamped item arriving through this path is the backstop's
427
+ // population: it cannot reach `main` without passing a push.
362
428
  const rendered = renderWorkDoc(d.doc);
363
429
  const current = existsSync(target) ? await fsp.readFile(target, "utf8") : null;
364
430
  const declared = scopes.documents.find((s) => s.path === d.path);
@@ -264,6 +264,45 @@ export async function refreshWorktreesTool(args: { repo: string; base: string; a
264
264
  return { ok: false as const, error: `${ref} does not resolve — nothing to fast-forward onto` };
265
265
  }
266
266
 
267
+ /**
268
+ * Are the commits HEAD has beyond `tip` ALREADY LANDED there?
269
+ *
270
+ * `rev-list --count <tip>..HEAD` is pure ANCESTRY, and under squash merge a landed
271
+ * branch's own shas never enter the base's history — so `ahead` stays > 0 FOREVER
272
+ * for work that shipped (q-5c40db91; 20 of the last 20 merges on this repo's main
273
+ * are single-parent). Reading `ahead > 0` as "this tree holds unlanded work" makes
274
+ * the verb whose job is fast-forwarding idle trees classify a FINISHED tree as
275
+ * mid-slice, permanently.
276
+ *
277
+ * MEASURED, and it cost a lane cycle: a `claim` was refused because a worker's tree
278
+ * "reads as 1 commit not on origin/main" while that branch had been squash-merged an
279
+ * hour earlier. Same inverted alarm as the item's headline — landed work reported as
280
+ * unmerged, inviting preservation of work already shipped — inside our own tooling.
281
+ *
282
+ * `git cherry` compares PATCH IDS, so every line marked `-` means every commit's patch
283
+ * is already upstream. It is NOT complete: several commits squashed into one carry a
284
+ * different combined patch id, so a negative is INCONCLUSIVE and is reported as
285
+ * mid-slice exactly as before. Only a positive changes the verdict, which keeps the
286
+ * failure direction the same as today's for everything this cannot prove.
287
+ */
288
+ function aheadIsLanded(at: string, tip: string): boolean {
289
+ // `git` here THROWS rather than returning null, so the catch is what keeps an
290
+ // UNREADABLE answer out of the landed bucket: not measured is not landed, and
291
+ // the tree stays mid-slice, which is the direction that refuses rather than
292
+ // the direction that tells someone their work is safe to discard.
293
+ let cherry: string;
294
+ try {
295
+ cherry = git(at, ["cherry", tip, "HEAD"]);
296
+ } catch {
297
+ return false;
298
+ }
299
+ if (cherry.trim().length === 0) return false;
300
+ return cherry
301
+ .trim()
302
+ .split("\n")
303
+ .every((l) => l.trim().startsWith("-"));
304
+ }
305
+
267
306
  const results = [];
268
307
  for (const w of listWorktrees(repo)) {
269
308
  const at = w.path;
@@ -308,7 +347,9 @@ export async function refreshWorktreesTool(args: { repo: string; base: string; a
308
347
  (dirty
309
348
  ? " and DIRTY — not fast-forwarded; uncommitted work is what a diff cannot show"
310
349
  : ahead !== "0"
311
- ? ` and ${ahead} ahead — not fast-forwarded; it has commits ${ref} does not`
350
+ ? aheadIsLanded(at, tip)
351
+ ? ` and ${ahead} ahead whose patches are ALREADY UPSTREAM — squash-merged, so ancestry will never agree; not fast-forwarded, but nothing here needs preserving`
352
+ : ` and ${ahead} ahead — not fast-forwarded; it has commits ${ref} does not`
312
353
  : args.apply
313
354
  ? ""
314
355
  : " — pass apply:true to fast-forward it"),
@@ -337,7 +378,28 @@ export async function refreshWorktreesTool(args: { repo: string; base: string; a
337
378
  /* treated as diverged below */
338
379
  }
339
380
  if (ahead !== "0") {
340
- results.push({ path: w.path, action: "refused", why: `MID-SLICE: ${ahead} commit(s) not on ${ref}. Refusing rather than --force.` });
381
+ // LANDED, NOT MID-SLICE — the ancestry count cannot tell these apart.
382
+ if (aheadIsLanded(at, tip)) {
383
+ results.push({
384
+ path: w.path,
385
+ action: "landed",
386
+ why:
387
+ `${ahead} commit(s) are not ancestors of ${ref}, but every one of their PATCHES is already upstream — ` +
388
+ `this branch was SQUASH-MERGED and its work has shipped. Ancestry can never say so: a squash writes a new ` +
389
+ `commit, so these shas will read as "not on ${ref}" forever. Nothing here needs preserving; detach to ${ref} ` +
390
+ `(or delete the branch) and this tree is reusable. NOT fast-forwarded automatically — moving a tree off ` +
391
+ `committed work is a decision, not a refresh.`,
392
+ });
393
+ continue;
394
+ }
395
+ results.push({
396
+ path: w.path,
397
+ action: "refused",
398
+ why:
399
+ `MID-SLICE: ${ahead} commit(s) not on ${ref}, and their patches are NOT upstream. Refusing rather than --force. ` +
400
+ `(A squash of SEVERAL commits into one changes the combined patch id, so this cannot prove the negative — ` +
401
+ `it reports mid-slice, which is the same direction it always failed in.)`,
402
+ });
341
403
  continue;
342
404
  }
343
405
  if (!args.apply) {
package/src/work.ts CHANGED
@@ -17,6 +17,10 @@ export {
17
17
  renderBoardRow,
18
18
  parseWorkDoc,
19
19
  renderWorkDoc,
20
+ renderWorkDocForWrite,
21
+ stampQueueIds,
22
+ zeroIsUnparsed,
23
+ substantiveLines,
20
24
  queueItemsOf,
21
25
  doneEntriesOf,
22
26
  boardRowsOf,
package/dist/build.js DELETED
@@ -1,113 +0,0 @@
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
package/dist/build.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
@@ -1,158 +0,0 @@
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
- /**
43
- * Each probe calls a behaviour that did not exist before its release and
44
- * reports what it observed. A probe that could pass without the code being
45
- * loaded is not a probe — it is a restatement of the version.
46
- */
47
- const PROBES = [
48
- {
49
- id: "typed-records-obligatory",
50
- since: "0.26.10",
51
- run: () => {
52
- const s = suggestRecordType("DONE: shipped it", []);
53
- const mode = typedRecordMode();
54
- return {
55
- present: s.type === "done" && (mode === "warn" || mode === "refuse"),
56
- evidence: `suggestRecordType("DONE: …") -> '${s.type}', policy mode '${mode}'`,
57
- };
58
- },
59
- },
60
- {
61
- id: "record-events-all-kinds",
62
- since: "0.26.12",
63
- run: () => {
64
- const kinds = [...EVENT_KIND_IDS].sort();
65
- const present = ["item", "phase", "pr", "task"].every((k) => kinds.includes(k));
66
- return { present, evidence: `subscribable kinds: ${kinds.join(", ")}` };
67
- },
68
- },
69
- {
70
- id: "multi-pr-citation",
71
- since: "0.26.14",
72
- run: () => {
73
- const refs = prRefsIn("owner/repo#170, #173");
74
- return {
75
- present: refs.length === 2 && refs[1] === "owner/repo#173",
76
- evidence: `prRefsIn("owner/repo#170, #173") -> [${refs.join(", ")}]`,
77
- };
78
- },
79
- },
80
- {
81
- id: "away-means-david-away",
82
- since: "0.26.13",
83
- run: () => {
84
- const present = "merge" in LEAD_REFUSED && PARKED_CATEGORIES.includes("licence");
85
- return {
86
- present,
87
- evidence: `coord_away refuses [${Object.keys(LEAD_REFUSED).join(", ")}] for the lead; parks ${PARKED_CATEGORIES.length} categories`,
88
- };
89
- },
90
- },
91
- {
92
- id: "subscription-scanned-vs-evaluated",
93
- since: "0.26.15",
94
- run: () => {
95
- // The capability is that "the machinery ran" and "your kind fired" are
96
- // separable. Probed by asking for the state that used to be reported as
97
- // broken: scanned, never evaluated — a healthy idle watch.
98
- const base = { id: "p", agentId: "p", kind: "item", target: "t", createdAt: 0, lastEvaluatedAt: null, lastEventAt: null, delivered: [] };
99
- const quiet = subscriptionHealth({ ...base, lastScannedAt: Date.now() });
100
- const unscanned = subscriptionHealth({ ...base, lastScannedAt: null });
101
- return {
102
- present: quiet.level === "ok" && unscanned.level === "error",
103
- evidence: `scanned+quiet -> '${quiet.level}', never-scanned -> '${unscanned.level}'`,
104
- };
105
- },
106
- },
107
- {
108
- id: "record-authority",
109
- since: "0.24.0",
110
- run: () => {
111
- const worker = recordAuthorityFor({ roleId: "worker" });
112
- return {
113
- present: worker.mayNotEmit.includes("verdict"),
114
- evidence: `a worker mayNotEmit: [${worker.mayNotEmit.join(", ")}]`,
115
- };
116
- },
117
- },
118
- ];
119
- export function probeCapabilities(context) {
120
- const probes = PROBES.map((p) => {
121
- try {
122
- const r = p.run();
123
- return { id: p.id, since: p.since, present: r.present, evidence: r.evidence };
124
- }
125
- catch (e) {
126
- // A THROWN PROBE IS AN ABSENT CAPABILITY, NOT A BROKEN CHECK. Older code
127
- // that lacks the symbol throws exactly here, and reporting that as an
128
- // error rather than an absence would make the common case look like a
129
- // malfunction.
130
- return { id: p.id, since: p.since, present: false, evidence: `probe threw: ${e.message}` };
131
- }
132
- });
133
- const missing = probes.filter((p) => !p.present).map((p) => p.id);
134
- return {
135
- answeredBy: {
136
- pid: process.pid,
137
- startedAtIso: new Date(Date.now() - Math.round(process.uptime() * 1000)).toISOString(),
138
- module: context.module,
139
- versionLabel: context.versionLabel,
140
- },
141
- probes,
142
- missing,
143
- ok: missing.length === 0,
144
- note: "Every line above was produced by CALLING the code in this process. `versionLabel` is a label someone typed " +
145
- "into package.json and is context, never evidence — a published tarball has already been observed carrying a " +
146
- "change its version said it could not. THIS ANSWER IS ABOUT ONE PROCESS: a release is delivered when every " +
147
- "live agent's OWN server answers, which is the fifth state (merged · published · installed · restarted · observed). " +
148
- "A server that has not restarted answers honestly about the old code it is still running.",
149
- };
150
- }
151
- /* ── the verb ──────────────────────────────────────────────────────────────── */
152
- import { resolveServerIdentity } from "./server-identity.js";
153
- export const capabilitiesSchema = {};
154
- export async function capabilitiesTool() {
155
- const id = resolveServerIdentity();
156
- return probeCapabilities({ module: id.path, versionLabel: id.version });
157
- }
158
- //# sourceMappingURL=capabilities.js.map
@@ -1 +0,0 @@
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;AAavD;;;;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;AAgBF,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,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,EAAE,GAAG,qBAAqB,EAAE,CAAC;IACnC,OAAO,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;AAC1E,CAAC"}
package/dist/prefix.js DELETED
@@ -1,64 +0,0 @@
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
@@ -1 +0,0 @@
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"}