donsetch 2.5.0 → 3.0.0
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/package.json +1 -1
- package/pi-extension.ts +68 -12
package/package.json
CHANGED
package/pi-extension.ts
CHANGED
|
@@ -118,7 +118,11 @@ function startServer(): Promise<void> {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
try {
|
|
121
|
-
|
|
121
|
+
// --supervised: crash-only daemon. If the MCP server is SIGKILLed
|
|
122
|
+
// (OOM, crash), the supervisor respawns it and replays in-flight
|
|
123
|
+
// requests — pi users never see a dead tool until the process
|
|
124
|
+
// itself dies.
|
|
125
|
+
proc = spawn(binaryPath, ["mcp", "--supervised"], {
|
|
122
126
|
stdio: ["pipe", "pipe", "pipe"],
|
|
123
127
|
env: { ...process.env },
|
|
124
128
|
windowsHide: true,
|
|
@@ -196,20 +200,58 @@ function startServer(): Promise<void> {
|
|
|
196
200
|
});
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
function sendRequest(
|
|
203
|
+
function sendRequest(
|
|
204
|
+
method: string,
|
|
205
|
+
params: any,
|
|
206
|
+
timeoutMs = CALL_TIMEOUT_MS,
|
|
207
|
+
signal?: AbortSignal
|
|
208
|
+
): Promise<any> {
|
|
200
209
|
return new Promise((resolve, reject) => {
|
|
201
210
|
if (!proc?.stdin?.writable) {
|
|
202
211
|
reject(new Error("donsetch MCP server not running"));
|
|
203
212
|
return;
|
|
204
213
|
}
|
|
205
214
|
const id = nextId++;
|
|
215
|
+
let settled = false;
|
|
216
|
+
const finish = (fn: (v: any) => void, v: any) => {
|
|
217
|
+
if (settled) return;
|
|
218
|
+
settled = true;
|
|
219
|
+
signal?.removeEventListener("abort", onAbort);
|
|
220
|
+
fn(v);
|
|
221
|
+
};
|
|
206
222
|
const timer = setTimeout(() => {
|
|
207
|
-
if (pending.
|
|
208
|
-
|
|
209
|
-
reject(new Error(`MCP request timeout (${timeoutMs}ms): ${method}`));
|
|
223
|
+
if (pending.delete(id)) {
|
|
224
|
+
finish(reject, new Error(`MCP request timeout (${timeoutMs}ms): ${method}`));
|
|
210
225
|
}
|
|
211
226
|
}, timeoutMs);
|
|
212
|
-
|
|
227
|
+
// v3 real MCP cancellation: forward pi's abort to the server so the
|
|
228
|
+
// in-flight fetch/crawl actually stops server-side, then settle
|
|
229
|
+
// locally. The caller maps this to a graceful "Cancelled" result.
|
|
230
|
+
const onAbort = () => {
|
|
231
|
+
if (pending.delete(id)) {
|
|
232
|
+
clearTimeout(timer);
|
|
233
|
+
sendNotification("notifications/cancelled", { id, reason: "client aborted" });
|
|
234
|
+
finish(reject, new Error("cancelled"));
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
if (signal) {
|
|
238
|
+
if (signal.aborted) {
|
|
239
|
+
onAbort();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
243
|
+
}
|
|
244
|
+
pending.set(id, {
|
|
245
|
+
resolve: (v: any) => {
|
|
246
|
+
clearTimeout(timer);
|
|
247
|
+
finish(resolve, v);
|
|
248
|
+
},
|
|
249
|
+
reject: (e: any) => {
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
finish(reject, e);
|
|
252
|
+
},
|
|
253
|
+
timer,
|
|
254
|
+
});
|
|
213
255
|
const msg = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
214
256
|
proc.stdin.write(msg + "\n");
|
|
215
257
|
});
|
|
@@ -221,8 +263,8 @@ function sendNotification(method: string, params: any): void {
|
|
|
221
263
|
proc.stdin.write(msg + "\n");
|
|
222
264
|
}
|
|
223
265
|
|
|
224
|
-
async function callMcpTool(name: string, args: any): Promise<any> {
|
|
225
|
-
return sendRequest("tools/call", { name, arguments: args ?? {} });
|
|
266
|
+
async function callMcpTool(name: string, args: any, signal?: AbortSignal): Promise<any> {
|
|
267
|
+
return sendRequest("tools/call", { name, arguments: args ?? {} }, CALL_TIMEOUT_MS, signal);
|
|
226
268
|
}
|
|
227
269
|
|
|
228
270
|
function killServer(): void {
|
|
@@ -384,7 +426,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
384
426
|
}
|
|
385
427
|
|
|
386
428
|
try {
|
|
387
|
-
const result = await callMcpTool(toolName, params);
|
|
429
|
+
const result = await callMcpTool(toolName, params, _signal);
|
|
388
430
|
// Join all content text blocks, skipping [meta] blocks.
|
|
389
431
|
// [meta] blocks contain compact metadata for clients
|
|
390
432
|
// (Claude Code, VSCode) that drop text when
|
|
@@ -411,13 +453,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
411
453
|
} else if (toolName === "web_fetch") {
|
|
412
454
|
details.source = getFetchSource(sc);
|
|
413
455
|
details.status = getFetchStatus(sc);
|
|
456
|
+
if (sc?.stitched) details.stitched = sc.stitched;
|
|
414
457
|
} else if (toolName === "web_crawl") {
|
|
415
458
|
details.pages = countCrawlPages(text);
|
|
416
459
|
}
|
|
417
460
|
|
|
418
|
-
// For errors, extract error text
|
|
461
|
+
// For errors, extract error text + v3 stable error code
|
|
419
462
|
if (isErr) {
|
|
420
463
|
details.error = getPreview(text, 60);
|
|
464
|
+
if (sc?.code) details.code = sc.code;
|
|
421
465
|
} else {
|
|
422
466
|
details.preview = getPreview(text);
|
|
423
467
|
}
|
|
@@ -428,6 +472,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
428
472
|
isError: isErr,
|
|
429
473
|
};
|
|
430
474
|
} catch (err: any) {
|
|
475
|
+
// User pressed Esc in pi: we already told the server to stop
|
|
476
|
+
// (notifications/cancelled). Graceful non-error result, per
|
|
477
|
+
// pi's extension contract for aborted calls.
|
|
478
|
+
if (err.message === "cancelled") {
|
|
479
|
+
return {
|
|
480
|
+
content: [{ type: "text", text: "Cancelled" }],
|
|
481
|
+
details: { mcpTool: toolName, cancelled: true },
|
|
482
|
+
};
|
|
483
|
+
}
|
|
431
484
|
return {
|
|
432
485
|
content: [{ type: "text", text: `donsetch MCP call failed: ${err.message}` }],
|
|
433
486
|
details: { mcpTool: toolName, isError: true, error: err.message },
|
|
@@ -469,6 +522,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
469
522
|
else if (d.source === "ghost") parts.push("via ghost");
|
|
470
523
|
if (d.status === "blocked") parts.push("blocked");
|
|
471
524
|
else if (d.status === "thin") parts.push("thin");
|
|
525
|
+
if (d.stitched) parts.push(`stitched \u00D7${d.stitched}`);
|
|
472
526
|
meta = parts.join(" \u00B7 ");
|
|
473
527
|
} else if (toolName === "web_search") {
|
|
474
528
|
const count = d.results ?? 0;
|
|
@@ -481,8 +535,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
481
535
|
|
|
482
536
|
// Build line 2: preview or error
|
|
483
537
|
let line2 = "";
|
|
484
|
-
if (
|
|
485
|
-
line2 =
|
|
538
|
+
if (d.cancelled) {
|
|
539
|
+
line2 = "cancelled";
|
|
540
|
+
} else if (isErr) {
|
|
541
|
+
line2 = d.code ? `[${d.code}] ${d.error || "failed"}` : d.error || "failed";
|
|
486
542
|
} else if (toolName === "web_search" && d.topResult) {
|
|
487
543
|
line2 = truncate(d.topResult, 70);
|
|
488
544
|
} else if (d.preview) {
|