kojee-mcp 0.7.5-staging.5 → 0.7.5-staging.7
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/README.md +16 -0
- package/dist/{chunk-FJUAMJHU.js → chunk-O4JQY7IF.js} +27 -1
- package/dist/{chunk-4E6WMRTM.js → chunk-RTYRJAJO.js} +42 -17
- package/dist/{chunk-A4IOKD4Z.js → chunk-S35ZIVOL.js} +2 -4
- package/dist/{chunk-6XWTUDWW.js → chunk-T6QANUS2.js} +1 -1
- package/dist/{chunk-526UIEAL.js → chunk-TUDC4GY5.js} +12 -5
- package/dist/{chunk-TCWIXG5C.js → chunk-XYDNRVOU.js} +1 -1
- package/dist/cli.js +16 -14
- package/dist/{codex-prompt-submit-hook-J5PZEJSK.js → codex-prompt-submit-hook-ACDZSHHC.js} +1 -1
- package/dist/{codex-stop-hook-32W4BIOM.js → codex-stop-hook-M3LEWCNP.js} +1 -1
- package/dist/{connect-handler-YYBLFRDP.js → connect-handler-W7EAIU4R.js} +3 -4
- package/dist/{doctor-NHUPLM6O.js → doctor-O7MLJEMO.js} +3 -3
- package/dist/{doctor-codex-PJFWIABF.js → doctor-codex-D4BOWIAA.js} +3 -5
- package/dist/{doctor-hermes-7N7O422Y.js → doctor-hermes-WLDI4R6P.js} +140 -9
- package/dist/index.js +4 -5
- package/dist/plugins/hermes/adapter.py +7 -2
- package/dist/{registry-DKWVVXJM.js → registry-ACVPVDCS.js} +6 -7
- package/dist/{server-DU3LFS32.js → server-RK4UK6SP.js} +2 -3
- package/dist/{setup-handler-6BDICX5F.js → setup-handler-6T7Q6DYQ.js} +3 -4
- package/dist/{stop-hook-4MNOSDCB.js → stop-hook-Y34L3RCR.js} +1 -1
- package/dist/{tail-stream-G4WGTXDS.js → tail-stream-HG4BKJOG.js} +105 -5
- package/package.json +1 -1
- package/dist/chunk-5DHIUN73.js +0 -25
package/README.md
CHANGED
|
@@ -574,6 +574,22 @@ npm run dev:stub
|
|
|
574
574
|
# Stub listens on http://localhost:8765
|
|
575
575
|
```
|
|
576
576
|
|
|
577
|
+
## Releases & staging versioning
|
|
578
|
+
|
|
579
|
+
Two channels, and one rule that keeps them clean:
|
|
580
|
+
|
|
581
|
+
- **`latest` (production)** — a **manual** release off `main` (`npm publish` → `latest`, or promote a verified staging build via `npm dist-tag add kojee-mcp@<ver> latest`).
|
|
582
|
+
- **`staging` (pre-release)** — **automatic** on every merge to the `staging` branch, via `.github/workflows/publish-staging.yml`, published under the `staging` dist-tag (never touches `latest`).
|
|
583
|
+
|
|
584
|
+
**How the staging version is computed** (`scripts/next-staging-version.mjs`):
|
|
585
|
+
|
|
586
|
+
1. **base** = `package.json.version` with its **PATCH bumped once** — e.g. `0.7.4` → `0.7.5`.
|
|
587
|
+
2. **counter** = `1 + highest existing <base>-staging.N on npm`. This is **per-base**, so it **RESETS to 1 automatically when the base progresses** (a brand-new base has no prior `-staging.N`).
|
|
588
|
+
|
|
589
|
+
So a run yields e.g. `0.7.5-staging.6`, and once `0.7.5` ships to `latest` and `package.json` moves to `0.7.5`, the next staging build resets to `0.7.6-staging.1`.
|
|
590
|
+
|
|
591
|
+
> ⚠️ **THE RULE: `package.json.version` must hold the LAST STABLE version — never a `-staging`/`-beta` pre-release.** The CI bumps the patch itself; if the file already carries the *next* version (or a `-beta` whose stable part is the next version), the base **double-bumps**. That is exactly how a `0.7.5-beta.1` base once published `0.7.6-staging.6` instead of the intended `0.7.5-staging.6`. Cutting a stable release is the *only* time `package.json.version` advances, and it advances to the version you just shipped to `latest`.
|
|
592
|
+
|
|
577
593
|
## How Approvals Work
|
|
578
594
|
|
|
579
595
|
Some tools are governed by approval policies configured in the Kojee dashboard. When an agent calls a governed tool:
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
+
// src/version.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
6
|
+
function resolveVersion() {
|
|
7
|
+
try {
|
|
8
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const parsed = JSON.parse(
|
|
10
|
+
fs.readFileSync(path.join(here, "..", "package.json"), "utf8")
|
|
11
|
+
);
|
|
12
|
+
return typeof parsed?.version === "string" && parsed.version ? parsed.version : FALLBACK_VERSION;
|
|
13
|
+
} catch (err) {
|
|
14
|
+
process.stderr.write(
|
|
15
|
+
`kojee-mcp: could not resolve version from package.json, falling back to ${FALLBACK_VERSION}: ${String(err)}
|
|
16
|
+
`
|
|
17
|
+
);
|
|
18
|
+
return FALLBACK_VERSION;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
var VERSION = resolveVersion();
|
|
22
|
+
|
|
1
23
|
// src/tandem/recipe.ts
|
|
2
24
|
var SEND_BODY_PARAM = "body";
|
|
25
|
+
function monitorPackageSpec() {
|
|
26
|
+
return VERSION && VERSION !== "0.0.0-unknown" ? `kojee-mcp@${VERSION}` : "kojee-mcp";
|
|
27
|
+
}
|
|
3
28
|
function buildMonitorCommand(logPath) {
|
|
4
|
-
return `npx
|
|
29
|
+
return `npx ${monitorPackageSpec()} tail --wake-only "${logPath}"`;
|
|
5
30
|
}
|
|
6
31
|
function buildMonitorSpawn(logPath) {
|
|
7
32
|
return `Monitor(command=\`${buildMonitorCommand(logPath)}\`, persistent=true, description="kojee Tandem events")`;
|
|
@@ -32,6 +57,7 @@ var CODEX_LISTEN_CAP_MS = 8e3;
|
|
|
32
57
|
var CODEX_WAKE_BELL = "[kojee] Tandem events may be pending. Call tandem_pending now and drain each room it lists (tandem_messages(id, since=cursor)), then reply in the room with a normal message (kind=message \u2014 never status). If it returns none for you, ignore this.";
|
|
33
58
|
|
|
34
59
|
export {
|
|
60
|
+
VERSION,
|
|
35
61
|
buildMonitorSpawn,
|
|
36
62
|
buildReplyRecipe,
|
|
37
63
|
buildCatchUpNote,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
isPlaceholderWebhookUrl,
|
|
6
6
|
removeCodexConfig,
|
|
7
7
|
writeCodexConfig
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-T6QANUS2.js";
|
|
9
9
|
import {
|
|
10
10
|
removeOpenclawMcpServer,
|
|
11
11
|
writeOpenclawMcpConfig
|
|
@@ -48,14 +48,12 @@ import {
|
|
|
48
48
|
secureFile,
|
|
49
49
|
unlinkSyncRetry
|
|
50
50
|
} from "./chunk-U5HHHRXA.js";
|
|
51
|
-
import {
|
|
52
|
-
VERSION
|
|
53
|
-
} from "./chunk-5DHIUN73.js";
|
|
54
51
|
import {
|
|
55
52
|
CODEX_LISTEN_CAP_MS,
|
|
53
|
+
VERSION,
|
|
56
54
|
buildCondensedTandemRules,
|
|
57
55
|
buildWebhookReceiverNote
|
|
58
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-O4JQY7IF.js";
|
|
59
57
|
|
|
60
58
|
// src/wizard/pair-slot.ts
|
|
61
59
|
import fs from "fs";
|
|
@@ -425,7 +423,8 @@ function installBundledPayload(opts) {
|
|
|
425
423
|
import fs5 from "fs";
|
|
426
424
|
import path5 from "path";
|
|
427
425
|
function daemonSourceExecCommand(spec) {
|
|
428
|
-
|
|
426
|
+
const target = spec.nodePath ? `${spec.nodePath} ${spec.binPath}` : spec.binPath;
|
|
427
|
+
return `set -a; . ${spec.envFile}; set +a; exec ${target}`;
|
|
429
428
|
}
|
|
430
429
|
function xmlEscape(value) {
|
|
431
430
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -571,6 +570,7 @@ function installHermes(inp) {
|
|
|
571
570
|
const svc = writeService(inp.platform, {
|
|
572
571
|
serviceName: "kojee-hermes",
|
|
573
572
|
binPath: inp.binPath,
|
|
573
|
+
...inp.nodePath ? { nodePath: inp.nodePath } : {},
|
|
574
574
|
envFile: daemonEnv,
|
|
575
575
|
runtime: "hermes",
|
|
576
576
|
homeDir: inp.homeDir
|
|
@@ -589,15 +589,18 @@ function installHermes(inp) {
|
|
|
589
589
|
// every daemon runtime) on the same URL-present path before this output, so
|
|
590
590
|
// emitting it here too would print the contract twice.
|
|
591
591
|
"",
|
|
592
|
-
"Next:",
|
|
593
|
-
|
|
594
|
-
"
|
|
595
|
-
"
|
|
596
|
-
"
|
|
597
|
-
|
|
598
|
-
"
|
|
599
|
-
"
|
|
600
|
-
|
|
592
|
+
"Next \u2014 ACTIVATE (owner steps; the receiver never binds until you do these, in order):",
|
|
593
|
+
" 1. enable the Tandem receiver plugin (WITHOUT this, the gateway restart loads nothing):",
|
|
594
|
+
" hermes plugins enable kojee-tandem",
|
|
595
|
+
" 2. restart the gateway to bind the receiver (:8645) AND load the kojee MCP tools:",
|
|
596
|
+
" hermes gateway restart (or: systemctl --user restart hermes-gateway)",
|
|
597
|
+
` 3. start the pusher daemon: ${svc.activateCmd}`,
|
|
598
|
+
" 4. verify end-to-end (should read HEALTHY): kojee-mcp doctor --runtime hermes",
|
|
599
|
+
"",
|
|
600
|
+
" Notes:",
|
|
601
|
+
" - the agent joins rooms from its own tools: mcp_kojee_tandem_join <tandem_id>",
|
|
602
|
+
" - if tools don't register: ensure Hermes has the MCP extra",
|
|
603
|
+
' (cd ~/.hermes/hermes-agent && uv pip install -e ".[mcp]") and re-check connect_timeout.'
|
|
601
604
|
];
|
|
602
605
|
return {
|
|
603
606
|
runtime: "hermes",
|
|
@@ -975,7 +978,18 @@ function distDir() {
|
|
|
975
978
|
}
|
|
976
979
|
function resolveBinPath() {
|
|
977
980
|
const entry = process.argv[1];
|
|
978
|
-
|
|
981
|
+
if (!entry || entry.length === 0) return "kojee-mcp";
|
|
982
|
+
try {
|
|
983
|
+
return fs6.realpathSync(entry);
|
|
984
|
+
} catch {
|
|
985
|
+
return entry;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
function resolveDaemonExec() {
|
|
989
|
+
const nodePath = process.execPath;
|
|
990
|
+
const binPath = resolveBinPath();
|
|
991
|
+
const ephemeral = /[\\/]_npx[\\/]/.test(binPath);
|
|
992
|
+
return { nodePath, binPath, ephemeral };
|
|
979
993
|
}
|
|
980
994
|
function configureHermes(opts) {
|
|
981
995
|
const runtime = "hermes";
|
|
@@ -1018,11 +1032,13 @@ function configureHermes(opts) {
|
|
|
1018
1032
|
}
|
|
1019
1033
|
const env = opts.env ?? process.env;
|
|
1020
1034
|
const suppliedSecret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
|
|
1035
|
+
const daemonExec = resolveDaemonExec();
|
|
1021
1036
|
const install = installHermes({
|
|
1022
1037
|
homeDir: home,
|
|
1023
1038
|
payloadBaseDir: base,
|
|
1024
1039
|
platform: process.platform,
|
|
1025
|
-
binPath:
|
|
1040
|
+
binPath: daemonExec.binPath,
|
|
1041
|
+
nodePath: daemonExec.nodePath,
|
|
1026
1042
|
webhookUrl: wh.url,
|
|
1027
1043
|
...suppliedSecret ? { webhookSecret: suppliedSecret } : {},
|
|
1028
1044
|
...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
|
|
@@ -1035,6 +1051,15 @@ function configureHermes(opts) {
|
|
|
1035
1051
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
1036
1052
|
}
|
|
1037
1053
|
recordRuntime(runtime);
|
|
1054
|
+
if (daemonExec.ephemeral) {
|
|
1055
|
+
lines.push(
|
|
1056
|
+
"\u26A0 DURABILITY: this ran from an ephemeral npx cache, so the daemon points at a",
|
|
1057
|
+
" path npx will eventually delete (the daemon would then fail to start).",
|
|
1058
|
+
" For a durable daemon, install globally and re-run init:",
|
|
1059
|
+
" npm i -g kojee-mcp && kojee-mcp init --runtime hermes",
|
|
1060
|
+
""
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1038
1063
|
const envFile = path7.join(home, ".kojee", "hermes.env");
|
|
1039
1064
|
lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
|
|
1040
1065
|
lines.push("");
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
VERSION
|
|
3
|
-
} from "./chunk-5DHIUN73.js";
|
|
4
|
-
import {
|
|
2
|
+
VERSION,
|
|
5
3
|
buildCatchUpNote,
|
|
6
4
|
buildCondensedTandemRules,
|
|
7
5
|
buildMonitorSpawn,
|
|
8
6
|
buildReplyRecipe
|
|
9
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-O4JQY7IF.js";
|
|
10
8
|
import {
|
|
11
9
|
translateToolCallResult
|
|
12
10
|
} from "./chunk-PPTKGWFF.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
claudeCodeAdapter
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XYDNRVOU.js";
|
|
4
4
|
import {
|
|
5
5
|
GatewayClient,
|
|
6
6
|
applyStableSessionId
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
createMcpServer,
|
|
13
13
|
startMcpServer
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-S35ZIVOL.js";
|
|
15
15
|
import {
|
|
16
16
|
findClaudeAncestorPid
|
|
17
17
|
} from "./chunk-XJEBJIQE.js";
|
|
@@ -295,7 +295,7 @@ async function startProxy(config) {
|
|
|
295
295
|
}
|
|
296
296
|
console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
|
|
297
297
|
let server;
|
|
298
|
-
const { selectDelivery } = await import("./registry-
|
|
298
|
+
const { selectDelivery } = await import("./registry-ACVPVDCS.js");
|
|
299
299
|
const delivery = selectDelivery(adapter.runtime, {
|
|
300
300
|
supportsChannels: adapter.supportsChannels,
|
|
301
301
|
// Per-window delivered mirror for the tandem_pending tool (codex only —
|
|
@@ -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"));
|
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
} from "./chunk-EIAUW6KO.js";
|
|
5
5
|
import {
|
|
6
6
|
startProxy
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-TUDC4GY5.js";
|
|
8
|
+
import "./chunk-XYDNRVOU.js";
|
|
9
9
|
import {
|
|
10
10
|
pairedConfigPath
|
|
11
11
|
} from "./chunk-5SZHXYPK.js";
|
|
@@ -18,11 +18,10 @@ import {
|
|
|
18
18
|
deriveKeystorePath
|
|
19
19
|
} from "./chunk-6G6YYST6.js";
|
|
20
20
|
import "./chunk-U5HHHRXA.js";
|
|
21
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-S35ZIVOL.js";
|
|
22
22
|
import {
|
|
23
23
|
VERSION
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-FJUAMJHU.js";
|
|
24
|
+
} from "./chunk-O4JQY7IF.js";
|
|
26
25
|
import "./chunk-PPTKGWFF.js";
|
|
27
26
|
import "./chunk-XJEBJIQE.js";
|
|
28
27
|
import "./chunk-SRLD2UY7.js";
|
|
@@ -48,7 +47,7 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
|
|
|
48
47
|
program.command("connect <code>").description(
|
|
49
48
|
"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
49
|
).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-
|
|
50
|
+
const { runConnect } = await import("./connect-handler-W7EAIU4R.js");
|
|
52
51
|
const result = await runConnect({
|
|
53
52
|
code,
|
|
54
53
|
runtime: opts.runtime,
|
|
@@ -63,7 +62,7 @@ program.command("hook").description("Run a kojee MCP hook script (called by Clau
|
|
|
63
62
|
"Hook type: stop, user-prompt-submit, codex-stop, or codex-prompt-submit"
|
|
64
63
|
).action(async (opts) => {
|
|
65
64
|
if (opts.type === "stop") {
|
|
66
|
-
const { runStopHook } = await import("./stop-hook-
|
|
65
|
+
const { runStopHook } = await import("./stop-hook-Y34L3RCR.js");
|
|
67
66
|
await runStopHook();
|
|
68
67
|
process.exit(0);
|
|
69
68
|
} else if (opts.type === "user-prompt-submit") {
|
|
@@ -71,11 +70,11 @@ program.command("hook").description("Run a kojee MCP hook script (called by Clau
|
|
|
71
70
|
await runUserPromptSubmitHook();
|
|
72
71
|
process.exit(0);
|
|
73
72
|
} else if (opts.type === "codex-stop") {
|
|
74
|
-
const { runCodexStopHook } = await import("./codex-stop-hook-
|
|
73
|
+
const { runCodexStopHook } = await import("./codex-stop-hook-M3LEWCNP.js");
|
|
75
74
|
await runCodexStopHook();
|
|
76
75
|
process.exit(0);
|
|
77
76
|
} else if (opts.type === "codex-prompt-submit") {
|
|
78
|
-
const { runCodexPromptSubmitHook } = await import("./codex-prompt-submit-hook-
|
|
77
|
+
const { runCodexPromptSubmitHook } = await import("./codex-prompt-submit-hook-ACDZSHHC.js");
|
|
79
78
|
await runCodexPromptSubmitHook();
|
|
80
79
|
process.exit(0);
|
|
81
80
|
} else {
|
|
@@ -114,17 +113,20 @@ program.command("send <tandem_id>").description(
|
|
|
114
113
|
process.stdout.write(JSON.stringify(envelope) + "\n");
|
|
115
114
|
process.exit(exitCode);
|
|
116
115
|
});
|
|
117
|
-
program.command("tail <path>").description("Stream a file's contents and follow appends (portable replacement for `tail -F`)").
|
|
118
|
-
|
|
116
|
+
program.command("tail <path>").description("Stream a file's contents and follow appends (portable replacement for `tail -F`)").option(
|
|
117
|
+
"--wake-only",
|
|
118
|
+
"Suppress routine heartbeat warn/recover telemetry; surface only a sustained stream outage"
|
|
119
|
+
).action(async (filePath, opts) => {
|
|
120
|
+
const { runTail } = await import("./tail-stream-HG4BKJOG.js");
|
|
119
121
|
try {
|
|
120
|
-
await runTail(filePath);
|
|
122
|
+
await runTail(filePath, { wakeOnly: opts.wakeOnly === true });
|
|
121
123
|
} catch (err) {
|
|
122
124
|
console.error("[kojee-mcp tail] Error:", err.message);
|
|
123
125
|
process.exit(1);
|
|
124
126
|
}
|
|
125
127
|
});
|
|
126
128
|
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-
|
|
129
|
+
const { runDoctor } = await import("./doctor-O7MLJEMO.js");
|
|
128
130
|
const code = await runDoctor();
|
|
129
131
|
process.exit(code);
|
|
130
132
|
});
|
|
@@ -143,7 +145,7 @@ function addInstallOptions(cmd, runtimeHelp) {
|
|
|
143
145
|
function makeInstallAction(verb) {
|
|
144
146
|
return async (opts) => {
|
|
145
147
|
const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
|
|
146
|
-
const { runSetup, resolvePairCode } = await import("./setup-handler-
|
|
148
|
+
const { runSetup, resolvePairCode } = await import("./setup-handler-6T7Q6DYQ.js");
|
|
147
149
|
const pairCode = resolvePairCode(opts);
|
|
148
150
|
const result = await runSetup({
|
|
149
151
|
verb,
|
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
DEFAULT_BROKER_URL,
|
|
3
3
|
reconcileConnectPairSlot,
|
|
4
4
|
runWizard
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-RTYRJAJO.js";
|
|
6
|
+
import "./chunk-T6QANUS2.js";
|
|
7
7
|
import "./chunk-E6WMFMM2.js";
|
|
8
8
|
import "./chunk-GEGUWQYT.js";
|
|
9
9
|
import "./chunk-77HWBSRH.js";
|
|
@@ -30,8 +30,7 @@ import {
|
|
|
30
30
|
deriveKeystorePath
|
|
31
31
|
} from "./chunk-6G6YYST6.js";
|
|
32
32
|
import "./chunk-U5HHHRXA.js";
|
|
33
|
-
import "./chunk-
|
|
34
|
-
import "./chunk-FJUAMJHU.js";
|
|
33
|
+
import "./chunk-O4JQY7IF.js";
|
|
35
34
|
|
|
36
35
|
// src/wizard/connect-handler.ts
|
|
37
36
|
import os from "os";
|
|
@@ -18,7 +18,7 @@ import "./chunk-U5HHHRXA.js";
|
|
|
18
18
|
import {
|
|
19
19
|
buildMonitorSpawn,
|
|
20
20
|
buildReplyRecipe
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-O4JQY7IF.js";
|
|
22
22
|
import {
|
|
23
23
|
deriveDiscoveryKey,
|
|
24
24
|
findClaudeAncestorPid
|
|
@@ -354,7 +354,7 @@ function formatDoctorReport(report) {
|
|
|
354
354
|
async function runDoctor() {
|
|
355
355
|
const { readRecordedRuntime } = await import("./runtime-record-OXRLTOLC.js");
|
|
356
356
|
if (readRecordedRuntime() === "codex") {
|
|
357
|
-
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-
|
|
357
|
+
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-D4BOWIAA.js");
|
|
358
358
|
const report2 = collectCodexDoctorReport();
|
|
359
359
|
console.error(formatCodexDoctorReport(report2));
|
|
360
360
|
return report2.verdict === "broken" ? 1 : 0;
|
|
@@ -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));
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
defaultCodexConfigPath,
|
|
3
3
|
defaultCodexHooksPath,
|
|
4
4
|
isPlaceholderWebhookUrl
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-T6QANUS2.js";
|
|
6
6
|
import "./chunk-TMCNB4JH.js";
|
|
7
7
|
import "./chunk-D6JKFJ6A.js";
|
|
8
8
|
import {
|
|
@@ -16,11 +16,9 @@ import {
|
|
|
16
16
|
} from "./chunk-V5VZPYMZ.js";
|
|
17
17
|
import "./chunk-U5HHHRXA.js";
|
|
18
18
|
import {
|
|
19
|
+
CODEX_LISTEN_CAP_MS,
|
|
19
20
|
VERSION
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import {
|
|
22
|
-
CODEX_LISTEN_CAP_MS
|
|
23
|
-
} from "./chunk-FJUAMJHU.js";
|
|
21
|
+
} from "./chunk-O4JQY7IF.js";
|
|
24
22
|
|
|
25
23
|
// src/doctor-codex.ts
|
|
26
24
|
import fs from "fs";
|
|
@@ -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
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
listTandemIds,
|
|
3
3
|
startProxy
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-TUDC4GY5.js";
|
|
5
|
+
import "./chunk-XYDNRVOU.js";
|
|
6
6
|
import "./chunk-TBVJOXIR.js";
|
|
7
7
|
import "./chunk-Z5LPNJQ6.js";
|
|
8
8
|
import "./chunk-I67C2HYA.js";
|
|
9
9
|
import "./chunk-MIEI4PLB.js";
|
|
10
10
|
import "./chunk-6G6YYST6.js";
|
|
11
11
|
import "./chunk-U5HHHRXA.js";
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-FJUAMJHU.js";
|
|
12
|
+
import "./chunk-S35ZIVOL.js";
|
|
13
|
+
import "./chunk-O4JQY7IF.js";
|
|
15
14
|
import "./chunk-PPTKGWFF.js";
|
|
16
15
|
import "./chunk-XJEBJIQE.js";
|
|
17
16
|
import "./chunk-SRLD2UY7.js";
|
|
@@ -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(
|
|
@@ -13,12 +13,11 @@ import {
|
|
|
13
13
|
} from "./chunk-IZN7IZPW.js";
|
|
14
14
|
import {
|
|
15
15
|
claudeCodeAdapter
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-XYDNRVOU.js";
|
|
17
|
+
import "./chunk-S35ZIVOL.js";
|
|
18
18
|
import {
|
|
19
19
|
VERSION
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-FJUAMJHU.js";
|
|
20
|
+
} from "./chunk-O4JQY7IF.js";
|
|
22
21
|
import "./chunk-PPTKGWFF.js";
|
|
23
22
|
|
|
24
23
|
// src/delivery/lib/fanout.ts
|
|
@@ -127,7 +126,7 @@ function createClaudeCodeDelivery() {
|
|
|
127
126
|
const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
|
|
128
127
|
const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
|
|
129
128
|
const { startEventStream } = await import("./event-stream-ATKYXDBV.js");
|
|
130
|
-
const { createMcpServer } = await import("./server-
|
|
129
|
+
const { createMcpServer } = await import("./server-RK4UK6SP.js");
|
|
131
130
|
const { deriveDiscoveryKey } = await import("./ancestry-A2F5KQ6A.js");
|
|
132
131
|
const { resolveSharedSessionId } = await import("./cc-session-id-NNCZCHZZ.js");
|
|
133
132
|
sweepStaleDiscovery();
|
|
@@ -324,7 +323,7 @@ function createWebhookDelivery(name, pendingLedger) {
|
|
|
324
323
|
const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
|
|
325
324
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
326
325
|
const { startEventStream } = await import("./event-stream-ATKYXDBV.js");
|
|
327
|
-
const { createMcpServer } = await import("./server-
|
|
326
|
+
const { createMcpServer } = await import("./server-RK4UK6SP.js");
|
|
328
327
|
sweepStaleEventLogs();
|
|
329
328
|
eventLog = startEventLog({
|
|
330
329
|
key: ctx.instanceKey,
|
|
@@ -518,7 +517,7 @@ function createOpenclawDelivery(deps = {}) {
|
|
|
518
517
|
const { startEventLog, sweepStaleEventLogs } = await import("./event-log-2NBJEIEP.js");
|
|
519
518
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
520
519
|
const { startEventStream } = await import("./event-stream-ATKYXDBV.js");
|
|
521
|
-
const { createMcpServer } = await import("./server-
|
|
520
|
+
const { createMcpServer } = await import("./server-RK4UK6SP.js");
|
|
522
521
|
sweepStaleEventLogs();
|
|
523
522
|
eventLog = startEventLog({
|
|
524
523
|
key: ctx.instanceKey,
|
|
@@ -5,9 +5,8 @@ import {
|
|
|
5
5
|
executeToolCall,
|
|
6
6
|
extractDrainCursor,
|
|
7
7
|
startMcpServer
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-FJUAMJHU.js";
|
|
8
|
+
} from "./chunk-S35ZIVOL.js";
|
|
9
|
+
import "./chunk-O4JQY7IF.js";
|
|
11
10
|
import "./chunk-PPTKGWFF.js";
|
|
12
11
|
export {
|
|
13
12
|
buildChannelInstructions,
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
pairSlotFor,
|
|
4
4
|
reconcileConnectPairSlot,
|
|
5
5
|
runWizard
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-RTYRJAJO.js";
|
|
7
|
+
import "./chunk-T6QANUS2.js";
|
|
8
8
|
import "./chunk-E6WMFMM2.js";
|
|
9
9
|
import "./chunk-GEGUWQYT.js";
|
|
10
10
|
import {
|
|
@@ -26,8 +26,7 @@ import "./chunk-I67C2HYA.js";
|
|
|
26
26
|
import "./chunk-MIEI4PLB.js";
|
|
27
27
|
import "./chunk-6G6YYST6.js";
|
|
28
28
|
import "./chunk-U5HHHRXA.js";
|
|
29
|
-
import "./chunk-
|
|
30
|
-
import "./chunk-FJUAMJHU.js";
|
|
29
|
+
import "./chunk-O4JQY7IF.js";
|
|
31
30
|
|
|
32
31
|
// src/wizard/setup-handler.ts
|
|
33
32
|
var SETUP_SUPPORTED_RUNTIMES = ["claude-code", "codex"];
|
|
@@ -16,6 +16,7 @@ import "./chunk-U5HHHRXA.js";
|
|
|
16
16
|
|
|
17
17
|
// src/tail-stream.ts
|
|
18
18
|
import fs from "fs";
|
|
19
|
+
import { spawn } from "child_process";
|
|
19
20
|
var STATUS_LINE_RE = new RegExp(`^\\[[^\\]]*\\] ${STATUS_LINE_PREFIX} (.*)$`);
|
|
20
21
|
var HEARTBEAT_FIELDS_RE = /(^|\s)status=heartbeat(\s|$)/;
|
|
21
22
|
var ROTATED_FIELDS_RE = /(^|\s)status=rotated(\s|$)/;
|
|
@@ -29,6 +30,7 @@ var TAIL_STALE_FLOOR_MS = envInt(
|
|
|
29
30
|
);
|
|
30
31
|
var STALE_CHECK_INTERVAL_MS = envInt("KOJEE_TAIL_CHECK_MS", 5e3);
|
|
31
32
|
var TAIL_REWARN_AFTER_MS = 30 * 6e4;
|
|
33
|
+
var SUSTAINED_STALE_MS = envInt("KOJEE_TAIL_SUSTAINED_MS", TAIL_REWARN_AFTER_MS);
|
|
32
34
|
var TAIL_HEALTHY_RESET_MS = 30 * 6e4;
|
|
33
35
|
var TAIL_BACKOFF_CAP_CYCLES = 3;
|
|
34
36
|
function envInt(name, fallback) {
|
|
@@ -99,9 +101,13 @@ function createStallDetector(startNow, options = {}) {
|
|
|
99
101
|
}
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
|
-
async function runTail(messagesPath) {
|
|
104
|
+
async function runTail(messagesPath, opts = {}) {
|
|
105
|
+
const wakeOnly = opts.wakeOnly === true;
|
|
103
106
|
const statusPath = statusLogPath(messagesPath);
|
|
104
|
-
const detector = createStallDetector(Date.now(), {
|
|
107
|
+
const detector = createStallDetector(Date.now(), {
|
|
108
|
+
floorMs: TAIL_STALE_FLOOR_MS,
|
|
109
|
+
rewarnAfterMs: SUSTAINED_STALE_MS
|
|
110
|
+
});
|
|
105
111
|
const heartbeatPath = monitorHeartbeatPath(messagesPath);
|
|
106
112
|
const touchHeartbeat = () => {
|
|
107
113
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -116,7 +122,7 @@ async function runTail(messagesPath) {
|
|
|
116
122
|
};
|
|
117
123
|
let lastCursor = null;
|
|
118
124
|
function onMessageLine(line) {
|
|
119
|
-
if (detector.onLife(Date.now())?.kind === "recovered") {
|
|
125
|
+
if (detector.onLife(Date.now())?.kind === "recovered" && !wakeOnly) {
|
|
120
126
|
process.stdout.write(
|
|
121
127
|
"[kojee] stream recovered \u2014 events flowing again (recovery is automatic; no action needed).\n"
|
|
122
128
|
);
|
|
@@ -133,7 +139,7 @@ async function runTail(messagesPath) {
|
|
|
133
139
|
if (HEARTBEAT_FIELDS_RE.test(fields)) {
|
|
134
140
|
detector.onHeartbeat(now);
|
|
135
141
|
}
|
|
136
|
-
if (detector.onLife(now)?.kind === "recovered") {
|
|
142
|
+
if (detector.onLife(now)?.kind === "recovered" && !wakeOnly) {
|
|
137
143
|
process.stdout.write(
|
|
138
144
|
"[kojee] stream recovered \u2014 heartbeats resumed (the stall was transient, likely idle-period timing or brief host load; no action needed).\n"
|
|
139
145
|
);
|
|
@@ -149,7 +155,12 @@ async function runTail(messagesPath) {
|
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
const followers = [
|
|
152
|
-
|
|
158
|
+
// MESSAGES (wake-critical) — prefer the OS `tail -F` follower where the
|
|
159
|
+
// platform has one; it delegates file-following to the battle-tested system
|
|
160
|
+
// binary instead of the Node fs.watch/poll tailer. STATUS (telemetry) stays
|
|
161
|
+
// on the Node follower: its startup-backlog seeding boundary (statusSeeded)
|
|
162
|
+
// is clean to detect only when we control the drain. See makeMessagesFollower.
|
|
163
|
+
makeMessagesFollower(messagesPath, onMessageLine, touchHeartbeat),
|
|
153
164
|
makeFollower(statusPath, onStatusLine, touchHeartbeat)
|
|
154
165
|
];
|
|
155
166
|
void Promise.all([
|
|
@@ -171,6 +182,7 @@ async function runTail(messagesPath) {
|
|
|
171
182
|
);
|
|
172
183
|
return;
|
|
173
184
|
}
|
|
185
|
+
if (wakeOnly) return;
|
|
174
186
|
const thresholdS = Math.round((signal.thresholdMs ?? 0) / 1e3);
|
|
175
187
|
process.stdout.write(
|
|
176
188
|
`[kojee] WARNING: no events or heartbeat for ${silentS}s (threshold ${thresholdS}s) \u2014 stream may be dead, but this is often benign (idle stream, or brief host load delaying heartbeats) and recovery is automatic; if it persists, run \`npx kojee-mcp doctor\`.
|
|
@@ -273,11 +285,99 @@ function makeFollower(filePath, onLine, onActivity) {
|
|
|
273
285
|
}
|
|
274
286
|
};
|
|
275
287
|
}
|
|
288
|
+
function osTailAvailable() {
|
|
289
|
+
const override = (process.env.KOJEE_TAIL_IMPL ?? "").trim().toLowerCase();
|
|
290
|
+
if (override === "os") return true;
|
|
291
|
+
if (override === "node") return false;
|
|
292
|
+
return process.platform !== "win32";
|
|
293
|
+
}
|
|
294
|
+
function makeMessagesFollower(filePath, onLine, onActivity) {
|
|
295
|
+
if (!osTailAvailable()) return makeFollower(filePath, onLine, onActivity);
|
|
296
|
+
return makeTailFollower(
|
|
297
|
+
filePath,
|
|
298
|
+
onLine,
|
|
299
|
+
onActivity,
|
|
300
|
+
() => makeFollower(filePath, onLine, onActivity)
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
function makeTailFollower(filePath, onLine, onActivity, fallback) {
|
|
304
|
+
let child = null;
|
|
305
|
+
let heir = null;
|
|
306
|
+
let hbTimer = null;
|
|
307
|
+
let buffer = "";
|
|
308
|
+
let stopped = false;
|
|
309
|
+
let handedOff = false;
|
|
310
|
+
function emit(chunk) {
|
|
311
|
+
onActivity();
|
|
312
|
+
buffer += chunk;
|
|
313
|
+
let nl;
|
|
314
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
315
|
+
onLine(buffer.slice(0, nl));
|
|
316
|
+
buffer = buffer.slice(nl + 1);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function handOff() {
|
|
320
|
+
if (stopped || handedOff) return;
|
|
321
|
+
handedOff = true;
|
|
322
|
+
if (hbTimer) {
|
|
323
|
+
clearInterval(hbTimer);
|
|
324
|
+
hbTimer = null;
|
|
325
|
+
}
|
|
326
|
+
if (child) {
|
|
327
|
+
try {
|
|
328
|
+
child.kill("SIGTERM");
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
child = null;
|
|
332
|
+
}
|
|
333
|
+
heir = fallback();
|
|
334
|
+
void heir.start().catch(() => {
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
async start() {
|
|
339
|
+
try {
|
|
340
|
+
child = spawn("tail", ["-n", "+1", "-F", filePath], {
|
|
341
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
342
|
+
});
|
|
343
|
+
} catch {
|
|
344
|
+
handOff();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
child.on("error", () => handOff());
|
|
348
|
+
child.on("exit", () => {
|
|
349
|
+
if (!stopped) handOff();
|
|
350
|
+
});
|
|
351
|
+
child.stdout?.setEncoding("utf8");
|
|
352
|
+
child.stdout?.on("data", (d) => emit(d));
|
|
353
|
+
onActivity();
|
|
354
|
+
hbTimer = setInterval(onActivity, 1e3);
|
|
355
|
+
},
|
|
356
|
+
stop() {
|
|
357
|
+
stopped = true;
|
|
358
|
+
if (hbTimer) {
|
|
359
|
+
clearInterval(hbTimer);
|
|
360
|
+
hbTimer = null;
|
|
361
|
+
}
|
|
362
|
+
if (child) {
|
|
363
|
+
try {
|
|
364
|
+
child.kill("SIGTERM");
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
child = null;
|
|
368
|
+
}
|
|
369
|
+
if (heir) heir.stop();
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
276
373
|
function sleep(ms) {
|
|
277
374
|
return new Promise((r) => setTimeout(r, ms));
|
|
278
375
|
}
|
|
279
376
|
export {
|
|
280
377
|
createStallDetector,
|
|
281
378
|
makeFollower,
|
|
379
|
+
makeMessagesFollower,
|
|
380
|
+
makeTailFollower,
|
|
381
|
+
osTailAvailable,
|
|
282
382
|
runTail
|
|
283
383
|
};
|
package/package.json
CHANGED
package/dist/chunk-5DHIUN73.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// src/version.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
6
|
-
function resolveVersion() {
|
|
7
|
-
try {
|
|
8
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const parsed = JSON.parse(
|
|
10
|
-
fs.readFileSync(path.join(here, "..", "package.json"), "utf8")
|
|
11
|
-
);
|
|
12
|
-
return typeof parsed?.version === "string" && parsed.version ? parsed.version : FALLBACK_VERSION;
|
|
13
|
-
} catch (err) {
|
|
14
|
-
process.stderr.write(
|
|
15
|
-
`kojee-mcp: could not resolve version from package.json, falling back to ${FALLBACK_VERSION}: ${String(err)}
|
|
16
|
-
`
|
|
17
|
-
);
|
|
18
|
-
return FALLBACK_VERSION;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
var VERSION = resolveVersion();
|
|
22
|
-
|
|
23
|
-
export {
|
|
24
|
-
VERSION
|
|
25
|
-
};
|