@thehammer/danx-dashboard-mcp 0.1.188 → 0.1.189
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/index.js +1 -1
- package/dist/listen.js +31 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -422,7 +422,7 @@ strictTool("attach_file", "Attach a LOCAL file to a card: `card_id` + `path` (an
|
|
|
422
422
|
// no existing plan for an id to name yet.
|
|
423
423
|
strictTool("plan_connect",
|
|
424
424
|
// DX-2735: trimmed with the problem tools to stay inside the work-profile budget.
|
|
425
|
-
"Connect THIS session to a plan. ONE CALL IS ENOUGH TO START: the reply carries `{session, movedFrom, browserInstruction, briefing, listenerHealth}` — `browserInstruction` is a server-built action to take NOW: it names the plan's URL and tells you to open it (in-app browser if you have one, else default), keep that tab open for the whole session without navigating it away, and use a different tab for your own browsing — the operator's tab for following and talking to you. Returned on every connect, including a re-connect, so it doubles as the post-context-loss reminder. `briefing` is every goal/rule/caveat (ref+body), every architecture section (id+title), the plan's own ref/name/status, a first page of open cards (id/type/status/title/openProblemCount/assignedAgent) with a `morePagesHint` when more exist, and the closed-card count — usually replacing the plan read + card batch-read a fresh session used to need. A session is on AT MOST ONE plan: connecting elsewhere MOVES it, and `movedFrom: {id, name} | null` says which plan it left (null = no plan, or already this one). Binds only your OWN session, resolved from the session id this server forwards; afterwards a `danxbot_api` call to a `/api/plans/mine/...` route (there is no dedicated plan-write tool) acts on this plan and takes no plan id. `listenerHealth` reports the event bridge's state — `null` (no session), or `{attached, state: \"unattached\"|\"credential_mismatch\"|\"healthy\", nextStep}` naming a concrete fix per unhealthy state (same shape the plan list/get routes report). Once healthy, every comment, answer, problem added and block/unblock on this plan's cards reaches the session unpolled, relayed by the plugin's event bridge as a line like `[DX-8 \"Title\" repo:board] newms87 answered \"<statement>\": chose \"Pause E2E\"`. Pass `title` (call `get_session({session_id:\"self\"})` first and forward its `title` verbatim) so the dashboard shows the same name Claude does — this server cannot read it itself. The response's `reminders[]` (when present) carries the mantra — read and follow it.", {
|
|
425
|
+
"Connect THIS session to a plan. ONE CALL IS ENOUGH TO START: the reply carries `{session, movedFrom, browserInstruction, briefing, listenerHealth}` — `browserInstruction` is a server-built action to take NOW: it names the plan's URL and tells you to open it (in-app browser if you have one, else default), keep that tab open for the whole session without navigating it away, and use a different tab for your own browsing — the operator's tab for following and talking to you. Returned on every connect, including a re-connect, so it doubles as the post-context-loss reminder. `briefing` is every goal/rule/caveat (ref+body), every architecture section (id+title), the plan's own ref/name/status, a first page of open cards (id/type/status/title/openProblemCount/assignedAgent) with a `morePagesHint` when more exist, and the closed-card count — usually replacing the plan read + card batch-read a fresh session used to need. A session is on AT MOST ONE plan: connecting elsewhere MOVES it, and `movedFrom: {id, name} | null` says which plan it left (null = no plan, or already this one). Binds only your OWN session, resolved from the session id this server forwards; afterwards a `danxbot_api` call to a `/api/plans/mine/...` route (there is no dedicated plan-write tool) acts on this plan and takes no plan id. `listenerHealth` reports the event bridge's state — `null` (no session), or `{attached, state: \"unattached\"|\"credential_mismatch\"|\"healthy\", nextStep}` naming a concrete fix per unhealthy state (same shape the plan list/get routes report). Once healthy, every comment, answer, problem added and block/unblock on this plan's cards reaches the session unpolled, relayed by the plugin's event bridge as a line like `[DX-8 \"Title\" repo:board] newms87 answered \"<statement>\": chose \"Pause E2E\"`. An action the operator DECLINES arrives as `... rejected the action \"<title>\" for \"<statement>\" — reason: \"<note>\". Do not perform it; re-plan from the reason` (a decision with `outcome: \"rejected\"` — never treat it as work done). Pass `title` (call `get_session({session_id:\"self\"})` first and forward its `title` verbatim) so the dashboard shows the same name Claude does — this server cannot read it itself. The response's `reminders[]` (when present) carries the mantra — read and follow it.", {
|
|
426
426
|
plan_id: z.number().int().positive().describe("The plan id, from the plans list (`danxbot_api` `GET /api/plans`)."),
|
|
427
427
|
title: z
|
|
428
428
|
.string()
|
package/dist/listen.js
CHANGED
|
@@ -78,6 +78,27 @@ function isRecord(value) {
|
|
|
78
78
|
function isCappedText(value) {
|
|
79
79
|
return isRecord(value) && typeof value.text === "string" && typeof value.truncated === "boolean";
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* DX-3880 — the part `solution_answered` and `problem_answer_changed` share: the
|
|
83
|
+
* decision's `outcome` and the either/or body it implies. A rejection (the
|
|
84
|
+
* operator DECLINED an action) always names the solution and carries its reason
|
|
85
|
+
* as `solution.note`; an answer is a solution (optional note) or free-form text.
|
|
86
|
+
*/
|
|
87
|
+
function invalidDecisionBody(d) {
|
|
88
|
+
if (d.outcome !== "answered" && d.outcome !== "rejected")
|
|
89
|
+
return 'detail.outcome is not "answered" or "rejected"';
|
|
90
|
+
if (d.solution === null) {
|
|
91
|
+
if (d.outcome === "rejected")
|
|
92
|
+
return "detail.solution is null on a rejection";
|
|
93
|
+
return isCappedText(d.freeform) ? null : "detail.freeform is not capped text";
|
|
94
|
+
}
|
|
95
|
+
if (!isRecord(d.solution) || typeof d.solution.title !== "string")
|
|
96
|
+
return "detail.solution is not {title, note}";
|
|
97
|
+
if (d.outcome === "rejected") {
|
|
98
|
+
return isCappedText(d.solution.note) ? null : "detail.solution.note (the rejection reason) is not capped text";
|
|
99
|
+
}
|
|
100
|
+
return d.solution.note === null || isCappedText(d.solution.note) ? null : "detail.solution.note is not capped text";
|
|
101
|
+
}
|
|
81
102
|
/**
|
|
82
103
|
* DX-2735 — why an event cannot be described, or `null` when it can.
|
|
83
104
|
*
|
|
@@ -122,11 +143,7 @@ export function invalidEventReason(value) {
|
|
|
122
143
|
if (!isRecord(problem) || typeof problem.id !== "number" || !isCappedText(problem.statement)) {
|
|
123
144
|
return "detail.problem is not {id, statement}";
|
|
124
145
|
}
|
|
125
|
-
|
|
126
|
-
return isCappedText(d.freeform) ? null : "detail.freeform is not capped text";
|
|
127
|
-
if (!isRecord(d.solution) || typeof d.solution.title !== "string")
|
|
128
|
-
return "detail.solution is not {title, note}";
|
|
129
|
-
return d.solution.note === null || isCappedText(d.solution.note) ? null : "detail.solution.note is not capped text";
|
|
146
|
+
return invalidDecisionBody(d);
|
|
130
147
|
}
|
|
131
148
|
case "problem_added": {
|
|
132
149
|
const problem = d.problem;
|
|
@@ -155,11 +172,7 @@ export function invalidEventReason(value) {
|
|
|
155
172
|
return "detail.retracted_decision_id is not a number";
|
|
156
173
|
if (typeof d.decision_id !== "number")
|
|
157
174
|
return "detail.decision_id is not a number";
|
|
158
|
-
|
|
159
|
-
return isCappedText(d.freeform) ? null : "detail.freeform is not capped text";
|
|
160
|
-
if (!isRecord(d.solution) || typeof d.solution.title !== "string")
|
|
161
|
-
return "detail.solution is not {title, note}";
|
|
162
|
-
return d.solution.note === null || isCappedText(d.solution.note) ? null : "detail.solution.note is not capped text";
|
|
175
|
+
return invalidDecisionBody(d);
|
|
163
176
|
}
|
|
164
177
|
case "blocked":
|
|
165
178
|
return isCappedText(d.reason) ? null : "detail.reason is not capped text";
|
|
@@ -196,8 +209,13 @@ function describe(event) {
|
|
|
196
209
|
// so the line names the problem — otherwise the agent cannot tell which of its
|
|
197
210
|
// questions just closed.
|
|
198
211
|
const problem = d.problem;
|
|
199
|
-
const answered = `${who} answered ${quoted(problem.statement)}:`;
|
|
200
212
|
const solution = d.solution;
|
|
213
|
+
// DX-3880 — a REJECTION is the opposite of work done: the operator declined
|
|
214
|
+
// the action, so the line says so and names the reason; the agent must not
|
|
215
|
+
// perform it and re-plans from the note.
|
|
216
|
+
if (d.outcome === "rejected")
|
|
217
|
+
return `${who} rejected the action "${solution.title}" for ${quoted(problem.statement)} — reason: ${quoted(solution.note)}. Do not perform it; re-plan from the reason`;
|
|
218
|
+
const answered = `${who} answered ${quoted(problem.statement)}:`;
|
|
201
219
|
if (solution === null)
|
|
202
220
|
return `${answered} ${quoted(d.freeform)}`;
|
|
203
221
|
const note = solution.note === null ? "" : ` — note: ${quoted(solution.note)}`;
|
|
@@ -222,6 +240,8 @@ function describe(event) {
|
|
|
222
240
|
// open questions knows which one just moved.
|
|
223
241
|
const problem = d.problem;
|
|
224
242
|
const solution = d.solution;
|
|
243
|
+
if (d.outcome === "rejected")
|
|
244
|
+
return `${who} changed the answer to ${quoted(problem.statement)}: now REJECTED the action "${solution.title}" — reason: ${quoted(solution.note)}. Do not perform it; re-plan from the reason`;
|
|
225
245
|
const now = solution === null ? quoted(d.freeform) : `"${solution.title}"`;
|
|
226
246
|
return `${who} changed the answer to ${quoted(problem.statement)}: now ${now}`;
|
|
227
247
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/danx-dashboard-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.189",
|
|
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",
|