agent-coord-mcp 0.26.34 → 0.26.39

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 (47) hide show
  1. package/dist/auth-faults.js +145 -0
  2. package/dist/auth-faults.js.map +1 -0
  3. package/dist/capabilities.js +98 -1
  4. package/dist/capabilities.js.map +1 -1
  5. package/dist/launch-unit.js +282 -0
  6. package/dist/launch-unit.js.map +1 -0
  7. package/dist/server.js +46 -7
  8. package/dist/server.js.map +1 -1
  9. package/dist/store.js +214 -27
  10. package/dist/store.js.map +1 -1
  11. package/dist/test-count-key.js +22 -0
  12. package/dist/test-count-key.js.map +1 -0
  13. package/dist/tools/admin.js +28 -31
  14. package/dist/tools/admin.js.map +1 -1
  15. package/dist/tools/events.js +77 -3
  16. package/dist/tools/events.js.map +1 -1
  17. package/dist/tools/herdr-delivery.js +51 -36
  18. package/dist/tools/herdr-delivery.js.map +1 -1
  19. package/dist/tools/herdr-tail.js +115 -57
  20. package/dist/tools/herdr-tail.js.map +1 -1
  21. package/dist/tools/jsonl-offsets.js +32 -0
  22. package/dist/tools/jsonl-offsets.js.map +1 -1
  23. package/dist/tools/messaging.js +2 -2
  24. package/dist/tools/messaging.js.map +1 -1
  25. package/dist/tools/registry.js +50 -3
  26. package/dist/tools/registry.js.map +1 -1
  27. package/dist/transports/herdr.js +92 -2
  28. package/dist/transports/herdr.js.map +1 -1
  29. package/package.json +1 -1
  30. package/scripts/check-test-count.mjs +80 -6
  31. package/scripts/coord-attention-clock.mjs +19 -21
  32. package/scripts/coord-stall-clock.mjs +23 -24
  33. package/scripts/lib/clock-unit-cli.mjs +134 -0
  34. package/src/auth-faults.ts +166 -0
  35. package/src/capabilities.ts +100 -1
  36. package/src/launch-unit.ts +350 -0
  37. package/src/server.ts +47 -6
  38. package/src/store.ts +232 -25
  39. package/src/test-count-key.ts +21 -0
  40. package/src/tools/admin.ts +46 -33
  41. package/src/tools/events.ts +89 -4
  42. package/src/tools/herdr-delivery.ts +46 -31
  43. package/src/tools/herdr-tail.ts +108 -52
  44. package/src/tools/jsonl-offsets.ts +23 -0
  45. package/src/tools/messaging.ts +2 -2
  46. package/src/tools/registry.ts +55 -3
  47. package/src/transports/herdr.ts +93 -2
