metheus-governance-mcp-cli 0.2.58 → 0.2.59

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.
Files changed (2) hide show
  1. package/cli.mjs +167 -15
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import path from "node:path";
6
6
  import process from "node:process";
7
7
  import readline from "node:readline";
8
8
  import { fileURLToPath } from "node:url";
9
- import { spawnSync } from "node:child_process";
9
+ import { spawn, spawnSync } from "node:child_process";
10
10
  import { createHash, randomBytes } from "node:crypto";
11
11
  import http from "node:http";
12
12
  import https from "node:https";
@@ -1363,7 +1363,7 @@ function buildRunnerShellInvocation(command) {
1363
1363
  };
1364
1364
  }
1365
1365
 
1366
- function runLocalAICommand({ command, inputPayload, route, destination }) {
1366
+ async function runLocalAICommand({ command, inputPayload, route, destination }) {
1367
1367
  const invocation = buildRunnerShellInvocation(command);
1368
1368
  const stdinText = `${JSON.stringify(inputPayload, null, 2)}\n`;
1369
1369
  const env = {
@@ -1374,16 +1374,72 @@ function runLocalAICommand({ command, inputPayload, route, destination }) {
1374
1374
  METHEUS_RUNNER_CHAT_ID: String(destination.chatID || "").trim(),
1375
1375
  METHEUS_RUNNER_DESTINATION_LABEL: String(destination.label || "").trim(),
1376
1376
  };
1377
- const result = spawnSync(invocation.file, invocation.args, {
1378
- encoding: "utf8",
1379
- input: stdinText,
1380
- env,
1381
- maxBuffer: 4 * 1024 * 1024,
1377
+ const result = await new Promise((resolve, reject) => {
1378
+ const child = spawn(invocation.file, invocation.args, {
1379
+ env,
1380
+ stdio: ["pipe", "pipe", "pipe"],
1381
+ });
1382
+ let stdoutText = "";
1383
+ let stderrText = "";
1384
+ let stdoutBytes = 0;
1385
+ let stderrBytes = 0;
1386
+ const maxBufferBytes = 4 * 1024 * 1024;
1387
+ let settled = false;
1388
+
1389
+ function finishWithError(error) {
1390
+ if (settled) return;
1391
+ settled = true;
1392
+ try {
1393
+ child.kill();
1394
+ } catch {}
1395
+ reject(error);
1396
+ }
1397
+
1398
+ function appendOutput(chunk, target) {
1399
+ const text = String(chunk || "");
1400
+ if (!text) return;
1401
+ const bytes = Buffer.byteLength(text);
1402
+ if (target === "stdout") {
1403
+ stdoutBytes += bytes;
1404
+ if (stdoutBytes > maxBufferBytes) {
1405
+ finishWithError(new Error("runner command exceeded stdout buffer limit"));
1406
+ return;
1407
+ }
1408
+ stdoutText += text;
1409
+ return;
1410
+ }
1411
+ stderrBytes += bytes;
1412
+ if (stderrBytes > maxBufferBytes) {
1413
+ finishWithError(new Error("runner command exceeded stderr buffer limit"));
1414
+ return;
1415
+ }
1416
+ stderrText += text;
1417
+ }
1418
+
1419
+ child.on("error", (error) => {
1420
+ if (settled) return;
1421
+ settled = true;
1422
+ reject(error);
1423
+ });
1424
+ child.stdout.on("data", (chunk) => appendOutput(chunk, "stdout"));
1425
+ child.stderr.on("data", (chunk) => appendOutput(chunk, "stderr"));
1426
+ child.on("close", (status, signal) => {
1427
+ if (settled) return;
1428
+ settled = true;
1429
+ resolve({
1430
+ status,
1431
+ signal,
1432
+ stdoutText: stdoutText.trim(),
1433
+ stderrText: stderrText.trim(),
1434
+ });
1435
+ });
1436
+ child.stdin.on("error", () => {});
1437
+ child.stdin.end(stdinText);
1382
1438
  });
1383
- const stdoutText = String(result.stdout || "").trim();
1384
- const stderrText = String(result.stderr || "").trim();
1385
- if (result.error) {
1386
- throw new Error(String(result.error?.message || result.error));
1439
+ const stdoutText = String(result.stdoutText || "").trim();
1440
+ const stderrText = String(result.stderrText || "").trim();
1441
+ if (result.signal) {
1442
+ throw new Error(stderrText || stdoutText || `runner command terminated by signal ${result.signal}`);
1387
1443
  }
1388
1444
  if (result.status !== 0) {
1389
1445
  throw new Error(stderrText || stdoutText || `runner command exited with status ${result.status}`);
@@ -1768,12 +1824,41 @@ async function processRunnerRouteOnce(route, runtime, mode) {
1768
1824
  selectedRecord,
1769
1825
  contextWindow,
1770
1826
  });
1771
- const aiResult = runLocalAICommand({
1772
- command: normalizedRoute.command,
1773
- inputPayload: aiPayload,
1774
- route: normalizedRoute,
1827
+ await maybeSendRunnerChatAction({
1828
+ provider: normalizedRoute.provider,
1775
1829
  destination,
1830
+ timeoutSeconds: runtime.timeoutSeconds,
1831
+ action: "typing",
1776
1832
  });
1833
+ let typingRequestInFlight = false;
1834
+ const typingInterval = setInterval(() => {
1835
+ if (typingRequestInFlight) return;
1836
+ typingRequestInFlight = true;
1837
+ maybeSendRunnerChatAction({
1838
+ provider: normalizedRoute.provider,
1839
+ destination,
1840
+ timeoutSeconds: runtime.timeoutSeconds,
1841
+ action: "typing",
1842
+ })
1843
+ .catch(() => {})
1844
+ .finally(() => {
1845
+ typingRequestInFlight = false;
1846
+ });
1847
+ }, 4000);
1848
+ if (typeof typingInterval.unref === "function") {
1849
+ typingInterval.unref();
1850
+ }
1851
+ let aiResult;
1852
+ try {
1853
+ aiResult = await runLocalAICommand({
1854
+ command: normalizedRoute.command,
1855
+ inputPayload: aiPayload,
1856
+ route: normalizedRoute,
1857
+ destination,
1858
+ });
1859
+ } finally {
1860
+ clearInterval(typingInterval);
1861
+ }
1777
1862
  if (aiResult.skip) {
1778
1863
  saveRunnerRouteState(
1779
1864
  routeKey,
@@ -2177,6 +2262,73 @@ async function deliverLocalProviderMessage({
2177
2262
  throw new Error(`${providerEnvConfig(normalizedProvider).label} local delivery is not implemented yet`);
2178
2263
  }
2179
2264
 
2265
+ async function sendLocalProviderChatAction({
2266
+ provider,
2267
+ token,
2268
+ destination,
2269
+ action,
2270
+ timeoutSeconds,
2271
+ }) {
2272
+ const normalizedProvider = normalizeBotProvider(provider);
2273
+ if (normalizedProvider === "telegram") {
2274
+ const requestURL = `https://api.telegram.org/bot${token}/sendChatAction`;
2275
+ const payload = {
2276
+ chat_id: destination.chatID,
2277
+ action: String(action || "typing").trim() || "typing",
2278
+ };
2279
+ const response = await postJSONWithoutAuth(requestURL, timeoutSeconds, payload);
2280
+ const responseJSON = parseJSONText(response.bodyText);
2281
+ return {
2282
+ statusCode: response.statusCode,
2283
+ body: responseJSON || response.bodyText,
2284
+ ok: response.statusCode >= 200 && response.statusCode < 300 && Boolean(responseJSON?.ok ?? true),
2285
+ url: sanitizeTelegramAPIURL(requestURL),
2286
+ };
2287
+ }
2288
+ return {
2289
+ statusCode: 204,
2290
+ body: { ok: true, skipped: true },
2291
+ ok: true,
2292
+ url: "",
2293
+ };
2294
+ }
2295
+
2296
+ async function maybeSendRunnerChatAction({
2297
+ provider,
2298
+ destination,
2299
+ timeoutSeconds,
2300
+ action = "typing",
2301
+ }) {
2302
+ const providerEnv = loadProviderEnvConfig(provider);
2303
+ if (!providerEnv.ok) {
2304
+ return {
2305
+ ok: false,
2306
+ skipped: true,
2307
+ detail: providerEnv.error,
2308
+ };
2309
+ }
2310
+ try {
2311
+ const result = await sendLocalProviderChatAction({
2312
+ provider,
2313
+ token: providerEnv.token,
2314
+ destination,
2315
+ action,
2316
+ timeoutSeconds,
2317
+ });
2318
+ return {
2319
+ ok: Boolean(result.ok),
2320
+ skipped: false,
2321
+ detail: result.ok ? "" : String(safeObject(result.body).description || safeObject(result.body).error || "").trim(),
2322
+ };
2323
+ } catch (err) {
2324
+ return {
2325
+ ok: false,
2326
+ skipped: false,
2327
+ detail: String(err?.message || err),
2328
+ };
2329
+ }
2330
+ }
2331
+
2180
2332
  function resolveWorkspaceDir(rawPath) {
2181
2333
  const input = String(rawPath || "").trim();
2182
2334
  if (input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.58",
3
+ "version": "0.2.59",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [