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.
- package/dist/auth-faults.js +145 -0
- package/dist/auth-faults.js.map +1 -0
- package/dist/capabilities.js +98 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/launch-unit.js +282 -0
- package/dist/launch-unit.js.map +1 -0
- package/dist/server.js +46 -7
- package/dist/server.js.map +1 -1
- package/dist/store.js +214 -27
- package/dist/store.js.map +1 -1
- package/dist/test-count-key.js +22 -0
- package/dist/test-count-key.js.map +1 -0
- package/dist/tools/admin.js +28 -31
- package/dist/tools/admin.js.map +1 -1
- package/dist/tools/events.js +77 -3
- package/dist/tools/events.js.map +1 -1
- package/dist/tools/herdr-delivery.js +51 -36
- package/dist/tools/herdr-delivery.js.map +1 -1
- package/dist/tools/herdr-tail.js +115 -57
- package/dist/tools/herdr-tail.js.map +1 -1
- package/dist/tools/jsonl-offsets.js +32 -0
- package/dist/tools/jsonl-offsets.js.map +1 -1
- package/dist/tools/messaging.js +2 -2
- package/dist/tools/messaging.js.map +1 -1
- package/dist/tools/registry.js +50 -3
- package/dist/tools/registry.js.map +1 -1
- package/dist/transports/herdr.js +92 -2
- package/dist/transports/herdr.js.map +1 -1
- package/package.json +1 -1
- package/scripts/check-test-count.mjs +80 -6
- package/scripts/coord-attention-clock.mjs +19 -21
- package/scripts/coord-stall-clock.mjs +23 -24
- package/scripts/lib/clock-unit-cli.mjs +134 -0
- package/src/auth-faults.ts +166 -0
- package/src/capabilities.ts +100 -1
- package/src/launch-unit.ts +350 -0
- package/src/server.ts +47 -6
- package/src/store.ts +232 -25
- package/src/test-count-key.ts +21 -0
- package/src/tools/admin.ts +46 -33
- package/src/tools/events.ts +89 -4
- package/src/tools/herdr-delivery.ts +46 -31
- package/src/tools/herdr-tail.ts +108 -52
- package/src/tools/jsonl-offsets.ts +23 -0
- package/src/tools/messaging.ts +2 -2
- package/src/tools/registry.ts +55 -3
- package/src/transports/herdr.ts +93 -2
package/src/transports/herdr.ts
CHANGED
|
@@ -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))
|
|
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
|
-
|
|
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
|
/**
|