@thehammer/danx-dashboard-mcp 0.1.197 → 0.1.199

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);
@@ -59,10 +59,17 @@ export function transcriptPathFromEnv(env) {
59
59
  const realWatcher = (options) => new SessionLogWatcher(options);
60
60
  /** The collector's dispatch-id argument; the heartbeat is addressed by session, not dispatch. */
61
61
  const OPERATOR_DISPATCH_REF = "operator-session";
62
+ /**
63
+ * DX-3962 - the bridge's collector. `"operator-bridge"` is the ONE mode that credits a sub-agent's replies to the card
64
+ * it claimed (from its own transcript's pickup); a worker builds its collector with attribution off.
65
+ */
66
+ function newCollector() {
67
+ return new DispatchUsage("operator-bridge");
68
+ }
62
69
  export class OperatorSessionStats {
63
70
  transcriptPath;
64
71
  makeWatcher;
65
- usage = new DispatchUsage();
72
+ usage = newCollector();
66
73
  watcher = null;
67
74
  stopped = false;
68
75
  constructor(transcriptPath, makeWatcher = realWatcher) {
@@ -153,7 +160,7 @@ export class OperatorSessionStats {
153
160
  return;
154
161
  watcher.stop();
155
162
  this.watcher = null;
156
- this.usage = new DispatchUsage();
163
+ this.usage = newCollector();
157
164
  })
158
165
  .catch((err) => {
159
166
  process.stderr.write(`operator session stats: transcript watcher failed: ${err.message}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.197",
3
+ "version": "0.1.199",
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.19",
34
+ "@thehammer/danx-session-telemetry": "0.1.22",
35
35
  "zod": "^3.25.76"
36
36
  },
37
37
  "devDependencies": {