@thehammer/danx-dashboard-mcp 0.1.172 → 0.1.173

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `danx-dashboard-mcp background-work <count|clear>` — the ONE-SHOT client of
2
+ * `danx-dashboard-mcp background-work <count|clear> <event-at>` — the ONE-SHOT client of
3
3
  * `PUT /api/plan-sessions/me/background-work` (DX-3367).
4
4
  *
5
5
  * WHO RUNS IT. The danxbot Claude Code plugin's `Stop` / `SubagentStop` /
@@ -29,10 +29,15 @@
29
29
  * no-op — `{"ok":false,"reason":"no_connection_record"}` — the plugin
30
30
  * hook simply does not forward anything further in this case, exactly
31
31
  * what "no-op when the session isn't plan-connected" means in practice;
32
- * - on success, `{"ok":true,"count":<number|null>}` echoes the `count`
33
- * that was SUBMITTED for storage, not a re-read of the row — see
34
- * `recordBackgroundWork`'s own doc (`db/plan-session-background-work.ts`)
35
- * for why a plain no-op write is indistinguishable from a real one here;
32
+ * - on success, `{"ok":true,"count":<number|null>,"applied":<boolean>}`
33
+ * echoes the SUBMITTED `count`; `applied` is the server's answer to
34
+ * whether it was stored (DX-3676: `false` for an out-of-order report
35
+ * older than the one already on file — the caller must not treat it as
36
+ * the dashboard's current count);
37
+ * - DX-3676 — `<event-at>` is the ISO-8601 UTC time the reporting HOOK
38
+ * fired, captured by the caller, never this process's own clock: the
39
+ * hooks are async and can finish out of order, and the server orders
40
+ * reports by this time;
36
41
  * - human-readable diagnostics go to stderr only, never stdout.
37
42
  *
38
43
  * THE CREDENTIAL — the SAME resolver `bridge` and `plan-state` use, via
@@ -56,7 +61,7 @@ export const BACKGROUND_WORK_SUBCOMMAND = "background-work";
56
61
  export const BACKGROUND_WORK_PATH = "/api/plan-sessions/me/background-work";
57
62
  /** How long this one-shot call may take before it counts as a timeout. */
58
63
  export const BACKGROUND_WORK_REQUEST_TIMEOUT_MS = 5_000;
59
- const USAGE = `usage: CLAUDE_CODE_SESSION_ID=<session-id> danx-dashboard-mcp ${BACKGROUND_WORK_SUBCOMMAND} <non-negative-integer|clear>`;
64
+ const USAGE = `usage: CLAUDE_CODE_SESSION_ID=<session-id> danx-dashboard-mcp ${BACKGROUND_WORK_SUBCOMMAND} <non-negative-integer|clear> <event-at ISO-8601 UTC>`;
60
65
  /**
61
66
  * Parse the ONE positional argument: `clear`, or a non-negative integer
62
67
  * string. Anything else is a usage error — never guessed or floored.
@@ -68,6 +73,15 @@ export function parseCountArg(raw) {
68
73
  return { error: `count must be "clear" or a non-negative integer, got "${raw}"` };
69
74
  return Number.parseInt(raw, 10);
70
75
  }
76
+ /** DX-3676 — an ISO-8601 UTC instant, as `Date#toISOString` writes it (the server accepts exactly this). */
77
+ const ISO_UTC_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
78
+ /** Parse the event-time argument (DX-3676). Never substituted with this process's clock. */
79
+ export function parseEventAtArg(raw) {
80
+ if (!ISO_UTC_INSTANT.test(raw) || Number.isNaN(Date.parse(raw))) {
81
+ return { error: `event-at must be an ISO-8601 UTC timestamp (e.g. 2026-09-30T00:00:00.000Z), got "${raw}"` };
82
+ }
83
+ return raw;
84
+ }
71
85
  /**
72
86
  * One authenticated PUT, with a hard timeout, classified into the output
73
87
  * shape this subcommand always prints — never a throw, never a non-2xx
@@ -81,8 +95,8 @@ export function parseCountArg(raw) {
81
95
  * (no `409 session_not_connected` equivalent), so status alone decides
82
96
  * whether a body read is even attempted.
83
97
  */
84
- export async function putBackgroundWork(options, count, deps) {
85
- const sent = await sendSessionRequest(options, BACKGROUND_WORK_PATH, { method: "PUT", body: JSON.stringify({ count }) }, deps);
98
+ export async function putBackgroundWork(options, report, deps) {
99
+ const sent = await sendSessionRequest(options, BACKGROUND_WORK_PATH, { method: "PUT", body: JSON.stringify({ count: report.count, eventAt: report.eventAt }) }, deps);
86
100
  if ("reason" in sent)
87
101
  return sent;
88
102
  const { status, text } = sent;
@@ -99,10 +113,12 @@ export async function putBackgroundWork(options, count, deps) {
99
113
  if (typeof body !== "object" ||
100
114
  body === null ||
101
115
  !("count" in body) ||
102
- (body.count !== null && typeof body.count !== "number")) {
116
+ (body.count !== null && typeof body.count !== "number") ||
117
+ typeof body.applied !== "boolean") {
103
118
  return { ok: false, reason: "bad_response" };
104
119
  }
105
- return { ok: true, count: body.count };
120
+ const shaped = body;
121
+ return { ok: true, count: shaped.count, applied: shaped.applied };
106
122
  }
107
123
  /** The bin's `background-work` subcommand, wired to the real process. Always resolves to exit code 0. */
108
124
  export async function runBackgroundWorkCommand(argv, env = process.env,
@@ -111,7 +127,7 @@ resolveFrom = {}) {
111
127
  const write = (output) => {
112
128
  process.stdout.write(`${JSON.stringify(output)}\n`);
113
129
  };
114
- if (argv.length !== 1) {
130
+ if (argv.length !== 2) {
115
131
  write({ ok: false, reason: "usage" });
116
132
  process.stderr.write(`${USAGE}\n`);
117
133
  return 0;
@@ -123,6 +139,12 @@ resolveFrom = {}) {
123
139
  return 0;
124
140
  }
125
141
  const count = parsed;
142
+ const eventAt = parseEventAtArg(argv[1]);
143
+ if (typeof eventAt === "object") {
144
+ write({ ok: false, reason: "usage" });
145
+ process.stderr.write(`${eventAt.error}\n${USAGE}\n`);
146
+ return 0;
147
+ }
126
148
  const resolved = await resolveSessionOptions(env, resolveFrom, USAGE);
127
149
  if (!resolved.ok) {
128
150
  // A session that never `plan_connect`-ed has no connection record — this
@@ -132,7 +154,7 @@ resolveFrom = {}) {
132
154
  process.stderr.write(`${resolved.detail}\n`);
133
155
  return 0;
134
156
  }
135
- const output = await putBackgroundWork(resolved.options, count, {
157
+ const output = await putBackgroundWork(resolved.options, { count, eventAt }, {
136
158
  fetch: (input, init) => fetch(input, init),
137
159
  requestTimeoutMs: BACKGROUND_WORK_REQUEST_TIMEOUT_MS,
138
160
  });
package/dist/listen.js CHANGED
@@ -284,6 +284,11 @@ function cardList(cards) {
284
284
  * this signal cannot support. See `evaluateIdleNudge`'s own doc comment
285
285
  * (`src/issues/db/session-idle-nudge.ts`) for why a sub-agent-awareness
286
286
  * special case was rejected instead of this wording fix.
287
+ *
288
+ * DX-3676 — every named card clears every dispatch gate, so if the session
289
+ * still cannot work one, the card's recorded state is wrong (DX-3400 sat in
290
+ * ToDo with its real blocker only in a comment). The line therefore ends by
291
+ * naming each state-correcting move, which is also what stops the nudge.
287
292
  */
288
293
  export function formatNudgeLine(event) {
289
294
  const invalid = invalidNudgeReason(event);
@@ -294,7 +299,9 @@ export function formatNudgeLine(event) {
294
299
  parts.push(`startable: ${cardList(event.startable)}`);
295
300
  if (event.held.length > 0)
296
301
  parts.push(`held: ${cardList(event.held)}`);
297
- return oneLine(`${LINE_PREFIX} this session has not made an MCP call in ${event.idleMinutes}m — ${parts.join("; ")}`);
302
+ return oneLine(`${LINE_PREFIX} this session has not made an MCP call in ${event.idleMinutes}m — ${parts.join("; ")}. ` +
303
+ "Work these cards; any you cannot work, update to its true state: open a problem, add depends_on or " +
304
+ "conflict_on, block it with the real reason, or rollback_pickup.");
298
305
  }
299
306
  /**
300
307
  * Incremental SSE parser. Comment lines (keep-alives, the connect marker) and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.172",
3
+ "version": "0.1.173",
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",