@@ -246,6 +246,20 @@ export class HerdrTransport implements Transport {
246
246
  #markerPid: () => HerdrMarkerPid;
247
247
  #env: Record<string, string | undefined>;
248
248
  #submitMode: SubmitMode;
249
+ /**
250
+ * ⛔⛆ PROVENANCE FOR THE ENTER WE PRESS ON A DRAFT WE DID NOT JUST TYPE: target → the exact text this
251
+ * transport left sitting un-submitted in that pane. A `pending` result means the payload is IN the box
252
+ * and our enters did not submit it; the recovery is to press Enter again, NEVER to retype.
253
+ * ⚠ Why the box alone cannot decide this: a pasted payload renders as `[Pasted text #N +M lines]`, and
254
+ * `isPasteChip` is CONTENT-BLIND (MEASURED 2026-09-27, 4 cases over the 4 draft classes readReadyBox
255
+ * distinguishes — our verbatim payload, a foreign plain draft, a foreign chip, an empty box: the foreign
256
+ * chip read as OURS). Our own paste is a chip too, so chip-vs-chip is indistinguishable ON SCREEN and a
257
+ * content-only rule would refuse the very case this fixes. So ownership is recorded HERE, by the code
258
+ * that typed it, and the screen read stays exactly as strict as it was.
259
+ * ⭐ In-memory by design: after a restart we claim nothing and fall through to the refusal, which is the
260
+ * conservative side and is what today's build already does.
261
+ */
262
+ #leftUnsubmitted = new Map<string, string>();
249
263
 
250
264
  constructor(opts: HerdrTransportOptions = {}) {
251
265
  this.#run = opts.run ?? defaultHerdrRunner;
@@ -329,11 +343,61 @@ export class HerdrTransport implements Transport {
329
343
  return herdrPaneExists(target, this.#run);
330
344
  }
331
345
 
346
+ /**
347
+ * ⟨q-f995c3c7⟩ next layer — THE CANONICAL ID BEHIND A PANE SPELLING. `null` = could not ask.
348
+ *
349
+ * ⛔ WHY THIS EXISTS: one pane has more than one name. This seat's env said `wA5:p1` while its
350
+ * marker said `wA3:p4`, both resolving to the same pane, and the first-claim guard compared the two
351
+ * STRINGS with `===`. So a seat restarting into its own pane was refused by its own marker — the
352
+ * defect ⟨q-f995c3c7⟩ fixed for the ENV-vs-TMUX_PANE case and left standing for spelling-vs-spelling.
353
+ * Measured 2026-09-26: six seats had to be hand-staged to the env spelling before a restart, and the
354
+ * fleet-wide standdown manifest now carries an `attachWith` field to work around it.
355
+ *
356
+ * ⭐ HERDR IS ALREADY THE RESOLVER — `pane get <spelling>` answers with the canonical `pane_id` for
357
+ * either name, and `attach()` above has read `paneOf(cur)?.pane_id` since before this row existed.
358
+ * Nothing new is being invented here; the guard simply stopped short of asking.
359
+ */
360
+ resolvePaneId(target: string): string | null {
361
+ const got = this.#run(["pane", "get", target]);
362
+ if (!got.ok) return null;
363
+ return paneOf(got)?.pane_id ?? null;
364
+ }
365
+
332
366
  /** Nothing to kill: there is no pusher. The tool deletes the marker. */
333
367
  async detach(_agentId: string): Promise<void> {
334
368
  return;
335
369
  }
336
370
 
371
+ /**
372
+ * Press Enter on a payload ALREADY sitting in the ready box, and verify by reading back. Returns a
373
+ * result when the outcome is settled, or `null` to fall through to the normal type-then-submit path.
374
+ *
375
+ * ⭐ Used only when `boxHoldsPayload` says the draft is OURS. No `send-text` happens here — the text is
376
+ * already there, and typing it again would join it to itself. This is the recovery for a `pending` left
377
+ * by an earlier attempt, and it is what turns that state from "a human must nudge it" into "the next
378
+ * tick submits it".
379
+ */
380
+ #pressEnterUntilSubmitted(target: string, text: string, before: ReturnType<typeof readReadyBox>, key: string): HerdrPushResult | null {
381
+ let enters = 0;
382
+ for (let attempt = 0; attempt < 2; attempt++) {
383
+ const pressed = this.#run(["pane", "send-keys", target, key]);
384
+ if (!pressed.ok) return { delivered: false, verified: false, outcome: "typed-unconfirmed", safeToRetry: false, enters, error: `send-keys enter to ${target} refused while submitting a payload already in the box: ${pressed.error?.message ?? pressed.stderr}` };
385
+ enters++;
386
+ this.#sleep(this.#settleMs);
387
+ const box = readReadyBox(this.#screen(target));
388
+ if (transcriptGained(before, box, text)) {
389
+ this.#leftUnsubmitted.delete(target);
390
+ return { delivered: true, verified: true, outcome: "delivered", safeToRetry: false, enters, busy: box.ready ? box.busy ?? undefined : undefined };
391
+ }
392
+ if (boxHoldsPayload(box, text)) continue;
393
+ // The box no longer holds our text and the transcript did not gain it: something else consumed it.
394
+ // Not ours to retype — report it rather than guess.
395
+ this.#leftUnsubmitted.delete(target);
396
+ return { delivered: false, verified: false, outcome: "unverified", safeToRetry: false, enters, error: `pressed enter on a payload already in ${target}'s box, but the screen after it does not show the message submitted (${box.ready ? (box.draft ? "the box now holds other text" : "the box is empty and the transcript gained no line carrying it") : box.reason})` };
397
+ }
398
+ return { delivered: false, verified: true, outcome: "pending", safeToRetry: true, enters, error: `payload was already sitting in ${target}'s input and ${enters} further enters did not submit it — the composer HOLDS this message: a human Enter in ${target} submits it, or clearing that box discards it` };
399
+ }
400
+
337
401
  /**
338
402
  * Type `text` into the pane and press enter; VERIFY by reading the pane back, and if
339
403
  * the line is still sitting unsubmitted (the spike's race), press enter once more.
@@ -363,6 +427,22 @@ export class HerdrTransport implements Transport {
363
427
  // ⟨q-15d763dc⟩ BEFORE ANY KEY: the screen must be Claude Code's ready, empty input box. The text
364
428
  // is held as firmly as the Enter — a digit typed into an open dialog selects an option.
365
429
  const before = readReadyBox(this.#screen(target));
430
+
431
+ // ⛔⛆ OUR OWN UN-SUBMITTED PAYLOAD IS SUBMITTED, NOT REFUSED AND NOT RETYPED. A previous attempt can
432
+ // leave this exact text in the composer: `pasteAndSubmit` types it, presses Enter up to twice, and
433
+ // returns `pending` when the box still holds it. `refusalBeforeKeys` then refuses ANY draft — so the
434
+ // retry refused on text THIS TRANSPORT typed, every tick, forever, and only a human Enter broke it.
435
+ // That is David's nudge on fleet HRx, shipped in 0.26.36 ⟨q-1ce5bc97⟩.
436
+ // ⭐ `boxHoldsPayload` is the discriminator worker-1 identified: OUR payload → press Enter and submit
437
+ // what is already there; a FOREIGN draft → refuse, which is what that refusal was always for. Typing
438
+ // again is never right — it would join the text to itself.
439
+ // ⚠ The refusal is NOT weakened for any other case: an unready screen, a dialog, or someone else's
440
+ // draft still stops before a single key, because `boxHoldsPayload` returns false for all of them.
441
+ if (this.#leftUnsubmitted.get(target) === text && boxHoldsPayload(before, text)) {
442
+ const resubmitted = this.#pressEnterUntilSubmitted(target, text, before, enterKey.key);
443
+ if (resubmitted) return resubmitted;
444
+ }
445
+
366
446
  const refusal = refusalBeforeKeys(before);
367
447
  if (refusal) return { delivered: false, verified: false, outcome: "held", safeToRetry: true, error: `held, nothing typed into ${target}: ${refusal}` };
368
448
 
@@ -381,11 +461,22 @@ export class HerdrTransport implements Transport {
381
461
  enters++;
382
462
  this.#sleep(this.#settleMs);
383
463
  box = readReadyBox(this.#screen(target));
384
- if (transcriptGained(before, box, text)) return { delivered: true, verified: true, outcome: "delivered", safeToRetry: false, enters, busy: box.ready ? box.busy ?? undefined : undefined };
464
+ if (transcriptGained(before, box, text)) {
465
+ this.#leftUnsubmitted.delete(target);
466
+ return { delivered: true, verified: true, outcome: "delivered", safeToRetry: false, enters, busy: box.ready ? box.busy ?? undefined : undefined };
467
+ }
385
468
  if (boxHoldsPayload(box, text)) continue;
469
+ this.#leftUnsubmitted.delete(target);
386
470
  return { delivered: false, verified: false, outcome: "unverified", safeToRetry: false, enters, error: `enter sent to ${target}, but the screen after it does not show the message submitted (${box.ready ? (box.draft ? "the box holds other text" : "the box is empty and the transcript gained no line carrying it") : box.reason})` };
387
471
  }
388
- return { delivered: false, verified: true, outcome: "pending", safeToRetry: true, enters, error: `text still sitting unsubmitted in ${target}'s input after ${enters} enters` };
472
+ // ⚠ `safeToRetry: true` STAYS, and it is not an oversight — ⟨q-1ce5bc97⟩ step 1 chose it because
473
+ // pending is the most recoverable non-delivery: the payload is KNOWN to be in the box, so a transient
474
+ // misread must not become a permanent drop. What was wrong lived in the CONSUMER, which read this flag
475
+ // as "no key was sent" and retried into the draft this attempt created (the 0.26.36 livelock).
476
+ // ⭐ The error now says what a sender actually faces, because `verified: true` here means the OUTCOME
477
+ // is known — we read the screen back and the payload is sitting there — and has never meant delivered.
478
+ this.#leftUnsubmitted.set(target, text);
479
+ return { delivered: false, verified: true, outcome: "pending", safeToRetry: true, enters, error: `text still sitting unsubmitted in ${target}'s input after ${enters} enters — the pane's composer HOLDS this message, so a retype would join it rather than send it: a human Enter in ${target} submits it, or clearing that box discards it` };
389
480
  }
390
481
 
391
482
  /**