kojee-mcp 0.7.5-beta.0 → 0.7.5-beta.1
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/{chunk-526UIEAL.js → chunk-4ATSAH7H.js} +9 -2
- package/dist/{chunk-4E6WMRTM.js → chunk-CPOK642I.js} +39 -12
- package/dist/cli.js +4 -4
- package/dist/{connect-handler-YYBLFRDP.js → connect-handler-NCHC6WPU.js} +1 -1
- package/dist/{doctor-NHUPLM6O.js → doctor-RKLXZR2B.js} +1 -1
- package/dist/{doctor-hermes-7N7O422Y.js → doctor-hermes-WLDI4R6P.js} +140 -9
- package/dist/index.js +1 -1
- package/dist/plugins/hermes/adapter.py +7 -2
- package/dist/{setup-handler-6BDICX5F.js → setup-handler-DA4465VH.js} +1 -1
- package/package.json +1 -1
|
@@ -338,8 +338,15 @@ async function startProxy(config) {
|
|
|
338
338
|
}
|
|
339
339
|
server = createMcpServer(registry, adapter, tandemMembershipCount);
|
|
340
340
|
}
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
const isHeadlessDaemon = !!process.env["KOJEE_WEBHOOK_EXPECTED"];
|
|
342
|
+
if (!isHeadlessDaemon) {
|
|
343
|
+
process.stdin.on("end", () => shutdown("stdin end"));
|
|
344
|
+
process.stdin.on("close", () => shutdown("stdin close"));
|
|
345
|
+
} else {
|
|
346
|
+
console.error(
|
|
347
|
+
"[kojee-mcp] headless daemon (KOJEE_WEBHOOK_EXPECTED=1): stdin EOF ignored; lifecycle owned by the service manager (SIGTERM/SIGINT/SIGHUP)."
|
|
348
|
+
);
|
|
349
|
+
}
|
|
343
350
|
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
344
351
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
345
352
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
@@ -425,7 +425,8 @@ function installBundledPayload(opts) {
|
|
|
425
425
|
import fs5 from "fs";
|
|
426
426
|
import path5 from "path";
|
|
427
427
|
function daemonSourceExecCommand(spec) {
|
|
428
|
-
|
|
428
|
+
const target = spec.nodePath ? `${spec.nodePath} ${spec.binPath}` : spec.binPath;
|
|
429
|
+
return `set -a; . ${spec.envFile}; set +a; exec ${target}`;
|
|
429
430
|
}
|
|
430
431
|
function xmlEscape(value) {
|
|
431
432
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -571,6 +572,7 @@ function installHermes(inp) {
|
|
|
571
572
|
const svc = writeService(inp.platform, {
|
|
572
573
|
serviceName: "kojee-hermes",
|
|
573
574
|
binPath: inp.binPath,
|
|
575
|
+
...inp.nodePath ? { nodePath: inp.nodePath } : {},
|
|
574
576
|
envFile: daemonEnv,
|
|
575
577
|
runtime: "hermes",
|
|
576
578
|
homeDir: inp.homeDir
|
|
@@ -589,15 +591,18 @@ function installHermes(inp) {
|
|
|
589
591
|
// every daemon runtime) on the same URL-present path before this output, so
|
|
590
592
|
// emitting it here too would print the contract twice.
|
|
591
593
|
"",
|
|
592
|
-
"Next:",
|
|
593
|
-
|
|
594
|
-
"
|
|
595
|
-
"
|
|
596
|
-
"
|
|
597
|
-
|
|
598
|
-
"
|
|
599
|
-
"
|
|
600
|
-
|
|
594
|
+
"Next \u2014 ACTIVATE (owner steps; the receiver never binds until you do these, in order):",
|
|
595
|
+
" 1. enable the Tandem receiver plugin (WITHOUT this, the gateway restart loads nothing):",
|
|
596
|
+
" hermes plugins enable kojee-tandem",
|
|
597
|
+
" 2. restart the gateway to bind the receiver (:8645) AND load the kojee MCP tools:",
|
|
598
|
+
" hermes gateway restart (or: systemctl --user restart hermes-gateway)",
|
|
599
|
+
` 3. start the pusher daemon: ${svc.activateCmd}`,
|
|
600
|
+
" 4. verify end-to-end (should read HEALTHY): kojee-mcp doctor --runtime hermes",
|
|
601
|
+
"",
|
|
602
|
+
" Notes:",
|
|
603
|
+
" - the agent joins rooms from its own tools: mcp_kojee_tandem_join <tandem_id>",
|
|
604
|
+
" - if tools don't register: ensure Hermes has the MCP extra",
|
|
605
|
+
' (cd ~/.hermes/hermes-agent && uv pip install -e ".[mcp]") and re-check connect_timeout.'
|
|
601
606
|
];
|
|
602
607
|
return {
|
|
603
608
|
runtime: "hermes",
|
|
@@ -975,7 +980,18 @@ function distDir() {
|
|
|
975
980
|
}
|
|
976
981
|
function resolveBinPath() {
|
|
977
982
|
const entry = process.argv[1];
|
|
978
|
-
|
|
983
|
+
if (!entry || entry.length === 0) return "kojee-mcp";
|
|
984
|
+
try {
|
|
985
|
+
return fs6.realpathSync(entry);
|
|
986
|
+
} catch {
|
|
987
|
+
return entry;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
function resolveDaemonExec() {
|
|
991
|
+
const nodePath = process.execPath;
|
|
992
|
+
const binPath = resolveBinPath();
|
|
993
|
+
const ephemeral = /[\\/]_npx[\\/]/.test(binPath);
|
|
994
|
+
return { nodePath, binPath, ephemeral };
|
|
979
995
|
}
|
|
980
996
|
function configureHermes(opts) {
|
|
981
997
|
const runtime = "hermes";
|
|
@@ -1018,11 +1034,13 @@ function configureHermes(opts) {
|
|
|
1018
1034
|
}
|
|
1019
1035
|
const env = opts.env ?? process.env;
|
|
1020
1036
|
const suppliedSecret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
|
|
1037
|
+
const daemonExec = resolveDaemonExec();
|
|
1021
1038
|
const install = installHermes({
|
|
1022
1039
|
homeDir: home,
|
|
1023
1040
|
payloadBaseDir: base,
|
|
1024
1041
|
platform: process.platform,
|
|
1025
|
-
binPath:
|
|
1042
|
+
binPath: daemonExec.binPath,
|
|
1043
|
+
nodePath: daemonExec.nodePath,
|
|
1026
1044
|
webhookUrl: wh.url,
|
|
1027
1045
|
...suppliedSecret ? { webhookSecret: suppliedSecret } : {},
|
|
1028
1046
|
...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
|
|
@@ -1035,6 +1053,15 @@ function configureHermes(opts) {
|
|
|
1035
1053
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
1036
1054
|
}
|
|
1037
1055
|
recordRuntime(runtime);
|
|
1056
|
+
if (daemonExec.ephemeral) {
|
|
1057
|
+
lines.push(
|
|
1058
|
+
"\u26A0 DURABILITY: this ran from an ephemeral npx cache, so the daemon points at a",
|
|
1059
|
+
" path npx will eventually delete (the daemon would then fail to start).",
|
|
1060
|
+
" For a durable daemon, install globally and re-run init:",
|
|
1061
|
+
" npm i -g kojee-mcp && kojee-mcp init --runtime hermes",
|
|
1062
|
+
""
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1038
1065
|
const envFile = path7.join(home, ".kojee", "hermes.env");
|
|
1039
1066
|
lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
|
|
1040
1067
|
lines.push("");
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-EIAUW6KO.js";
|
|
5
5
|
import {
|
|
6
6
|
startProxy
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4ATSAH7H.js";
|
|
8
8
|
import "./chunk-TCWIXG5C.js";
|
|
9
9
|
import {
|
|
10
10
|
pairedConfigPath
|
|
@@ -48,7 +48,7 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
|
|
|
48
48
|
program.command("connect <code>").description(
|
|
49
49
|
"Connect this runtime to Kojee with a per-agent pair code from the dashboard (claude-code | codex | openclaw | hermes). claude-code/codex/openclaw write a per-runtime paired slot (~/.kojee/agents/<runtime>/config.json) and point the runtime launcher at it via --paired-config; hermes writes the global ~/.kojee/config.json (its daemon + `kojee-mcp send` read that). Idempotent."
|
|
50
50
|
).requiredOption("--runtime <id>", "Target runtime: claude-code | codex | openclaw | hermes").option("--url <url>", "Broker base URL (default: the canonical staging broker)").action(async (code, opts) => {
|
|
51
|
-
const { runConnect } = await import("./connect-handler-
|
|
51
|
+
const { runConnect } = await import("./connect-handler-NCHC6WPU.js");
|
|
52
52
|
const result = await runConnect({
|
|
53
53
|
code,
|
|
54
54
|
runtime: opts.runtime,
|
|
@@ -124,7 +124,7 @@ program.command("tail <path>").description("Stream a file's contents and follow
|
|
|
124
124
|
}
|
|
125
125
|
});
|
|
126
126
|
program.command("doctor").description("Diagnose the kojee wake path (proxy, hook-server, SSE stream, event log, Monitor) and print the exact wake recipe").action(async () => {
|
|
127
|
-
const { runDoctor } = await import("./doctor-
|
|
127
|
+
const { runDoctor } = await import("./doctor-RKLXZR2B.js");
|
|
128
128
|
const code = await runDoctor();
|
|
129
129
|
process.exit(code);
|
|
130
130
|
});
|
|
@@ -143,7 +143,7 @@ function addInstallOptions(cmd, runtimeHelp) {
|
|
|
143
143
|
function makeInstallAction(verb) {
|
|
144
144
|
return async (opts) => {
|
|
145
145
|
const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
|
|
146
|
-
const { runSetup, resolvePairCode } = await import("./setup-handler-
|
|
146
|
+
const { runSetup, resolvePairCode } = await import("./setup-handler-DA4465VH.js");
|
|
147
147
|
const pairCode = resolvePairCode(opts);
|
|
148
148
|
const result = await runSetup({
|
|
149
149
|
verb,
|
|
@@ -365,7 +365,7 @@ async function runDoctor() {
|
|
|
365
365
|
console.error(formatOpenclawDoctorReport(report2));
|
|
366
366
|
return report2.verdict === "broken" ? 1 : 0;
|
|
367
367
|
}
|
|
368
|
-
const { hermesArtifactsPresent, collectHermesDoctorReport, formatHermesDoctorReport } = await import("./doctor-hermes-
|
|
368
|
+
const { hermesArtifactsPresent, collectHermesDoctorReport, formatHermesDoctorReport } = await import("./doctor-hermes-WLDI4R6P.js");
|
|
369
369
|
if (hermesArtifactsPresent()) {
|
|
370
370
|
const hermesReport = collectHermesDoctorReport();
|
|
371
371
|
console.error(formatHermesDoctorReport(hermesReport));
|
|
@@ -103,6 +103,27 @@ function readChildEnv(configText) {
|
|
|
103
103
|
function sha256(value) {
|
|
104
104
|
return crypto.createHash("sha256").update(value, "utf8").digest("hex");
|
|
105
105
|
}
|
|
106
|
+
function readPluginEnabled(configText) {
|
|
107
|
+
if (configText === null) return null;
|
|
108
|
+
let doc;
|
|
109
|
+
try {
|
|
110
|
+
doc = parse(configText);
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (!doc || typeof doc !== "object") return null;
|
|
115
|
+
const plugins = doc["plugins"];
|
|
116
|
+
if (!plugins || typeof plugins !== "object") return false;
|
|
117
|
+
const enabled = plugins["enabled"];
|
|
118
|
+
if (!Array.isArray(enabled)) return false;
|
|
119
|
+
return enabled.includes("kojee-tandem");
|
|
120
|
+
}
|
|
121
|
+
function parseExecTargets(unitText) {
|
|
122
|
+
if (unitText === null) return null;
|
|
123
|
+
const m = unitText.match(/exec\s+([^'"\n<]+)/);
|
|
124
|
+
if (!m) return null;
|
|
125
|
+
return m[1].trim().split(/\s+/).filter((tok) => tok.startsWith("/"));
|
|
126
|
+
}
|
|
106
127
|
function defaultReadText(filePath) {
|
|
107
128
|
try {
|
|
108
129
|
return fs.readFileSync(filePath, "utf8");
|
|
@@ -186,6 +207,7 @@ function collectHermesDoctorReport(deps = {}) {
|
|
|
186
207
|
const homeDir = deps.homeDir ?? os.homedir();
|
|
187
208
|
const platform = deps.platform ?? process.platform;
|
|
188
209
|
const readText = deps.readText ?? defaultReadText;
|
|
210
|
+
const exists = deps.exists ?? ((p) => fs.existsSync(p));
|
|
189
211
|
const probeService = deps.probeService ?? defaultProbeService(platform);
|
|
190
212
|
const probeReceiver = deps.probeReceiver ?? defaultProbeReceiver(platform);
|
|
191
213
|
const readDaemonEnv = deps.readDaemonEnv ?? defaultReadDaemonEnv(platform);
|
|
@@ -200,6 +222,7 @@ function collectHermesDoctorReport(deps = {}) {
|
|
|
200
222
|
const daemonEnv = daemonEnvText !== null ? parseEnvFile(daemonEnvText) : null;
|
|
201
223
|
const adapterEnv = adapterEnvText !== null ? parseEnvFile(adapterEnvText) : null;
|
|
202
224
|
const childEnv = readChildEnv(configText);
|
|
225
|
+
const pluginEnabled = readPluginEnabled(configText);
|
|
203
226
|
const checks = [];
|
|
204
227
|
const collector = new FindingCollector();
|
|
205
228
|
if (daemonEnv === null) {
|
|
@@ -293,6 +316,46 @@ function collectHermesDoctorReport(deps = {}) {
|
|
|
293
316
|
same ? "daemon + adapter secrets match (compared by sha256; values never printed)." : `daemon vs adapter KOJEE_WEBHOOK_SECRET DIFFER (compared by sha256; values never printed) \u2014 every POST is rejected 401. Resync one side: ${WIZARD_RERUN} (writes one secret both sides).`
|
|
294
317
|
);
|
|
295
318
|
}
|
|
319
|
+
const execTargets = parseExecTargets(unitText);
|
|
320
|
+
if (unitText === null) {
|
|
321
|
+
emit(checks, collector, "webhook.daemon-bin", "unknown", `service unit unreadable at ${unitPath}.`);
|
|
322
|
+
} else if (execTargets === null || execTargets.length === 0) {
|
|
323
|
+
emit(
|
|
324
|
+
checks,
|
|
325
|
+
collector,
|
|
326
|
+
"webhook.daemon-bin",
|
|
327
|
+
"warn",
|
|
328
|
+
"could not parse the ExecStart exec target from the unit \u2014 cannot verify the daemon binary."
|
|
329
|
+
);
|
|
330
|
+
} else {
|
|
331
|
+
const missing = execTargets.filter((p) => !exists(p));
|
|
332
|
+
const ephemeral = execTargets.find((p) => /[\\/]_npx[\\/]/.test(p));
|
|
333
|
+
if (missing.length > 0) {
|
|
334
|
+
emit(
|
|
335
|
+
checks,
|
|
336
|
+
collector,
|
|
337
|
+
"webhook.daemon-bin",
|
|
338
|
+
"broken",
|
|
339
|
+
`daemon ExecStart points at ${missing.join(", ")} which does NOT exist \u2014 the service fails to start (exit 127) and crash-loops. Install globally + ${WIZARD_RERUN}: \`npm i -g kojee-mcp && kojee-mcp init --runtime hermes\`.`
|
|
340
|
+
);
|
|
341
|
+
} else if (ephemeral) {
|
|
342
|
+
emit(
|
|
343
|
+
checks,
|
|
344
|
+
collector,
|
|
345
|
+
"webhook.daemon-bin",
|
|
346
|
+
"warn",
|
|
347
|
+
`daemon ExecStart runs from an EPHEMERAL npx cache (${ephemeral}) \u2014 it works now but npx will eventually delete it, then the daemon dies (exit 127). For durability: \`npm i -g kojee-mcp && kojee-mcp init --runtime hermes\`.`
|
|
348
|
+
);
|
|
349
|
+
} else {
|
|
350
|
+
emit(
|
|
351
|
+
checks,
|
|
352
|
+
collector,
|
|
353
|
+
"webhook.daemon-bin",
|
|
354
|
+
"ok",
|
|
355
|
+
`daemon ExecStart targets exist (${execTargets.join(" ")}) \u2014 node + CLI resolve without PATH.`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
296
359
|
const svc = probeService();
|
|
297
360
|
if (svc === null) {
|
|
298
361
|
emit(
|
|
@@ -305,12 +368,38 @@ function collectHermesDoctorReport(deps = {}) {
|
|
|
305
368
|
} else if (svc.active) {
|
|
306
369
|
emit(checks, collector, "webhook.pusher-up", "ok", "kojee-hermes service is active.");
|
|
307
370
|
} else {
|
|
371
|
+
const why = execTargets && execTargets.some((p) => !exists(p)) ? " (likely the missing ExecStart binary above \u2014 exit 127)" : "";
|
|
308
372
|
emit(
|
|
309
373
|
checks,
|
|
310
374
|
collector,
|
|
311
375
|
"webhook.pusher-up",
|
|
312
376
|
"broken",
|
|
313
|
-
|
|
377
|
+
`kojee-hermes service is NOT active${why} \u2014 no wakes are being pushed. Enable+start it (\`systemctl --user enable --now kojee-hermes\`) \u2014 shared infra, so the principal's call.`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
if (pluginEnabled === null) {
|
|
381
|
+
emit(
|
|
382
|
+
checks,
|
|
383
|
+
collector,
|
|
384
|
+
"webhook.plugin-enabled",
|
|
385
|
+
"unknown",
|
|
386
|
+
"could not read plugins.enabled from ~/.hermes/config.yaml."
|
|
387
|
+
);
|
|
388
|
+
} else if (pluginEnabled) {
|
|
389
|
+
emit(
|
|
390
|
+
checks,
|
|
391
|
+
collector,
|
|
392
|
+
"webhook.plugin-enabled",
|
|
393
|
+
"ok",
|
|
394
|
+
"kojee-tandem receiver plugin is enabled in Hermes (plugins.enabled)."
|
|
395
|
+
);
|
|
396
|
+
} else {
|
|
397
|
+
emit(
|
|
398
|
+
checks,
|
|
399
|
+
collector,
|
|
400
|
+
"webhook.plugin-enabled",
|
|
401
|
+
"broken",
|
|
402
|
+
"kojee-tandem receiver plugin is NOT enabled \u2014 the gateway loads no receiver, so wakes never arrive (a gateway restart alone can't fix this). Enable it, then restart the gateway: `hermes plugins enable kojee-tandem && hermes gateway restart`."
|
|
314
403
|
);
|
|
315
404
|
}
|
|
316
405
|
const receiver = probeReceiver(listenHost, Number.parseInt(listenPort, 10));
|
|
@@ -331,12 +420,13 @@ function collectHermesDoctorReport(deps = {}) {
|
|
|
331
420
|
`receiver is listening on ${listenHost}:${listenPort}.`
|
|
332
421
|
);
|
|
333
422
|
} else {
|
|
423
|
+
const fix = pluginEnabled === false ? "the kojee-tandem plugin is DISABLED (see webhook.plugin-enabled) \u2014 enable it, then restart the gateway" : "the Hermes gateway (kojee-tandem plugin) receiver is down \u2014 restart the gateway to load the plugin";
|
|
334
424
|
emit(
|
|
335
425
|
checks,
|
|
336
426
|
collector,
|
|
337
427
|
"webhook.receiver-up",
|
|
338
428
|
"warn",
|
|
339
|
-
`nothing is listening on ${listenHost}:${listenPort} \u2014
|
|
429
|
+
`nothing is listening on ${listenHost}:${listenPort} \u2014 ${fix}.`
|
|
340
430
|
);
|
|
341
431
|
}
|
|
342
432
|
if (childEnv === null) {
|
|
@@ -425,20 +515,61 @@ function collectHermesDoctorReport(deps = {}) {
|
|
|
425
515
|
}
|
|
426
516
|
return { checks, findings: collector.findings, verdict: collector.verdict() };
|
|
427
517
|
}
|
|
518
|
+
var CHECK_GROUP = {
|
|
519
|
+
"webhook.env-format": "Config",
|
|
520
|
+
"webhook.url-receiver-align": "Config",
|
|
521
|
+
"webhook.secret-match": "Config",
|
|
522
|
+
"webhook.no-duplicate": "Config",
|
|
523
|
+
"webhook.instance-align": "Config",
|
|
524
|
+
"webhook.plugin-enabled": "Receiver",
|
|
525
|
+
"webhook.receiver-up": "Receiver",
|
|
526
|
+
"webhook.daemon-bin": "Pusher",
|
|
527
|
+
"webhook.pusher-up": "Pusher",
|
|
528
|
+
"webhook.env-reaches-daemon": "Pusher"
|
|
529
|
+
};
|
|
428
530
|
function formatHermesDoctorReport(report) {
|
|
429
531
|
const mark = (ok) => ok === true ? "\u2713" : ok === "warn" ? "\u26A0" : ok === "unknown" ? "?" : "\u2717";
|
|
532
|
+
const shortName = (name) => name.replace(/^webhook\./, "");
|
|
533
|
+
const nOk = report.checks.filter((c) => c.ok === true).length;
|
|
534
|
+
const nWarn = report.checks.filter((c) => c.ok === "warn").length;
|
|
535
|
+
const nBad = report.checks.filter((c) => c.ok === false).length;
|
|
536
|
+
const nUnknown = report.checks.filter((c) => c.ok === "unknown").length;
|
|
537
|
+
const verdictMark = report.verdict === "healthy" ? "\u2713" : report.verdict === "degraded" ? "\u26A0" : "\u2717";
|
|
430
538
|
const lines = [];
|
|
431
|
-
lines.push(`kojee-mcp doctor
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
539
|
+
lines.push(`kojee-mcp doctor \xB7 hermes wake-path \xB7 ${verdictMark} ${report.verdict.toUpperCase()}`);
|
|
540
|
+
const tally = [
|
|
541
|
+
`${nOk} ok`,
|
|
542
|
+
nWarn ? `${nWarn} warning${nWarn > 1 ? "s" : ""}` : null,
|
|
543
|
+
nBad ? `${nBad} problem${nBad > 1 ? "s" : ""}` : null,
|
|
544
|
+
nUnknown ? `${nUnknown} unknown` : null
|
|
545
|
+
].filter(Boolean).join(", ");
|
|
546
|
+
lines.push(` ${tally}`);
|
|
547
|
+
lines.push(" path: daemon (SSE \u2192 HMAC POST) \u2192 :8645 receiver plugin \u2192 wakes the agent");
|
|
435
548
|
lines.push("");
|
|
436
|
-
for (const
|
|
437
|
-
|
|
549
|
+
for (const group of ["Config", "Receiver", "Pusher"]) {
|
|
550
|
+
const groupChecks = report.checks.filter((c) => CHECK_GROUP[c.name] === group);
|
|
551
|
+
if (groupChecks.length === 0) continue;
|
|
552
|
+
lines.push(` ${group}:`);
|
|
553
|
+
for (const c of groupChecks) {
|
|
554
|
+
lines.push(` ${mark(c.ok)} ${shortName(c.name)}: ${c.detail}`);
|
|
555
|
+
}
|
|
556
|
+
lines.push("");
|
|
557
|
+
}
|
|
558
|
+
const actionable = [
|
|
559
|
+
...report.checks.filter((c) => c.ok === false),
|
|
560
|
+
...report.checks.filter((c) => c.ok === "warn")
|
|
561
|
+
];
|
|
562
|
+
if (actionable.length === 0) {
|
|
563
|
+
lines.push("\u2192 Wake-path is healthy. Nothing to do.");
|
|
564
|
+
} else {
|
|
565
|
+
lines.push("\u2192 Next steps (in order):");
|
|
566
|
+
actionable.forEach((c, i) => {
|
|
567
|
+
lines.push(` ${i + 1}. [${shortName(c.name)}] ${c.detail}`);
|
|
568
|
+
});
|
|
438
569
|
}
|
|
439
570
|
lines.push("");
|
|
440
571
|
lines.push(
|
|
441
|
-
"
|
|
572
|
+
" (read-only. Enabling/restarting the daemon, plugin, or gateway is an owner step \u2014 shared infra needs the principal's word.)"
|
|
442
573
|
);
|
|
443
574
|
return lines.join("\n");
|
|
444
575
|
}
|
package/dist/index.js
CHANGED
|
@@ -154,8 +154,13 @@ class KojeeTandemAdapter(BasePlatformAdapter):
|
|
|
154
154
|
|
|
155
155
|
# ── Connection lifecycle ────────────────────────────────────────────
|
|
156
156
|
|
|
157
|
-
async def connect(self) -> bool:
|
|
158
|
-
"""Start the loopback webhook listener the kojee-mcp daemon POSTs to.
|
|
157
|
+
async def connect(self, *, is_reconnect: bool = False) -> bool:
|
|
158
|
+
"""Start the loopback webhook listener the kojee-mcp daemon POSTs to.
|
|
159
|
+
|
|
160
|
+
``is_reconnect`` is part of the BasePlatformAdapter.connect contract
|
|
161
|
+
(the gateway's reconnection watcher calls ``connect(is_reconnect=True)``);
|
|
162
|
+
this listener is stateless per-connect so the flag is accepted and ignored.
|
|
163
|
+
"""
|
|
159
164
|
if not self.webhook_secret:
|
|
160
165
|
logger.error("kojee-tandem: KOJEE_WEBHOOK_SECRET must be set (signed-only inbound)")
|
|
161
166
|
self._set_fatal_error(
|