@thehammer/danx-dashboard-mcp 0.1.302 → 0.1.304

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,6 @@
1
1
  import { boardStampOf } from "./route-board-scope.js";
2
2
  import { assertVersionFloorSatisfied, MCP_MIN_VERSION_HEADER } from "./version-floor.js";
3
+ import { PLUGIN_BACKGROUND_HEADER } from "./session-options.js";
3
4
  export class DashboardHttpClient {
4
5
  config;
5
6
  fetchImpl;
@@ -17,6 +18,14 @@ export class DashboardHttpClient {
17
18
  setToken(token) {
18
19
  this.config.token = token;
19
20
  }
21
+ /**
22
+ * DX-5076 - the ONE entry for a request this process makes on its own (the listener's health read, the key-permission read, the permission claim
23
+ * poll): it is never the agent acting, so it carries the background mark and ends no agent pause. A call the agent makes (`danxbot_api`, a
24
+ * permission-request create) goes through `request` instead. No call site sets `background` by hand.
25
+ */
26
+ requestOwn(args) {
27
+ return this.request({ ...args, background: true });
28
+ }
20
29
  async request(args) {
21
30
  if (this.config.token === "") {
22
31
  // DX-4390 — a caller reaches here only past the sign-in halt; a bearer-less call is a bug.
@@ -51,7 +60,7 @@ export class DashboardHttpClient {
51
60
  headers["x-danx-session-id"] = this.config.session.id;
52
61
  }
53
62
  if (args.background === true) {
54
- headers["x-danx-plugin-background"] = "1";
63
+ headers[PLUGIN_BACKGROUND_HEADER] = "1";
55
64
  }
56
65
  let bodyString;
57
66
  if (args.body !== undefined) {
package/dist/index.js CHANGED
@@ -217,11 +217,12 @@ function boot() {
217
217
  permissionRequests = new PermissionRequests({
218
218
  access,
219
219
  request: (args) => dashboardClient.request(args),
220
+ requestOwn: (args) => dashboardClient.requestOwn(args),
220
221
  log: (line) => console.error(line),
221
222
  });
222
223
  listenerReminder = createListenerReminder({
223
224
  sessionId: config.session?.id,
224
- fetchHealth: () => dashboardClient.request({ method: "GET", basePath: "", path: "/api/plans", query: { limit: 1 } }),
225
+ fetchHealth: () => dashboardClient.requestOwn({ method: "GET", basePath: "", path: "/api/plans", query: { limit: 1 } }),
225
226
  isPlanConnected: isPlanConnectedOnThisMachine,
226
227
  now: Date.now,
227
228
  logLine: (line) => console.error(line),
@@ -270,7 +271,8 @@ function boot() {
270
271
  async function readKeyPermissions() {
271
272
  if (!access.isSignedIn())
272
273
  return { outcome: "no-key" };
273
- const guarded = await access.guard(() => client.request({ method: "GET", basePath: "", path: "/api/auth/me" }));
274
+ // DX-5076: this process reading its own key's permissions is not the agent acting.
275
+ const guarded = await access.guard(() => client.requestOwn({ method: "GET", basePath: "", path: "/api/auth/me" }));
274
276
  if ("halt" in guarded)
275
277
  return { outcome: "no-key" };
276
278
  const { result } = guarded;
@@ -396,7 +398,7 @@ function readSessionConfig() {
396
398
  */
397
399
  const SERVER_INSTRUCTIONS = `\
398
400
  \`danxbot_api\` is your primary channel to the dashboard: every card, plan, Slack and dispatch-callback job (ending
399
- your dispatch included) is a call to it. Find a route with \`danxbot_api_list\`. Read its exact shape with
401
+ yours too) is a call to it. Find a route with \`danxbot_api_list\`. Read its exact shape with
400
402
  \`danxbot_api_spec\` before the first call to any route you have not called before; never guess a body.
401
403
 
402
404
  RESOURCES. Most collections take GET (list) and POST (create); a single resource (\`<collection>/<id>\`) takes
@@ -405,7 +407,7 @@ GET, PATCH or PUT (send only the fields you change) and DELETE. A card is edited
405
407
  \`total\` (page with \`limit\` and \`offset\`). Name each other field or relation you need in the \`fields\` query
406
408
  tree. \`GET /api/resources/<resource>/fields\` lists the names.
407
409
 
408
- STALE HASH. A write to contested text (a card's title, description or checklists, a plan record, a problem) sends
410
+ STALE HASH. A write to contested text (card text, a plan record, a problem) sends
409
411
  back the \`content_hash\` or \`base_hash\` you read from that resource. A stale hash is refused 409 with the current
410
412
  hash and content: merge your change into that and retry with the fresh hash.
411
413
 
@@ -415,8 +417,11 @@ REFUSALS. A 4xx body is \`{error, ...}\`, and its other fields name the fix: \`o
415
417
  WRITING STANDARD, for every title, summary, description, comment, problem, solution and plan record you write. The
416
418
  reader is new to the subject and will not read the code or docs: what you write is their documentation. Open with
417
419
  plain background (what the thing is, why it exists, what is wrong or what the choice changes) before evidence.
418
- Define each term a newcomer would not know. Cite exact ids, SHAs and paths. Be brief: less to read, never a
419
- transcript of your reasoning. A title or summary alone must let the reader decide.
420
+ Define each term a newcomer would not know. Cite exact ids, SHAs and paths. Be brief; never transcribe your
421
+ reasoning. A title or summary alone must let the reader decide.
422
+
423
+ DESIGN. UI designs and style options go on a Design card (\`design_publish\`), never an Artifact or HTML page.
424
+ Decide choices yourself; show alternatives as design states, never ask.
420
425
  `;
421
426
  assertWithinTextLimit("the server instructions", SERVER_INSTRUCTIONS);
422
427
  export const server = new McpServer({
@@ -96,7 +96,7 @@ export class PermissionRequests {
96
96
  let guarded;
97
97
  try {
98
98
  // `guard` runs before EVERY claim (DX-4418): a revoked or signed-out session answers its halt without a request.
99
- guarded = await this.deps.access.guard(() => this.deps.request({ method: "POST", basePath: PERMISSION_REQUESTS_BASE_PATH, path: `/${request.publicId}/claim` }));
99
+ guarded = await this.deps.access.guard(() => this.deps.requestOwn({ method: "POST", basePath: PERMISSION_REQUESTS_BASE_PATH, path: `/${request.publicId}/claim` }));
100
100
  }
101
101
  catch (err) {
102
102
  // An outdated MCP will never get a different answer; any other failure is retried until the request ends.
@@ -10,6 +10,8 @@ import { credentialFingerprint, describeCredentialSource, readCredential } from
10
10
  import { readSessionConnection } from "./session-connection.js";
11
11
  import { fixForStop } from "./stop-fixes.js";
12
12
  export const SESSION_ID_HEADER = "x-danx-session-id";
13
+ /** DX-5033 / DX-5076 - marks a request as this process's own background work (not the agent acting): the dashboard ends no agent pause on it. */
14
+ export const PLUGIN_BACKGROUND_HEADER = "x-danx-plugin-background";
13
15
  /** A start this process cannot make, named the way the session will be told about it. */
14
16
  export class SessionStartError extends Error {
15
17
  // `ListenStopReason`, not `string`: a start-time failure is always one of `no_connection_record` / `credential_unavailable`
@@ -10,7 +10,7 @@ import { errorCodeOf } from "./json-body.js";
10
10
  import { boundRequest } from "./request-bound.js";
11
11
  import { keyRevocationOf, keyRevokedHalt } from "./key-revoked-halt.js";
12
12
  import { oneLine } from "./one-line.js";
13
- import { SESSION_ID_HEADER } from "./session-options.js";
13
+ import { PLUGIN_BACKGROUND_HEADER, SESSION_ID_HEADER } from "./session-options.js";
14
14
  import { isClaimState } from "@thehammer/danx-session-telemetry";
15
15
  /**
16
16
  * DX-3912 — the longest heartbeat `reason` the relay sends: one under the dashboard's own
@@ -74,6 +74,8 @@ async function request(options, deps, what, url, init = { method: "GET" }) {
74
74
  Authorization: `Bearer ${options.token}`,
75
75
  Accept: "application/json",
76
76
  [SESSION_ID_HEADER]: options.sessionId,
77
+ // DX-5076: every request this module makes is the relay's own work (ticket mint, inventory read, heartbeat), never the agent acting, so it must not end an agent pause. One marking point, so a new request here cannot forget it.
78
+ [PLUGIN_BACKGROUND_HEADER]: "1",
77
79
  };
78
80
  if (init.body !== undefined)
79
81
  headers["Content-Type"] = "application/json";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.302",
3
+ "version": "0.1.304",
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",