tandem-editor 0.6.2 → 0.6.3
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/CHANGELOG.md +22 -0
- package/dist/channel/index.js.map +1 -1
- package/dist/cli/index.js +167 -46
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/index-B1Cd5UGT.js +349 -0
- package/dist/client/index.html +48 -1
- package/dist/monitor/index.js +1 -0
- package/dist/monitor/index.js.map +1 -1
- package/dist/server/index.js +60501 -59641
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/index-mo5ZOPfU.js +0 -349
package/dist/cli/index.js
CHANGED
|
@@ -257,7 +257,7 @@ var init_cli_runtime = __esm({
|
|
|
257
257
|
});
|
|
258
258
|
|
|
259
259
|
// src/cli/preflight.ts
|
|
260
|
-
async function
|
|
260
|
+
async function probeTandemServer(opts = {}) {
|
|
261
261
|
const url = resolveTandemUrl(opts.url);
|
|
262
262
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
263
263
|
const controller = new AbortController();
|
|
@@ -265,22 +265,36 @@ async function ensureTandemServer(opts = {}) {
|
|
|
265
265
|
try {
|
|
266
266
|
const res = await fetch(`${url}/health`, { signal: controller.signal });
|
|
267
267
|
if (!res.ok) {
|
|
268
|
-
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
url,
|
|
271
|
+
reason: `health endpoint returned HTTP ${res.status}`,
|
|
272
|
+
kind: "unhealthy"
|
|
273
|
+
};
|
|
269
274
|
}
|
|
275
|
+
return { ok: true };
|
|
270
276
|
} catch (err) {
|
|
271
|
-
|
|
272
|
-
|
|
277
|
+
return {
|
|
278
|
+
ok: false,
|
|
279
|
+
url,
|
|
280
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
281
|
+
kind: "unreachable"
|
|
282
|
+
};
|
|
273
283
|
} finally {
|
|
274
284
|
clearTimeout(timer);
|
|
275
285
|
}
|
|
276
286
|
}
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
287
|
+
async function ensureTandemServer(opts = {}) {
|
|
288
|
+
const probe = await probeTandemServer(opts);
|
|
289
|
+
if (!probe.ok) {
|
|
290
|
+
const guidance = probe.kind === "unreachable" ? "Start the Tauri app or run `tandem start` on the host, then retry." : "The Tandem server is running but unhealthy \u2014 check the host logs.";
|
|
291
|
+
process.stderr.write(
|
|
292
|
+
`[tandem] Tandem server preflight failed at ${probe.url} (${probe.reason}).
|
|
293
|
+
[tandem] ${guidance}
|
|
281
294
|
`
|
|
282
|
-
|
|
283
|
-
|
|
295
|
+
);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
284
298
|
}
|
|
285
299
|
var DEFAULT_TIMEOUT_MS;
|
|
286
300
|
var init_preflight = __esm({
|
|
@@ -295,80 +309,165 @@ var init_preflight = __esm({
|
|
|
295
309
|
var mcp_stdio_exports = {};
|
|
296
310
|
__export(mcp_stdio_exports, {
|
|
297
311
|
getRequestId: () => getRequestId,
|
|
312
|
+
getResponseId: () => getResponseId,
|
|
298
313
|
runMcpStdio: () => runMcpStdio
|
|
299
314
|
});
|
|
300
315
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
301
316
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
302
317
|
async function runMcpStdio() {
|
|
303
318
|
const baseUrl = resolveTandemUrl();
|
|
304
|
-
await ensureTandemServer({ url: baseUrl });
|
|
305
319
|
const http = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`));
|
|
306
320
|
const stdio = new StdioServerTransport();
|
|
321
|
+
const pendingIds = /* @__PURE__ */ new Set();
|
|
322
|
+
const preReadyBuffer = [];
|
|
307
323
|
let shuttingDown = false;
|
|
308
|
-
|
|
324
|
+
let httpReady = false;
|
|
325
|
+
async function sendErrorResponse(id, message, detail) {
|
|
326
|
+
const errorResponse = {
|
|
327
|
+
jsonrpc: "2.0",
|
|
328
|
+
id,
|
|
329
|
+
error: {
|
|
330
|
+
// -32000 is the implementation-defined server error range per
|
|
331
|
+
// JSON-RPC 2.0 §5.1 — upstream unavailability is an application-
|
|
332
|
+
// level condition, not a generic Internal Error.
|
|
333
|
+
code: -32e3,
|
|
334
|
+
message,
|
|
335
|
+
...detail !== void 0 ? { data: { detail } } : {}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
try {
|
|
339
|
+
await stdio.send(errorResponse);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
const detail2 = err instanceof Error ? err.message : String(err);
|
|
342
|
+
process.stderr.write(
|
|
343
|
+
`[tandem mcp-stdio] failed to send synthesized error for id ${id}: ${detail2}
|
|
344
|
+
`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function forwardToUpstream(msg) {
|
|
349
|
+
const requestId = getRequestId(msg);
|
|
350
|
+
if (requestId !== void 0) pendingIds.add(requestId);
|
|
351
|
+
http.send(msg).catch((err) => {
|
|
352
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
353
|
+
process.stderr.write(`[tandem mcp-stdio] upstream send failed: ${detail}
|
|
354
|
+
`);
|
|
355
|
+
if (requestId !== void 0 && pendingIds.delete(requestId)) {
|
|
356
|
+
void sendErrorResponse(requestId, "Tandem HTTP upstream unreachable", detail);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async function synthesizeBuffered(message, detail) {
|
|
361
|
+
const buffered2 = preReadyBuffer.splice(0);
|
|
362
|
+
const ids = buffered2.map((msg) => getRequestId(msg)).filter((id) => id !== void 0);
|
|
363
|
+
for (const id of ids) {
|
|
364
|
+
await sendErrorResponse(id, message, detail);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function synthesizePending(message, detail) {
|
|
368
|
+
if (pendingIds.size === 0) return;
|
|
369
|
+
const ids = [...pendingIds];
|
|
370
|
+
pendingIds.clear();
|
|
371
|
+
await Promise.all(ids.map((id) => sendErrorResponse(id, message, detail)));
|
|
372
|
+
}
|
|
373
|
+
const shutdown = async (code = 0, synth) => {
|
|
309
374
|
if (!shuttingDown) {
|
|
310
375
|
shuttingDown = true;
|
|
311
|
-
|
|
376
|
+
if (synth) {
|
|
377
|
+
await synthesizeBuffered(synth.message, synth.detail);
|
|
378
|
+
await synthesizePending(synth.message, synth.detail);
|
|
379
|
+
}
|
|
380
|
+
await http.close().catch((err) => {
|
|
381
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
382
|
+
process.stderr.write(`[tandem mcp-stdio] http.close failed: ${detail}
|
|
383
|
+
`);
|
|
312
384
|
});
|
|
313
|
-
await stdio.close().catch(() => {
|
|
385
|
+
await stdio.close().catch((err) => {
|
|
386
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
387
|
+
process.stderr.write(`[tandem mcp-stdio] stdio.close failed: ${detail}
|
|
388
|
+
`);
|
|
314
389
|
});
|
|
315
390
|
}
|
|
316
391
|
process.exit(code);
|
|
317
392
|
};
|
|
393
|
+
function deferredShutdown(synth) {
|
|
394
|
+
setTimeout(() => void shutdown(1, synth), PREFLIGHT_GRACE_MS);
|
|
395
|
+
}
|
|
318
396
|
stdio.onmessage = (msg) => {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (requestId !== void 0) {
|
|
325
|
-
const errorResponse = {
|
|
326
|
-
jsonrpc: "2.0",
|
|
327
|
-
id: requestId,
|
|
328
|
-
error: {
|
|
329
|
-
// -32000 is the implementation-defined server error range per
|
|
330
|
-
// JSON-RPC 2.0 §5.1 — the upstream being unreachable is an
|
|
331
|
-
// application-level condition, not a generic Internal Error.
|
|
332
|
-
code: -32e3,
|
|
333
|
-
message: "Tandem HTTP upstream unreachable",
|
|
334
|
-
data: { detail }
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
stdio.send(errorResponse).catch(() => {
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
});
|
|
397
|
+
if (!httpReady) {
|
|
398
|
+
preReadyBuffer.push(msg);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
forwardToUpstream(msg);
|
|
341
402
|
};
|
|
342
403
|
http.onmessage = (msg) => {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
404
|
+
const responseId = getResponseId(msg);
|
|
405
|
+
stdio.send(msg).then(
|
|
406
|
+
() => {
|
|
407
|
+
if (responseId !== void 0) pendingIds.delete(responseId);
|
|
408
|
+
},
|
|
409
|
+
(err) => {
|
|
410
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
411
|
+
process.stderr.write(
|
|
412
|
+
`[tandem mcp-stdio] stdio write failed for id ${responseId ?? "<notification>"}: ${detail}
|
|
413
|
+
`
|
|
414
|
+
);
|
|
415
|
+
void shutdown(1, {
|
|
416
|
+
message: "Tandem stdio write failed",
|
|
417
|
+
detail
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
);
|
|
348
421
|
};
|
|
349
422
|
stdio.onerror = (err) => {
|
|
350
423
|
process.stderr.write(`[tandem mcp-stdio] stdio error: ${err.message}
|
|
424
|
+
${err.stack ?? ""}
|
|
351
425
|
`);
|
|
352
426
|
};
|
|
353
427
|
http.onerror = (err) => {
|
|
354
|
-
|
|
355
|
-
|
|
428
|
+
const cause = err.cause;
|
|
429
|
+
process.stderr.write(
|
|
430
|
+
`[tandem mcp-stdio] http error: ${err.message}
|
|
431
|
+
${err.stack ?? ""}${cause !== void 0 ? `
|
|
432
|
+
cause: ${cause}` : ""}
|
|
433
|
+
`
|
|
434
|
+
);
|
|
356
435
|
};
|
|
357
436
|
stdio.onclose = () => {
|
|
358
437
|
void shutdown(0);
|
|
359
438
|
};
|
|
360
439
|
http.onclose = () => {
|
|
361
|
-
|
|
440
|
+
if (shuttingDown) return;
|
|
441
|
+
void shutdown(1, {
|
|
442
|
+
message: "Tandem HTTP upstream closed unexpectedly",
|
|
443
|
+
detail: "upstream connection dropped mid-session"
|
|
444
|
+
});
|
|
362
445
|
};
|
|
363
446
|
await stdio.start();
|
|
447
|
+
const probe = await probeTandemServer({ url: baseUrl });
|
|
448
|
+
if (!probe.ok) {
|
|
449
|
+
const guidance = probe.kind === "unreachable" ? "Start the Tauri app or run `tandem start` on the host, then retry." : "The Tandem server is running but unhealthy \u2014 check the host logs.";
|
|
450
|
+
process.stderr.write(
|
|
451
|
+
`[tandem mcp-stdio] Tandem server preflight failed at ${probe.url} (${probe.reason}).
|
|
452
|
+
[tandem mcp-stdio] ${guidance}
|
|
453
|
+
`
|
|
454
|
+
);
|
|
455
|
+
const synthMessage = probe.kind === "unreachable" ? "Tandem server not running. Start the Tauri app or run `tandem start`." : "Tandem server unhealthy (check host logs).";
|
|
456
|
+
deferredShutdown({ message: synthMessage, detail: probe.reason });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
364
459
|
try {
|
|
365
460
|
await http.start();
|
|
366
461
|
} catch (err) {
|
|
367
462
|
const detail = err instanceof Error ? err.message : String(err);
|
|
368
463
|
process.stderr.write(`[tandem mcp-stdio] upstream http start failed: ${detail}
|
|
369
464
|
`);
|
|
370
|
-
|
|
465
|
+
deferredShutdown({ message: "Tandem HTTP upstream failed to start", detail });
|
|
466
|
+
return;
|
|
371
467
|
}
|
|
468
|
+
httpReady = true;
|
|
469
|
+
const buffered = preReadyBuffer.splice(0);
|
|
470
|
+
for (const msg of buffered) forwardToUpstream(msg);
|
|
372
471
|
}
|
|
373
472
|
function getRequestId(msg) {
|
|
374
473
|
const m = msg;
|
|
@@ -376,12 +475,34 @@ function getRequestId(msg) {
|
|
|
376
475
|
if (typeof m.id === "string" || typeof m.id === "number") return m.id;
|
|
377
476
|
return void 0;
|
|
378
477
|
}
|
|
478
|
+
function getResponseId(msg) {
|
|
479
|
+
const m = msg;
|
|
480
|
+
if (typeof m.method === "string") return void 0;
|
|
481
|
+
if (typeof m.id === "string" || typeof m.id === "number") return m.id;
|
|
482
|
+
return void 0;
|
|
483
|
+
}
|
|
484
|
+
var PREFLIGHT_GRACE_MS;
|
|
379
485
|
var init_mcp_stdio = __esm({
|
|
380
486
|
"src/cli/mcp-stdio.ts"() {
|
|
381
487
|
"use strict";
|
|
382
488
|
init_cli_runtime();
|
|
383
489
|
init_preflight();
|
|
384
490
|
redirectConsoleToStderr();
|
|
491
|
+
PREFLIGHT_GRACE_MS = 1500;
|
|
492
|
+
process.once("uncaughtException", (err) => {
|
|
493
|
+
process.stderr.write(
|
|
494
|
+
`[tandem mcp-stdio] uncaughtException: ${err.message}
|
|
495
|
+
${err.stack ?? ""}
|
|
496
|
+
`
|
|
497
|
+
);
|
|
498
|
+
process.exit(1);
|
|
499
|
+
});
|
|
500
|
+
process.once("unhandledRejection", (reason) => {
|
|
501
|
+
const detail = reason instanceof Error ? reason.message : String(reason);
|
|
502
|
+
process.stderr.write(`[tandem mcp-stdio] unhandledRejection: ${detail}
|
|
503
|
+
`);
|
|
504
|
+
process.exit(1);
|
|
505
|
+
});
|
|
385
506
|
}
|
|
386
507
|
});
|
|
387
508
|
|
|
@@ -905,7 +1026,7 @@ var init_start = __esm({
|
|
|
905
1026
|
|
|
906
1027
|
// src/cli/index.ts
|
|
907
1028
|
import updateNotifier from "update-notifier";
|
|
908
|
-
var version = true ? "0.6.
|
|
1029
|
+
var version = true ? "0.6.3" : "0.0.0-dev";
|
|
909
1030
|
var args = process.argv.slice(2);
|
|
910
1031
|
var isStdioMode = args[0] === "mcp-stdio" || args[0] === "channel";
|
|
911
1032
|
if (!isStdioMode) {
|