@thehammer/danx-dashboard-mcp 0.1.290 → 0.1.291

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.
@@ -87,6 +87,7 @@ export class OperatorSessionStats {
87
87
  }
88
88
  let carried = false;
89
89
  let landed = true;
90
+ let planless = false;
90
91
  await this.usage.flush({
91
92
  applyStats: async (_dispatchId, payload) => {
92
93
  carried = true;
@@ -95,12 +96,24 @@ export class OperatorSessionStats {
95
96
  landed = false;
96
97
  throw new Error("stats heartbeat did not reach the dashboard");
97
98
  }
99
+ if (answer.statsNotStored === "no_plan") {
100
+ planless = true;
101
+ throw new Error("stats heartbeat refused: the session is on no plan, so the dashboard stores no stats for it");
102
+ }
98
103
  if (answer.stats === null) {
99
104
  throw new Error(`stats heartbeat answered HTTP ${answer.status} without stats`);
100
105
  }
101
106
  return answer.stats;
102
107
  },
103
108
  }, OPERATOR_DISPATCH_REF);
109
+ if (planless) {
110
+ // DX-4892: re-queueing would resend this batch on every tick for as long as the session stays planless. Drop the collector instead:
111
+ // the plain heartbeats that follow start collection again from the stored cursors once an answer carries stats (the session is bound).
112
+ this.watcher?.stop();
113
+ this.watcher = null;
114
+ this.usage = newCollector();
115
+ return landed;
116
+ }
104
117
  if (carried)
105
118
  return landed;
106
119
  return (await send(undefined)).landed;
@@ -296,7 +296,7 @@ title) {
296
296
  // request never landed at all, which is exactly the signal the silent-
297
297
  // failure counter in the relay below needs. Still never throws.
298
298
  if (outcome.kind !== "answer")
299
- return { landed: false, status: null, stats: null, revocation: null, title: null };
299
+ return { landed: false, status: null, stats: null, statsNotStored: null, revocation: null, title: null };
300
300
  // DX-2954 — the answer's `stats` (the operator dispatch's stored or
301
301
  // just-written totals and cursors) feed the session's stats collector.
302
302
  const ok = outcome.status >= 200 && outcome.status < 300;
@@ -321,6 +321,8 @@ title) {
321
321
  landed: true,
322
322
  status: outcome.status,
323
323
  stats: answered,
324
+ // DX-4892: the dashboard says why carried stats were not stored (a session on no plan), instead of the relay inferring it from a missing `stats`.
325
+ statsNotStored: ok && isRecord(outcome.body) && isRecord(outcome.body.statsError) && outcome.body.statsError.kind === "no_plan" ? "no_plan" : null,
324
326
  revocation: keyRevocationOf({ ok: false, status: outcome.status, body: outcome.body }),
325
327
  title: storedTitle,
326
328
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.290",
3
+ "version": "0.1.291",
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, request_permission, attach_file, and danxbot_slack_reply) (DX-3465, PLN-11 R-25).",
5
5
  "license": "MIT",
6
6
  "type": "module",