@thehammer/danx-dashboard-mcp 0.1.216 → 0.1.219
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/lifecycle.js +38 -13
- package/dist/terms-halt.js +3 -1
- package/package.json +1 -1
package/dist/lifecycle.js
CHANGED
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
* - Descriptions trimmed of DX-card historical narration where cheap to do
|
|
37
37
|
* without re-deriving the byte-for-byte target DX-3394 measured.
|
|
38
38
|
*/
|
|
39
|
+
import { readFile, stat } from "node:fs/promises";
|
|
40
|
+
import { basename, isAbsolute } from "node:path";
|
|
39
41
|
import { z } from "zod";
|
|
40
42
|
/**
|
|
41
43
|
* The five canonical agent-visible `danxbot_complete` statuses. DX-3394
|
|
@@ -114,15 +116,37 @@ async function postWorkerRoute(url, body, toolName, bearer) {
|
|
|
114
116
|
return { raw: text };
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
/** Must match `MAX_DECODED_BYTES` in danxbot's `src/issues/attachment-limits.ts` (duplicated, not imported: this package is published on its own). */
|
|
120
|
+
const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
121
|
+
/**
|
|
122
|
+
* DX-4251 — read each attached path HERE, in the MCP server process beside the
|
|
123
|
+
* agent, and return its bytes. The dashboard that serves the reply route can
|
|
124
|
+
* run on a different host than the agent (dedicated topology), so a path could
|
|
125
|
+
* never be resolved over there.
|
|
126
|
+
*/
|
|
127
|
+
async function readAttachments(paths) {
|
|
128
|
+
if (paths === undefined)
|
|
119
129
|
return [];
|
|
120
|
-
return
|
|
121
|
-
if (!entry
|
|
122
|
-
throw new Error(`field "
|
|
130
|
+
return Promise.all(paths.map(async (entry, idx) => {
|
|
131
|
+
if (!isAbsolute(entry)) {
|
|
132
|
+
throw new Error(`field "files"[${idx}] must be an absolute path (got "${entry}")`);
|
|
123
133
|
}
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
const info = await stat(entry);
|
|
135
|
+
// DX-4251: a directory stats as size 0 (Windows) or 4096 (Linux); name it, never read it as a file.
|
|
136
|
+
if (!info.isFile())
|
|
137
|
+
throw new Error(`field "files"[${idx}] "${entry}" is not a file`);
|
|
138
|
+
if (info.size === 0)
|
|
139
|
+
throw new Error(`field "files"[${idx}] "${entry}" is an empty file`);
|
|
140
|
+
if (info.size > MAX_ATTACHMENT_BYTES) {
|
|
141
|
+
throw new Error(`field "files"[${idx}] "${entry}" is ${info.size} bytes, over the ${MAX_ATTACHMENT_BYTES}-byte attachment limit`);
|
|
142
|
+
}
|
|
143
|
+
return { filename: basename(entry), base64: (await readFile(entry)).toString("base64") };
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
/** A refused callback's body is the only place the server says why — relay it, never just the status. */
|
|
147
|
+
async function responseFailure(label, response) {
|
|
148
|
+
const text = await response.text();
|
|
149
|
+
return new Error(`${label} returned HTTP ${response.status}${text ? `: ${text}` : ""}`);
|
|
126
150
|
}
|
|
127
151
|
/**
|
|
128
152
|
* Registers every lifecycle tool on `server` via the caller's `strictTool`
|
|
@@ -190,25 +214,26 @@ export function registerLifecycleTools(strictTool) {
|
|
|
190
214
|
});
|
|
191
215
|
// ---------------- danxbot_slack_reply ----------------
|
|
192
216
|
const slackReply = strictTool("danxbot_slack_reply", "Post the FINAL user-facing reply to the originating Slack thread. Call exactly once per dispatch, " +
|
|
193
|
-
"immediately before danxbot_complete. Optionally attach result files via files[] (absolute
|
|
194
|
-
"
|
|
217
|
+
"immediately before danxbot_complete. Optionally attach result files via files[] (absolute paths of files on YOUR own " +
|
|
218
|
+
"disk — their bytes are uploaded, so this works whichever host the dashboard runs on) — text + attachments " +
|
|
219
|
+
"land as ONE atomic thread post. For intermediate " +
|
|
195
220
|
"progress use danxbot_slack_post_update instead.", {
|
|
196
221
|
text: z.string().describe("The final reply text to post to the Slack thread."),
|
|
197
222
|
files: z
|
|
198
223
|
.array(z.string())
|
|
199
224
|
.optional()
|
|
200
|
-
.describe("Optional absolute
|
|
225
|
+
.describe("Optional absolute paths of files on your own disk to attach; their bytes are uploaded."),
|
|
201
226
|
}, async (args) => {
|
|
202
227
|
const url = readLifecycleUrlsFromEnv().slackReply;
|
|
203
228
|
if (!url)
|
|
204
229
|
throw new Error("danxbot_slack_reply called outside a Slack dispatch");
|
|
205
|
-
const files =
|
|
230
|
+
const files = await readAttachments(args.files);
|
|
206
231
|
const body = { text: args.text };
|
|
207
232
|
if (files.length > 0)
|
|
208
233
|
body.files = files;
|
|
209
234
|
const response = await postJson(url, body, dashboardBearer());
|
|
210
235
|
if (!response.ok)
|
|
211
|
-
throw
|
|
236
|
+
throw await responseFailure("Slack reply API", response);
|
|
212
237
|
return {
|
|
213
238
|
content: [
|
|
214
239
|
{
|
|
@@ -228,7 +253,7 @@ export function registerLifecycleTools(strictTool) {
|
|
|
228
253
|
throw new Error("danxbot_slack_post_update called outside a Slack dispatch");
|
|
229
254
|
const response = await postJson(url, { text: args.text }, dashboardBearer());
|
|
230
255
|
if (!response.ok)
|
|
231
|
-
throw
|
|
256
|
+
throw await responseFailure("Slack update API", response);
|
|
232
257
|
return { content: [{ type: "text", text: "Update posted to Slack thread" }] };
|
|
233
258
|
});
|
|
234
259
|
// ---------------- link_thread_to_issue ----------------
|
package/dist/terms-halt.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const TERMS_ACCEPTANCE_PATH = "/api/account/terms-acceptance";
|
|
2
2
|
// Mirrors `termsAcceptUrl("/")` in src/lib/terms-accept-path.ts (a published package cannot import it);
|
|
3
3
|
// terms-gate.integration.test.ts pins the server's link to the same literal. Only meaningful for an
|
|
4
|
-
// operator session: a dispatch's DANXBOT_DASHBOARD_URL is internal, and a dispatch's own
|
|
4
|
+
// operator session: a dispatch's DANXBOT_DASHBOARD_URL is internal, and a dispatch's own key is its
|
|
5
|
+
// owner's API key (DX-4191), so a dispatched agent can meet this refusal too and hands the link to the
|
|
6
|
+
// owner's operator.
|
|
5
7
|
const ACCEPT_SCREEN_PATH = "/terms/accept?return=%2F";
|
|
6
8
|
/** Keep in step with `termsRefusalInstruction` in src/dashboard/route-policy.ts. */
|
|
7
9
|
export function haltText(acceptUrl, owner) {
|
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.219",
|
|
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",
|