@thehammer/danx-dashboard-mcp 0.1.198 → 0.1.200

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/bridge.js CHANGED
@@ -73,6 +73,7 @@ import { oneLine } from "./one-line.js";
73
73
  import { credentialFingerprint, describeCredentialSource, readCredential, } from "./credential.js";
74
74
  import { readSessionConnection, sessionConnectionPath } from "./session-connection.js";
75
75
  import { capResumeIds, DELIVERED_ID_MEMORY, HEALTHY_CONNECTION_MS, READ_IDLE_TIMEOUT_MS, runListener, } from "./listen.js";
76
+ import { isClaimState } from "@thehammer/danx-session-telemetry";
76
77
  import { OperatorSessionStats, transcriptPathFromEnv, } from "./session-stats.js";
77
78
  /**
78
79
  * DX-2730 D4 — WHICH EVENTS WAKE THE SESSION, NOW THAT EVERY ONE OF THEM CAN.
@@ -600,7 +601,13 @@ async function sendHeartbeat(options, deps, connection, stats) {
600
601
  // rather than treat them as the answer to its write.
601
602
  const applied = ok && isRecord(outcome.body) && outcome.body.statsError === undefined;
602
603
  const statsBody = applied && isRecord(outcome.body) && isRecord(outcome.body.stats) ? outcome.body.stats : null;
603
- const answered = statsBody !== null && isRecord(statsBody.sourceCursors) && isRecord(statsBody.totals)
604
+ // DX-3962: an answer from a server that predates attribution stores cursors with no `claims`. Treated exactly like
605
+ // an answer with no stats (the older-server path): the collector never starts, the heartbeat still lands, and
606
+ // nothing is recorded until the server is deployed. Handing it on would crash the collector at attach.
607
+ const answered = statsBody !== null &&
608
+ isRecord(statsBody.sourceCursors) &&
609
+ isRecord(statsBody.totals) &&
610
+ Object.values(statsBody.sourceCursors).every((cursor) => isRecord(cursor) && isClaimState(cursor.claims))
604
611
  ? statsBody
605
612
  : null;
606
613
  return { landed: true, status: outcome.status, stats: answered };
@@ -691,6 +698,10 @@ export async function runBridge(options, deps) {
691
698
  // PROCESS reconnecting, never an external watchdog and never a second
692
699
  // process racing the first.
693
700
  forceReconnect?.();
701
+ }).catch((err) => {
702
+ // DX-3962: a stats failure must never take the bridge down. `heartbeatTick` is fired without being awaited, so
703
+ // a throw here would be an unhandled rejection that kills the process and with it the session's plan events.
704
+ process.stderr.write(`operator session stats: heartbeat tick failed: ${err instanceof Error ? err.message : String(err)}\n`);
694
705
  });
695
706
  };
696
707
  const heartbeat = deps.scheduleInterval(heartbeatTick, HEARTBEAT_INTERVAL_MS);
@@ -12,6 +12,10 @@
12
12
  * a 4xx on three writes: a card create without `plan`, an edit whose `ac[]`
13
13
  * items were keyed `text` (and one with no `content_hash`), and a retro whose
14
14
  * flag was false but whose description was missing, then prose.
15
+ * DX-3917 / DX-4015 — the phase D run found 9 work sessions writing the
16
+ * retro before the `complete` transition (the list went AC tick -> retro,
17
+ * with no transition between) and 14 of 24 writing the transition with no
18
+ * spec call: the transition is now a shown write, placed before the retro.
15
19
  *
16
20
  * Kept as data, not prose, so danxbot's own tests can check each example
17
21
  * against the live server contract (route table, resource fields, body
@@ -49,6 +53,11 @@ export const DANXBOT_API_EXAMPLES = {
49
53
  path: "/api/issues/<id>/checklists/<checklists[].id>/items/<items[].id>",
50
54
  body: { status: "passing" },
51
55
  },
56
+ transition: {
57
+ method: "POST",
58
+ path: "/api/issues/<id>/transition",
59
+ body: { action: "<action>", summary: "<text>" },
60
+ },
52
61
  retro: {
53
62
  method: "PUT",
54
63
  path: "/api/issues/<id>/retro",
@@ -78,6 +87,7 @@ export function danxbotApiExamplesText() {
78
87
  `card edit ${render(e.cardEdit)} (only the keys you change; \`ac\` replaces the list); ` +
79
88
  `comment ${render(e.comment)}; ` +
80
89
  `AC tick ${render(e.acTick)}; ` +
81
- `retro ${render(e.retro)} (description "" unless the flag is true; ` +
90
+ `transition ${render(e.transition)}; ` +
91
+ `retro, only after \`complete\` or \`cancel\`, ${render(e.retro)} (description "" unless the flag is true; ` +
82
92
  `\`tests[].kind\` ${RETRO_TEST_KIND_EXAMPLES.map((k) => `"${k}"`).join(" or ")}).`);
83
93
  }
package/dist/index.js CHANGED
@@ -340,10 +340,11 @@ const boardField = {
340
340
  // ---------------- danxbot_api (DX-3465, PLN-11 R-25) ----------------
341
341
  strictTool("danxbot_api", "Call any dashboard API route your credential may reach. `method` + `path` (a FULL path, e.g. " +
342
342
  '"/api/issues/DX-1/transition" — substitute real ids yourself, never a route template with `:id` in it) + ' +
343
+ // DX-3997 — `reminders[]` also relays the dashboard's own listener reminder
344
+ // to a plan-connected session; the reminder's text explains itself.
343
345
  "optional `query`/`body`. The response is the route's JSON body verbatim, `reminders[]` included when the " +
344
- "route attaches one — a plan-connected session whose event listener the dashboard reports unattached also gets " +
345
- "the dashboard's own `plan_list.listener_unattached` reminder relayed there (DX-3997). `board` overrides this dispatch's configured board for a board-scoped route only " +
346
- "(ignored by a route that isn't board-scoped). See this server's own instructions (shown once, at connect) " +
346
+ "route attaches one. `board` overrides this dispatch's configured board for a board-scoped route only. " +
347
+ "See this server's own instructions (shown once, at connect) " +
347
348
  "for the shared resource shape, the stale-hash rule, and how to read a refusal — don't know the route? call " +
348
349
  "`danxbot_api_list` first. " +
349
350
  // DX-4015 — the ONE statement of which writes need a spec call first; the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.198",
3
+ "version": "0.1.200",
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",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@modelcontextprotocol/sdk": "1.29.0",
34
- "@thehammer/danx-session-telemetry": "0.1.21",
34
+ "@thehammer/danx-session-telemetry": "0.1.22",
35
35
  "zod": "^3.25.76"
36
36
  },
37
37
  "devDependencies": {