@thehammer/danx-dashboard-mcp 0.1.312 → 0.1.314

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.
@@ -9,7 +9,10 @@
9
9
  * - the rest is written to ONE local file the danxbot plugin's `PreCompact` gate reads: `~/.config/danxbot/compaction/<sessionId>.json`. The gate never
10
10
  * asks the network: Claude Code proposes a compaction before every model request.
11
11
  *
12
- * The file is `{v: 1, sessionId, control: {floorTokens, passAtTokens, windowTokens, releaseUntilMs, releaseId} | null, askedAt: string | null}`. `sessionId` is the
12
+ * The file is `{v: 1, sessionId, control: {floorTokens, passAtTokens, windowTokens, releaseUntilMs, releaseId, agents} | null, askedAt: string | null}`.
13
+ * DX-5218: `agents` holds each SUB-AGENT's own control by agent id, `{passAtTokens, ask, releaseUntilMs, releaseId}`: the sub-agent's own safety-valve size, its own
14
+ * request (the relay cannot show a sub-agent a request, so unlike the main agent's it stays in the file for the plugin to deliver to that agent) and its own
15
+ * single-use release. `sessionId` is the
13
16
  * session the file was written for (the plugin refuses a file that names another); `windowTokens` is the context window the dashboard assumed for the session's
14
17
  * model line (the plugin does not arm a session whose real window is smaller). `control: null` says the session is not under the handshake (the plugin disarms). `askedAt` is the relay's own memory of the last request it showed the agent, kept in the file so a restart
15
18
  * of this server does not show it again; the plugin ignores it.
@@ -27,6 +30,20 @@ import { assertSessionIdSegment, pruneStaleSessionFiles } from "./session-connec
27
30
  export const COMPACTION_ASK_MAX_CODE_POINTS = 1000;
28
31
  const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
29
32
  const isCount = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
33
+ const isTime = (v) => typeof v === "number" && Number.isFinite(v);
34
+ const isAsk = (a) => isRecord(a) && isCount(a.seq) && typeof a.at === "string" && a.at !== "" && typeof a.text === "string" && a.text.trim() !== "";
35
+ /** Why an ask / release pair (the main agent's, or a sub-agent's) cannot be read, or `null` when it can. */
36
+ function invalidAskAndRelease(c) {
37
+ if (c.ask !== null && !isAsk(c.ask))
38
+ return "ask must be null or {seq, at, text}";
39
+ if ((c.releaseUntilMs === null) !== (c.releaseId === null))
40
+ return "releaseUntilMs and releaseId are both set or both null";
41
+ if (c.releaseUntilMs !== null && !isTime(c.releaseUntilMs))
42
+ return "releaseUntilMs must be a number or null";
43
+ if (c.releaseId !== null && (typeof c.releaseId !== "string" || c.releaseId === ""))
44
+ return "releaseId must be a string or null";
45
+ return null;
46
+ }
30
47
  /** Why a `compaction` frame's data cannot be read, or `null` when it can. */
31
48
  export function invalidCompactionReason(value) {
32
49
  if (!isRecord(value) || !("control" in value))
@@ -45,17 +62,22 @@ export function invalidCompactionReason(value) {
45
62
  return "passAtTokens must be above floorTokens";
46
63
  if (c.windowTokens <= c.passAtTokens)
47
64
  return "windowTokens must be above passAtTokens";
48
- if (c.ask !== null) {
49
- if (!isRecord(c.ask) || !isCount(c.ask.seq) || typeof c.ask.at !== "string" || c.ask.at === "" || typeof c.ask.text !== "string" || c.ask.text.trim() === "") {
50
- return "ask must be null or {seq, at, text}";
51
- }
65
+ const own = invalidAskAndRelease(c);
66
+ if (own !== null)
67
+ return own;
68
+ if (!isRecord(c.agents))
69
+ return "agents must be an object keyed by agent id";
70
+ for (const [id, a] of Object.entries(c.agents)) {
71
+ if (id === "")
72
+ return "agents has an empty agent id";
73
+ if (!isRecord(a))
74
+ return `agents.${id} is not an object`;
75
+ if (!isCount(a.passAtTokens))
76
+ return `agents.${id}.passAtTokens must be a whole number`;
77
+ const reason = invalidAskAndRelease(a);
78
+ if (reason !== null)
79
+ return `agents.${id}: ${reason}`;
52
80
  }
53
- if ((c.releaseUntilMs === null) !== (c.releaseId === null))
54
- return "releaseUntilMs and releaseId are both set or both null";
55
- if (c.releaseUntilMs !== null && !(typeof c.releaseUntilMs === "number" && Number.isFinite(c.releaseUntilMs)))
56
- return "releaseUntilMs must be a number or null";
57
- if (c.releaseId !== null && (typeof c.releaseId !== "string" || c.releaseId === ""))
58
- return "releaseId must be a string or null";
59
81
  return null;
60
82
  }
61
83
  /** The control of a valid frame's data (`invalidCompactionReason` is `null`). */
@@ -76,7 +98,14 @@ export function fileOf(control, askedAt, sessionId) {
76
98
  sessionId,
77
99
  control: control === null
78
100
  ? null
79
- : { floorTokens: control.floorTokens, passAtTokens: control.passAtTokens, windowTokens: control.windowTokens, releaseUntilMs: control.releaseUntilMs, releaseId: control.releaseId },
101
+ : {
102
+ floorTokens: control.floorTokens,
103
+ passAtTokens: control.passAtTokens,
104
+ windowTokens: control.windowTokens,
105
+ releaseUntilMs: control.releaseUntilMs,
106
+ releaseId: control.releaseId,
107
+ agents: control.agents,
108
+ },
80
109
  askedAt,
81
110
  };
82
111
  }
@@ -40,6 +40,7 @@ export const GET_ROUTE_BOARD_SCOPE = [
40
40
  ["/api/plans/mine/records", "refuse"],
41
41
  ["/api/platform/signup-codes", "refuse"],
42
42
  ["/api/platform/teams", "refuse"],
43
+ ["/api/plugins", "refuse"],
43
44
  ["/api/reminders", "refuse"],
44
45
  ["/api/repos", "refuse"],
45
46
  ["/api/security-incidents", "refuse"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.312",
3
+ "version": "0.1.314",
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",