@thehammer/danx-dashboard-mcp 0.1.254 → 0.1.258
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/listen.js +57 -39
- package/dist/one-line.js +6 -0
- package/dist/pacing-relay.js +16 -0
- package/package.json +1 -1
package/dist/listen.js
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
* `text` is what the session reads; an event it cannot read still produces one
|
|
13
13
|
* (`could not read event …`), never silence. `id` is `null` only for a frame
|
|
14
14
|
* that carried no usable id — which includes DX-2885's `nudge` frame
|
|
15
|
-
* (`formatNudgeLine`)
|
|
16
|
-
* dashboard's own keep-alive tick,
|
|
17
|
-
* same shape with `id: null` rather
|
|
15
|
+
* (`formatNudgeLine`) and DX-4340's `pacing` frame (`formatPacingLine`):
|
|
16
|
+
* both are advisory, written fresh by the dashboard's own keep-alive tick,
|
|
17
|
+
* and never replayed, so they ride this same shape with `id: null` rather
|
|
18
|
+
* than growing a THIRD output variant.
|
|
18
19
|
* - `{type:"stopped", reason, detail, refusal?}` — once, last. `reason`
|
|
19
20
|
* is the dashboard's own end reason (`superseded`, `replaced`, `revoked`,
|
|
20
21
|
* `not_connected`), `refused` (the ticket was not admitted), or
|
|
@@ -48,7 +49,8 @@
|
|
|
48
49
|
* danxbot source.
|
|
49
50
|
*/
|
|
50
51
|
import { errorCodeOf } from "./json-body.js";
|
|
51
|
-
import { oneLine } from "./one-line.js";
|
|
52
|
+
import { LINE_PREFIX, oneLine } from "./one-line.js";
|
|
53
|
+
import { pacingRelayLine } from "./pacing-relay.js";
|
|
52
54
|
export const INITIAL_BACKOFF_MS = 1_000;
|
|
53
55
|
export const MAX_BACKOFF_MS = 30_000;
|
|
54
56
|
/** Three of the dashboard's 15 s keep-alives without a byte means the connection is dead. */
|
|
@@ -67,7 +69,6 @@ export const DELIVERED_ID_MEMORY = 1_000;
|
|
|
67
69
|
export function capResumeIds(ids) {
|
|
68
70
|
return ids.slice(-DELIVERED_ID_MEMORY);
|
|
69
71
|
}
|
|
70
|
-
const LINE_PREFIX = "[danx-dashboard stream]";
|
|
71
72
|
const ACTIVITY_ORIGINS = new Set(["operator", "agent", "machine"]);
|
|
72
73
|
function quoted(value) {
|
|
73
74
|
return `"${value.text}${value.truncated ? "…" : ""}"`;
|
|
@@ -329,6 +330,28 @@ export function formatNudgeLine(event) {
|
|
|
329
330
|
"Work these cards; any you cannot work, update to its true state: open a problem, add depends_on or " +
|
|
330
331
|
"conflict_on, block it with the real reason, or rollback_pickup.");
|
|
331
332
|
}
|
|
333
|
+
const PACING_LEVELS = ["on_pace", "over_pace", "critical"];
|
|
334
|
+
/** Why a pacing message cannot be read, or `null` when it can. */
|
|
335
|
+
export function invalidPacingReason(value) {
|
|
336
|
+
if (!isRecord(value))
|
|
337
|
+
return "the pacing message is not an object";
|
|
338
|
+
if (!PACING_LEVELS.includes(value.level))
|
|
339
|
+
return "level is not on_pace, over_pace or critical";
|
|
340
|
+
if (value.budget !== null && !(typeof value.budget === "number" && Number.isInteger(value.budget) && value.budget >= 0))
|
|
341
|
+
return "budget is not a count or null";
|
|
342
|
+
if (typeof value.resets_at !== "string" || Number.isNaN(Date.parse(value.resets_at)))
|
|
343
|
+
return "resets_at is not a timestamp";
|
|
344
|
+
if (typeof value.text !== "string" || value.text.trim() === "")
|
|
345
|
+
return "text is empty";
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
/** The one readable line for a pacing message: the dashboard's text, on one line. Throws, naming the fault, on a malformed message. */
|
|
349
|
+
export function formatPacingLine(event) {
|
|
350
|
+
const invalid = invalidPacingReason(event);
|
|
351
|
+
if (invalid !== null)
|
|
352
|
+
throw new Error(`malformed pacing message: ${invalid}`);
|
|
353
|
+
return pacingRelayLine(event.text);
|
|
354
|
+
}
|
|
332
355
|
/**
|
|
333
356
|
* Incremental SSE parser. Comment lines (keep-alives, the connect marker) and
|
|
334
357
|
* field-less blocks produce no message, which is what keeps them out of the output.
|
|
@@ -416,6 +439,30 @@ export async function runListener(options, deps) {
|
|
|
416
439
|
deps.write({ type: "stopped", reason, detail, refusal: extra?.refusal });
|
|
417
440
|
return code;
|
|
418
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* The shared path of an ADVISORY frame (DX-2885 `nudge`, DX-4340 `pacing`): it carries no usable id (re-derived or written fresh by the
|
|
444
|
+
* dashboard's keep-alive tick, never replayed - see `plan-session-stream.ts`'s module doc), so it rides the SAME `{type:"event", id:null, text}`
|
|
445
|
+
* shape an ordinary activity line uses. That is what lets `bridge.ts` and the plugin's relay (`danxbot/scripts/plan-event-bridge.mjs`, outside
|
|
446
|
+
* this repo) forward it with NO changes: both forward any `{type:"event", id, text}` line keyed only on `type`.
|
|
447
|
+
*
|
|
448
|
+
* `origin: null` is DX-2730 D4's exemption: the frame is delivered at once, never held for a digest flush (a nudge exists to interrupt a quiet
|
|
449
|
+
* session; a pacing change is an instruction to act now). A frame that is not JSON or not the right shape is reported from an explicit check
|
|
450
|
+
* naming the fault, and the next frame still emits.
|
|
451
|
+
*/
|
|
452
|
+
const emitAdvisory = (message, what, invalidReason, format) => {
|
|
453
|
+
let parsed;
|
|
454
|
+
let invalid;
|
|
455
|
+
try {
|
|
456
|
+
parsed = JSON.parse(message.data);
|
|
457
|
+
invalid = invalidReason(parsed);
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
invalid = `not JSON: ${err.message}`;
|
|
461
|
+
}
|
|
462
|
+
const text = invalid === null ? format(parsed) : `${LINE_PREFIX} could not read ${what} (${invalid}): ${oneLine(message.data, 300)}`;
|
|
463
|
+
deps.write({ type: "event", id: null, text, origin: null });
|
|
464
|
+
return null;
|
|
465
|
+
};
|
|
419
466
|
const handle = (message) => {
|
|
420
467
|
if (message.event === "end") {
|
|
421
468
|
// DX-2920 (round 3) — a JSON.parse failure here falls through to the
|
|
@@ -435,40 +482,11 @@ export async function runListener(options, deps) {
|
|
|
435
482
|
// `Outcome`'s "ended" reason is a closed set (`EndedReason`).
|
|
436
483
|
return { kind: "ended", reason: isKnownWireEndReason(reason) ? reason : "unknown" };
|
|
437
484
|
}
|
|
438
|
-
if (message.event === "nudge")
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
// that carried no usable id — a nudge is exactly that case). That is
|
|
444
|
-
// what lets `bridge.ts` and the plugin's relay
|
|
445
|
-
// (`danxbot/scripts/plan-event-bridge.mjs`, outside this repo) forward
|
|
446
|
-
// it with NO changes at all: both already forward any `{type:"event",
|
|
447
|
-
// id, text}` line generically, keyed only on `type`.
|
|
448
|
-
//
|
|
449
|
-
// DX-2885 — this frame must stay exempt from DX-2730 D4's digest-
|
|
450
|
-
// batching when that lands (see DX-2730 AC 29939, and the matching note
|
|
451
|
-
// at the dashboard's own emit site): it must keep delivering
|
|
452
|
-
// immediately, never held for a later digest flush.
|
|
453
|
-
let parsed;
|
|
454
|
-
let invalid;
|
|
455
|
-
try {
|
|
456
|
-
parsed = JSON.parse(message.data);
|
|
457
|
-
invalid = invalidNudgeReason(parsed);
|
|
458
|
-
}
|
|
459
|
-
catch (err) {
|
|
460
|
-
invalid = `not JSON: ${err.message}`;
|
|
461
|
-
}
|
|
462
|
-
const text = invalid === null
|
|
463
|
-
? formatNudgeLine(parsed)
|
|
464
|
-
: `${LINE_PREFIX} could not read nudge (${invalid}): ${oneLine(message.data, 300)}`;
|
|
465
|
-
// DX-2730 D4 — `origin: null` here, not the parsed event's own origin
|
|
466
|
-
// (a nudge has none): this is the exemption the module doc above
|
|
467
|
-
// describes, letting `bridge.ts` treat it exactly like an operator
|
|
468
|
-
// event (immediate) without needing a fourth vocabulary member.
|
|
469
|
-
deps.write({ type: "event", id: null, text, origin: null });
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
485
|
+
if (message.event === "nudge")
|
|
486
|
+
return emitAdvisory(message, "nudge", invalidNudgeReason, (p) => formatNudgeLine(p));
|
|
487
|
+
// DX-4340 - the pacing tick's one message for this session's Claude account (level or budget changed): advisory like a nudge.
|
|
488
|
+
if (message.event === "pacing")
|
|
489
|
+
return emitAdvisory(message, "pacing message", invalidPacingReason, (p) => formatPacingLine(p));
|
|
472
490
|
if (message.event !== "activity")
|
|
473
491
|
return null;
|
|
474
492
|
// `Number(null)` is 0, so a frame with no id must never be read as event 0: that id
|
package/dist/one-line.js
CHANGED
|
@@ -17,3 +17,9 @@ export function oneLine(text, maxCodePoints) {
|
|
|
17
17
|
const points = Array.from(collapsed);
|
|
18
18
|
return points.length > maxCodePoints ? `${points.slice(0, maxCodePoints).join("")}…` : collapsed;
|
|
19
19
|
}
|
|
20
|
+
/** The prefix of every line the stream listener emits for the session to read. */
|
|
21
|
+
export const LINE_PREFIX = "[danx-dashboard stream]";
|
|
22
|
+
/** `text` as one prefixed listener line, optionally cut (the one template of a listener line, shared with whoever must know its length). */
|
|
23
|
+
export function prefixedLine(text, maxCodePoints) {
|
|
24
|
+
return oneLine(`${LINE_PREFIX} ${text}`, maxCodePoints);
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { prefixedLine } from "./one-line.js";
|
|
2
|
+
/**
|
|
3
|
+
* DX-4341 - the longest pacing line the stream listener relays whole. `formatPacingLine` (`listen.ts`) cuts a pacing line, PREFIX INCLUDED and
|
|
4
|
+
* after whitespace collapses, to this many code points and ends it with an ellipsis, which would drop the tail of a message first (an
|
|
5
|
+
* orchestrator's duties, the end of the pause note format). The dashboard renders the message and refuses one that would be cut
|
|
6
|
+
* (`pacingRelayLength`, used by `src/team-pacing/pacing-message.ts`), so the cut and the refusal measure the very same line.
|
|
7
|
+
*/
|
|
8
|
+
export const PACING_RELAY_MAX_CODE_POINTS = 1000;
|
|
9
|
+
/** The line the listener emits for pacing text `text`: prefixed, on one line, cut at the relay maximum. */
|
|
10
|
+
export function pacingRelayLine(text) {
|
|
11
|
+
return prefixedLine(text, PACING_RELAY_MAX_CODE_POINTS);
|
|
12
|
+
}
|
|
13
|
+
/** How many code points the listener's line for `text` has before the cut. */
|
|
14
|
+
export function pacingRelayLength(text) {
|
|
15
|
+
return Array.from(prefixedLine(text)).length;
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/danx-dashboard-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.258",
|
|
4
4
|
"description": "Stdio MCP server fronting danxbot's dashboard API for agents (operator sessions and dispatched workers alike): the generic danxbot_api/danxbot_api_list/danxbot_api_spec tools reach any route the caller's credential may call, plus dedicated agent-specific tools (plan_connect, danxbot_complete, and other per-dispatch lifecycle callbacks) (DX-3465, PLN-11 R-25).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|