kojee-mcp 0.7.5-staging.7 → 0.7.5-staging.9
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-RTYRJAJO.js → chunk-7DCZNZSP.js} +87 -22
- package/dist/{chunk-T6QANUS2.js → chunk-7LKCTUBY.js} +6 -4
- package/dist/chunk-COM6435C.js +65 -0
- package/dist/{chunk-TUDC4GY5.js → chunk-EF466Y7P.js} +31 -3
- package/dist/{chunk-NE2F5CKS.js → chunk-LKGKDZSO.js} +9 -5
- package/dist/chunk-P64UR3SE.js +86 -0
- package/dist/{chunk-CUOJHP2S.js → chunk-QDQGN5XP.js} +12 -4
- package/dist/cli.js +15 -6
- package/dist/{connect-handler-W7EAIU4R.js → connect-handler-J5RA3EJJ.js} +66 -7
- package/dist/{doctor-O7MLJEMO.js → doctor-7Y7PWE54.js} +56 -3
- package/dist/{doctor-codex-D4BOWIAA.js → doctor-codex-OEYUM7DE.js} +82 -6
- package/dist/{doctor-hermes-WLDI4R6P.js → doctor-hermes-KMHM27Y3.js} +17 -0
- package/dist/{doctor-openclaw-LL6NSPLC.js → doctor-openclaw-B5KADVHD.js} +5 -1
- package/dist/{event-stream-ATKYXDBV.js → event-stream-KQHPW42G.js} +3 -1
- package/dist/flap-log-D4KG7RK4.js +23 -0
- package/dist/index.js +1 -1
- package/dist/{install-JQNDGAAQ.js → install-NG366AOQ.js} +4 -4
- package/dist/lib.js +1 -1
- package/dist/{registry-ACVPVDCS.js → registry-PNL4VGNK.js} +17 -13
- package/dist/{setup-handler-6T7Q6DYQ.js → setup-handler-VWP6D74B.js} +5 -5
- package/dist/stream-watchdog-CLRS2VMV.js +59 -0
- package/dist/{tail-stream-HG4BKJOG.js → tail-stream-EIMLRRUP.js} +1 -1
- package/package.json +1 -1
- package/dist/chunk-TMCNB4JH.js +0 -33
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CODEX_TRUST_FIX_STEPS,
|
|
2
3
|
buildCodexMcpServerTable,
|
|
3
4
|
buildCodexPromptSubmitHookBlock,
|
|
4
5
|
buildCodexStopHookBlock,
|
|
5
6
|
isPlaceholderWebhookUrl,
|
|
6
7
|
removeCodexConfig,
|
|
7
8
|
writeCodexConfig
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7LKCTUBY.js";
|
|
9
10
|
import {
|
|
10
11
|
removeOpenclawMcpServer,
|
|
11
12
|
writeOpenclawMcpConfig
|
|
@@ -16,10 +17,6 @@ import {
|
|
|
16
17
|
removeHermesMcpServer,
|
|
17
18
|
writeHermesMcpConfig
|
|
18
19
|
} from "./chunk-GEGUWQYT.js";
|
|
19
|
-
import {
|
|
20
|
-
WIZARD_RUNTIMES,
|
|
21
|
-
isWizardRuntime
|
|
22
|
-
} from "./chunk-77HWBSRH.js";
|
|
23
20
|
import {
|
|
24
21
|
CONNECT_RUNTIMES
|
|
25
22
|
} from "./chunk-EIH2LNF4.js";
|
|
@@ -28,6 +25,10 @@ import {
|
|
|
28
25
|
readRecordedRuntime,
|
|
29
26
|
recordRuntime
|
|
30
27
|
} from "./chunk-RDLF4NQC.js";
|
|
28
|
+
import {
|
|
29
|
+
WIZARD_RUNTIMES,
|
|
30
|
+
isWizardRuntime
|
|
31
|
+
} from "./chunk-77HWBSRH.js";
|
|
31
32
|
import {
|
|
32
33
|
kojeeHomeDir
|
|
33
34
|
} from "./chunk-SQL56SEB.js";
|
|
@@ -422,9 +423,18 @@ function installBundledPayload(opts) {
|
|
|
422
423
|
// src/wizard/service.ts
|
|
423
424
|
import fs5 from "fs";
|
|
424
425
|
import path5 from "path";
|
|
426
|
+
function shQuote(value) {
|
|
427
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
428
|
+
}
|
|
429
|
+
function wrapperEnvEntries(spec) {
|
|
430
|
+
const marker = spec.wrapperEnv ?? { KOJEE_WEBHOOK_EXPECTED: "1" };
|
|
431
|
+
return [["KOJEE_RUNTIME", spec.runtime], ...Object.entries(marker)];
|
|
432
|
+
}
|
|
425
433
|
function daemonSourceExecCommand(spec) {
|
|
426
434
|
const target = spec.nodePath ? `${spec.nodePath} ${spec.binPath}` : spec.binPath;
|
|
427
|
-
|
|
435
|
+
const withArgs = spec.binArgs && spec.binArgs.length > 0 ? `${target} ${spec.binArgs.map(shQuote).join(" ")}` : target;
|
|
436
|
+
const source = spec.envFile ? `set -a; . ${spec.envFile}; set +a; ` : "";
|
|
437
|
+
return `${source}exec ${withArgs}`;
|
|
428
438
|
}
|
|
429
439
|
function xmlEscape(value) {
|
|
430
440
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -443,7 +453,7 @@ function systemdUnit(spec) {
|
|
|
443
453
|
// otherwise kill the marker too. It lets startProxy tell the "webhook was
|
|
444
454
|
// meant to be here" daemon apart from the tools-only child (which never sets
|
|
445
455
|
// it) when the sourced URL is absent → null delivery.
|
|
446
|
-
`Environment
|
|
456
|
+
`Environment=${wrapperEnvEntries(spec).map(([k, v]) => `${k}=${v}`).join(" ")}`,
|
|
447
457
|
// §2a: source the env file inside a shell wrapper (replaces EnvironmentFile=).
|
|
448
458
|
`ExecStart=/bin/sh -c '${daemonSourceExecCommand(spec)}'`,
|
|
449
459
|
"Restart=always",
|
|
@@ -478,7 +488,7 @@ function launchdPlist(spec, label) {
|
|
|
478
488
|
" </array>",
|
|
479
489
|
// §2b: KOJEE_WEBHOOK_EXPECTED in the wrapper env (NOT the sourced file).
|
|
480
490
|
" <key>EnvironmentVariables</key>",
|
|
481
|
-
` <dict
|
|
491
|
+
` <dict>${wrapperEnvEntries(spec).map(([k, v]) => `<key>${xmlEscape(k)}</key><string>${xmlEscape(v)}</string>`).join("")}</dict>`,
|
|
482
492
|
" <key>RunAtLoad</key><true/>",
|
|
483
493
|
" <key>KeepAlive</key><true/>",
|
|
484
494
|
"</dict>",
|
|
@@ -530,6 +540,15 @@ function writeService(platform, spec) {
|
|
|
530
540
|
}
|
|
531
541
|
return plan;
|
|
532
542
|
}
|
|
543
|
+
function removeService(platform, spec) {
|
|
544
|
+
const plan = planService(platform, spec);
|
|
545
|
+
let removed = false;
|
|
546
|
+
if (plan.unitPath && fs5.existsSync(plan.unitPath)) {
|
|
547
|
+
fs5.unlinkSync(plan.unitPath);
|
|
548
|
+
removed = true;
|
|
549
|
+
}
|
|
550
|
+
return { removed, deactivateCmd: plan.deactivateCmd };
|
|
551
|
+
}
|
|
533
552
|
|
|
534
553
|
// src/wizard/installers/hermes.ts
|
|
535
554
|
function installHermes(inp) {
|
|
@@ -617,6 +636,16 @@ function installOpenclaw(inp) {
|
|
|
617
636
|
...inp.pairedConfigPath ? { pairedConfigPath: inp.pairedConfigPath } : {},
|
|
618
637
|
...inp.now ? { now: inp.now } : {}
|
|
619
638
|
});
|
|
639
|
+
const platform = inp.platform ?? process.platform;
|
|
640
|
+
const svc = !inp.skipService && inp.binPath && inp.homeDir ? writeService(platform, {
|
|
641
|
+
serviceName: "kojee-openclaw",
|
|
642
|
+
binPath: inp.binPath,
|
|
643
|
+
...inp.nodePath ? { nodePath: inp.nodePath } : {},
|
|
644
|
+
runtime: "openclaw",
|
|
645
|
+
homeDir: inp.homeDir,
|
|
646
|
+
binArgs: ["--paired-config", inp.pairedConfigPath ?? perRuntimeConfigPath("openclaw")],
|
|
647
|
+
wrapperEnv: { KOJEE_HEADLESS: "1" }
|
|
648
|
+
}) : null;
|
|
620
649
|
const lines = [];
|
|
621
650
|
if (r.backedUp) {
|
|
622
651
|
lines.push(
|
|
@@ -625,17 +654,22 @@ function installOpenclaw(inp) {
|
|
|
625
654
|
);
|
|
626
655
|
}
|
|
627
656
|
lines.push(
|
|
628
|
-
"Configured runtime: openclaw (MCP server + /wake
|
|
629
|
-
"Wake mode: kojee-
|
|
630
|
-
" OpenClaw gateway /hooks/
|
|
631
|
-
|
|
657
|
+
"Configured runtime: openclaw (MCP server + standing /hooks/agent wake daemon)",
|
|
658
|
+
"Wake mode: the kojee-openclaw DAEMON holds the Tandem SSE stream and, on an @mention,",
|
|
659
|
+
" POSTs the local OpenClaw gateway /hooks/agent (Bearer hooks.token) \u2192 a FULL",
|
|
660
|
+
" agent run (kojee tools mounted) so the agent can reply via tandem_send.",
|
|
661
|
+
` mcp config: ${inp.openclawConfigPath} (mcp.servers.kojee \u2014 per-run tools; no SSE stream)`,
|
|
632
662
|
inp.pairedConfigPath ? ` credentials: per-runtime paired slot (${inp.pairedConfigPath})` : ` credentials: shared ~/.kojee/config.json (paired)`,
|
|
633
663
|
` hooks token: ${r.hooksTokenGenerated ? "generated + hooks.enabled=true (new)" : "preserved existing"}`,
|
|
664
|
+
svc ? svc.supported ? ` wake daemon: ${svc.unitPath}` : ` wake daemon: manual \u2014 ${svc.note ?? "unsupported platform"}` : " wake daemon: NOT installed (no launch path resolved) \u2014 run connect from the CLI",
|
|
634
665
|
"",
|
|
635
666
|
"Next steps (openclaw):",
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
667
|
+
...svc && svc.supported ? [
|
|
668
|
+
` - Start the standing wake daemon: ${svc.activateCmd}`,
|
|
669
|
+
" (it holds the SSE stream so an IDLE agent is still woken on @mentions)."
|
|
670
|
+
] : svc ? [` - Start the standing wake daemon: ${svc.activateCmd}`] : [],
|
|
671
|
+
" - Ensure the OpenClaw gateway is running (the daemon POSTs /hooks/agent to it).",
|
|
672
|
+
" - Restart the OpenClaw gateway to spawn the kojee MCP tools server: openclaw gateway restart",
|
|
639
673
|
" - Verify: openclaw mcp list (kojee should be listed) / kojee-mcp doctor",
|
|
640
674
|
"",
|
|
641
675
|
// Belt-and-suspenders: the same condensed rules ride the kojee MCP server's
|
|
@@ -650,11 +684,21 @@ function installOpenclaw(inp) {
|
|
|
650
684
|
}
|
|
651
685
|
function uninstallOpenclaw(inp) {
|
|
652
686
|
const removed = removeOpenclawMcpServer(inp.openclawConfigPath);
|
|
687
|
+
const svc = inp.homeDir ? removeService(inp.platform ?? process.platform, {
|
|
688
|
+
serviceName: "kojee-openclaw",
|
|
689
|
+
binPath: "",
|
|
690
|
+
runtime: "openclaw",
|
|
691
|
+
homeDir: inp.homeDir
|
|
692
|
+
}) : null;
|
|
653
693
|
const lines = [
|
|
654
|
-
"Uninstalling runtime: openclaw (MCP server +
|
|
694
|
+
"Uninstalling runtime: openclaw (MCP server + wake daemon)",
|
|
655
695
|
removed ? ` removed mcp.servers.kojee from ${inp.openclawConfigPath} (hooks + siblings preserved)` : ` no mcp.servers.kojee in ${inp.openclawConfigPath} \u2014 nothing to remove`,
|
|
696
|
+
...svc ? [
|
|
697
|
+
svc.removed ? ` removed the kojee-openclaw wake-daemon unit (stop it: ${svc.deactivateCmd})` : " no kojee-openclaw wake-daemon unit found"
|
|
698
|
+
] : [],
|
|
656
699
|
"",
|
|
657
700
|
"Next steps (openclaw):",
|
|
701
|
+
" - Stop the wake daemon if still running (see the deactivate command above).",
|
|
658
702
|
" - Restart the OpenClaw gateway: openclaw gateway restart",
|
|
659
703
|
" - hooks.enabled/token were left intact (you may use hooks for other integrations)."
|
|
660
704
|
];
|
|
@@ -827,7 +871,7 @@ function whatHappensNext(runtime, opts) {
|
|
|
827
871
|
return lines.join("\n");
|
|
828
872
|
}
|
|
829
873
|
async function configureClaudeCode(opts) {
|
|
830
|
-
const { runInit } = await import("./install-
|
|
874
|
+
const { runInit } = await import("./install-NG366AOQ.js");
|
|
831
875
|
const report = runInit({
|
|
832
876
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
833
877
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
|
|
@@ -929,7 +973,7 @@ function configureCodex(opts) {
|
|
|
929
973
|
lines.push("");
|
|
930
974
|
if (writeResult.hooksChanged) {
|
|
931
975
|
lines.push(
|
|
932
|
-
|
|
976
|
+
`IMPORTANT: this rewrite invalidated Codex's hook trust \u2014 the hooks are now UNTRUSTED and wakes stay silent until you re-trust them: ${CODEX_TRUST_FIX_STEPS}.`
|
|
933
977
|
);
|
|
934
978
|
lines.push("");
|
|
935
979
|
}
|
|
@@ -1086,18 +1130,35 @@ function openclawConfigPath(opts) {
|
|
|
1086
1130
|
}
|
|
1087
1131
|
function configureOpenclaw(opts) {
|
|
1088
1132
|
const runtime = "openclaw";
|
|
1133
|
+
const daemonExec = resolveDaemonExec();
|
|
1089
1134
|
const install = installOpenclaw({
|
|
1090
1135
|
openclawConfigPath: openclawConfigPath(opts),
|
|
1091
|
-
...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {}
|
|
1136
|
+
...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {},
|
|
1137
|
+
// Launch coords for the standing kojee-openclaw wake daemon (holds the SSE
|
|
1138
|
+
// stream so an idle agent is reachable). Absent → installer writes only the
|
|
1139
|
+
// MCP config (the shape tests).
|
|
1140
|
+
binPath: daemonExec.binPath,
|
|
1141
|
+
nodePath: daemonExec.nodePath,
|
|
1142
|
+
homeDir: kojeeHomeDir(),
|
|
1143
|
+
platform: process.platform
|
|
1092
1144
|
});
|
|
1093
1145
|
recordRuntime(runtime);
|
|
1094
|
-
|
|
1146
|
+
const lines = [install.output];
|
|
1147
|
+
if (daemonExec.ephemeral) {
|
|
1148
|
+
lines.push(
|
|
1149
|
+
"",
|
|
1150
|
+
"\u26A0 DURABILITY: this ran from an ephemeral npx cache, so the wake daemon points at a",
|
|
1151
|
+
" path npx will eventually delete (the daemon would then fail to start). For a durable",
|
|
1152
|
+
" daemon: npm i -g kojee-mcp && kojee-mcp connect <code> --runtime openclaw"
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
1095
1156
|
}
|
|
1096
1157
|
async function runWizardUninstall(runtime, opts) {
|
|
1097
1158
|
const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
|
|
1098
1159
|
const lines = [`Uninstalling runtime: ${effective}`];
|
|
1099
1160
|
if (effective === "claude-code") {
|
|
1100
|
-
const { runUninstall } = await import("./install-
|
|
1161
|
+
const { runUninstall } = await import("./install-NG366AOQ.js");
|
|
1101
1162
|
const report = runUninstall({
|
|
1102
1163
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
1103
1164
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
|
|
@@ -1121,7 +1182,11 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
1121
1182
|
lines.push(` config.toml [mcp_servers.kojee]: ${removed.mcpServer ? "removed" : "not found"}`);
|
|
1122
1183
|
lines.push(` hooks.json Stop: ${removed.stopHook ? "removed" : "not found"}`);
|
|
1123
1184
|
} else if (effective === "openclaw") {
|
|
1124
|
-
const un = uninstallOpenclaw({
|
|
1185
|
+
const un = uninstallOpenclaw({
|
|
1186
|
+
openclawConfigPath: openclawConfigPath(opts),
|
|
1187
|
+
homeDir: kojeeHomeDir(),
|
|
1188
|
+
platform: process.platform
|
|
1189
|
+
});
|
|
1125
1190
|
lines.push(un.output);
|
|
1126
1191
|
} else {
|
|
1127
1192
|
const hermesConfig = defaultHermesConfigPath(kojeeHomeDir());
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
wrapNpxLauncher
|
|
3
|
+
} from "./chunk-D6JKFJ6A.js";
|
|
1
4
|
import {
|
|
2
5
|
buildHookCommand,
|
|
3
6
|
isKojeeHookCommand,
|
|
4
7
|
npxHookCommand
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
wrapNpxLauncher
|
|
8
|
-
} from "./chunk-D6JKFJ6A.js";
|
|
8
|
+
} from "./chunk-COM6435C.js";
|
|
9
9
|
import {
|
|
10
10
|
kojeeHomeDir
|
|
11
11
|
} from "./chunk-SQL56SEB.js";
|
|
@@ -32,6 +32,7 @@ function defaultCodexConfigPath() {
|
|
|
32
32
|
function defaultCodexHooksPath() {
|
|
33
33
|
return win32DriveQualified(path.join(kojeeHomeDir(), ".codex", "hooks.json"), ".codex", "hooks.json");
|
|
34
34
|
}
|
|
35
|
+
var CODEX_TRUST_FIX_STEPS = 'to trust it, run `codex`, then type `/hooks` and press `t` on the entry \u2014 Codex only prints a "hooks need review" warning at startup, it does not auto-prompt';
|
|
35
36
|
var CODEX_STOP_HOOK_COMMAND = npxHookCommand("codex-stop");
|
|
36
37
|
function codexStopHookCommand() {
|
|
37
38
|
return buildHookCommand("codex-stop");
|
|
@@ -345,6 +346,7 @@ function writeFile600(p, content) {
|
|
|
345
346
|
export {
|
|
346
347
|
defaultCodexConfigPath,
|
|
347
348
|
defaultCodexHooksPath,
|
|
349
|
+
CODEX_TRUST_FIX_STEPS,
|
|
348
350
|
buildCodexMcpServerTable,
|
|
349
351
|
buildCodexStopHookBlock,
|
|
350
352
|
buildCodexPromptSubmitHookBlock,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/hooks/hook-command.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
function npxHookCommand(type) {
|
|
6
|
+
return `npx -y kojee-mcp hook --type=${type}`;
|
|
7
|
+
}
|
|
8
|
+
var NPX_CACHE_RE = /[\\/]_npx[\\/]/;
|
|
9
|
+
function resolveCliEntry(moduleUrl = import.meta.url) {
|
|
10
|
+
try {
|
|
11
|
+
const here = fileURLToPath(moduleUrl);
|
|
12
|
+
const candidate = path.join(path.dirname(here), "cli.js");
|
|
13
|
+
if (!fs.existsSync(candidate)) return null;
|
|
14
|
+
if (NPX_CACHE_RE.test(candidate)) return null;
|
|
15
|
+
return candidate;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function stableExecPath(execPath = process.execPath) {
|
|
21
|
+
const m = /^(.*)\/Cellar\/node(?:@[^/]+)?\/[^/]+\/bin\/node$/.exec(execPath);
|
|
22
|
+
if (!m || !m[1]) return execPath;
|
|
23
|
+
const alias = path.join(m[1], "bin", "node");
|
|
24
|
+
try {
|
|
25
|
+
if (!fs.existsSync(alias)) return execPath;
|
|
26
|
+
const real = fs.realpathSync(alias);
|
|
27
|
+
return /[\\/]Cellar[\\/]node(@[^/]+)?[\\/]/.test(real) ? alias : execPath;
|
|
28
|
+
} catch {
|
|
29
|
+
return execPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function buildHookCommand(type, opts = {}) {
|
|
33
|
+
const cliEntry = opts.cliEntry !== void 0 ? opts.cliEntry : resolveCliEntry();
|
|
34
|
+
if (!cliEntry) return npxHookCommand(type);
|
|
35
|
+
const execPath = stableExecPath(opts.execPath ?? process.execPath);
|
|
36
|
+
return `"${execPath}" "${cliEntry}" hook --type=${type}`;
|
|
37
|
+
}
|
|
38
|
+
var PINNED_HOOK_RE = /^"[^"]+" "[^"]+" hook --type=(stop|user-prompt-submit|codex-stop|codex-prompt-submit)$/;
|
|
39
|
+
function isKojeeHookCommand(command) {
|
|
40
|
+
if (command.startsWith("npx -y kojee-mcp hook --type=")) return true;
|
|
41
|
+
return PINNED_HOOK_RE.test(command);
|
|
42
|
+
}
|
|
43
|
+
function classifyHookCommand(command, deps = {}) {
|
|
44
|
+
if (command.startsWith("npx -y kojee-mcp hook --type=")) return "npx";
|
|
45
|
+
if (!PINNED_HOOK_RE.test(command)) return "foreign";
|
|
46
|
+
const paths = [...command.matchAll(/"([^"]+)"/g)].map((m) => m[1] ?? "");
|
|
47
|
+
const cliPath = paths[1] ?? "";
|
|
48
|
+
if (NPX_CACHE_RE.test(cliPath)) return "pinned-ephemeral";
|
|
49
|
+
const exists = deps.exists ?? ((p) => {
|
|
50
|
+
try {
|
|
51
|
+
return fs.existsSync(p);
|
|
52
|
+
} catch {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
if (cliPath && !exists(cliPath)) return "pinned-broken";
|
|
57
|
+
return "pinned-durable";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
npxHookCommand,
|
|
62
|
+
buildHookCommand,
|
|
63
|
+
isKojeeHookCommand,
|
|
64
|
+
classifyHookCommand
|
|
65
|
+
};
|
|
@@ -295,9 +295,11 @@ async function startProxy(config) {
|
|
|
295
295
|
}
|
|
296
296
|
console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
|
|
297
297
|
let server;
|
|
298
|
-
const
|
|
298
|
+
const isHeadlessDaemon = !!process.env["KOJEE_HEADLESS"] || !!process.env["KOJEE_WEBHOOK_EXPECTED"];
|
|
299
|
+
const { selectDelivery } = await import("./registry-PNL4VGNK.js");
|
|
299
300
|
const delivery = selectDelivery(adapter.runtime, {
|
|
300
301
|
supportsChannels: adapter.supportsChannels,
|
|
302
|
+
headless: isHeadlessDaemon,
|
|
301
303
|
// Per-window delivered mirror for the tandem_pending tool (codex only —
|
|
302
304
|
// other runtimes' deliveries ignore this even when passed).
|
|
303
305
|
...pendingState ? { onDelivered: (e) => pendingState.noteDelivered(e.tandem_id, e.cursor) } : {}
|
|
@@ -338,13 +340,12 @@ async function startProxy(config) {
|
|
|
338
340
|
}
|
|
339
341
|
server = createMcpServer(registry, adapter, tandemMembershipCount);
|
|
340
342
|
}
|
|
341
|
-
const isHeadlessDaemon = !!process.env["KOJEE_WEBHOOK_EXPECTED"];
|
|
342
343
|
if (!isHeadlessDaemon) {
|
|
343
344
|
process.stdin.on("end", () => shutdown("stdin end"));
|
|
344
345
|
process.stdin.on("close", () => shutdown("stdin close"));
|
|
345
346
|
} else {
|
|
346
347
|
console.error(
|
|
347
|
-
"[kojee-mcp] headless daemon (KOJEE_WEBHOOK_EXPECTED
|
|
348
|
+
"[kojee-mcp] headless daemon (KOJEE_HEADLESS / KOJEE_WEBHOOK_EXPECTED): stdin EOF ignored; lifecycle owned by the service manager (SIGTERM/SIGINT/SIGHUP)."
|
|
348
349
|
);
|
|
349
350
|
}
|
|
350
351
|
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
@@ -363,6 +364,33 @@ async function startProxy(config) {
|
|
|
363
364
|
"[kojee-mcp] no resolvable Claude ancestor (Windows fallback) \u2014 parent-liveness watchdog not armed; stdin close/end + SIGHUP/SIGINT/SIGTERM cover shutdown, and per-window wake is preserved via the session-id discovery key."
|
|
364
365
|
);
|
|
365
366
|
}
|
|
367
|
+
if (isHeadlessDaemon && activeStreamHandle) {
|
|
368
|
+
const { createStreamWatchdog } = await import("./stream-watchdog-CLRS2VMV.js");
|
|
369
|
+
const { recordFlap } = await import("./flap-log-D4KG7RK4.js");
|
|
370
|
+
const handleForWatchdog = activeStreamHandle;
|
|
371
|
+
const bootAt = Date.now();
|
|
372
|
+
const streamWatchdog = createStreamWatchdog({
|
|
373
|
+
getState: () => {
|
|
374
|
+
const s = handleForWatchdog.getState();
|
|
375
|
+
return { connected: s.connected, lastEventAt: s.lastEventAt };
|
|
376
|
+
},
|
|
377
|
+
onUnhealthy: ({ disconnectedForMs, reason }) => {
|
|
378
|
+
recordFlap({
|
|
379
|
+
ts: Date.now(),
|
|
380
|
+
instanceKey,
|
|
381
|
+
reason,
|
|
382
|
+
disconnectedForMs,
|
|
383
|
+
uptimeMs: Date.now() - bootAt
|
|
384
|
+
});
|
|
385
|
+
console.error(
|
|
386
|
+
`[kojee-mcp] stream-liveness watchdog: ${reason} \u2014 self-exiting(1); the service manager (Restart=always) will start a fresh process. Repeated exits are recorded and surfaced by \`kojee-mcp doctor\` (flap detection).`
|
|
387
|
+
);
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
streamWatchdog.start();
|
|
392
|
+
teardownSteps.push(() => streamWatchdog.stop());
|
|
393
|
+
}
|
|
366
394
|
await startMcpServer(server);
|
|
367
395
|
}
|
|
368
396
|
async function enrollAndDiscover(config, keystorePath, isRetry = false) {
|
|
@@ -87,18 +87,22 @@ function createCachedOpenclawWakeResolver(opts = {}) {
|
|
|
87
87
|
return resolution;
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
function openclawAgentUrl(wakeUrl) {
|
|
91
|
+
return wakeUrl.replace(/\/wake$/, "/agent");
|
|
92
|
+
}
|
|
93
|
+
async function postOpenclawAgent(target, body, deps = {}) {
|
|
91
94
|
const doFetch = deps.fetch ?? fetch;
|
|
95
|
+
const url = openclawAgentUrl(target.url);
|
|
92
96
|
const controller = new AbortController();
|
|
93
97
|
const timer = setTimeout(() => controller.abort(), deps.timeoutMs ?? 4e3);
|
|
94
98
|
try {
|
|
95
|
-
const res = await doFetch(
|
|
99
|
+
const res = await doFetch(url, {
|
|
96
100
|
method: "POST",
|
|
97
101
|
headers: { "content-type": "application/json", authorization: `Bearer ${target.token}` },
|
|
98
|
-
body: JSON.stringify(body),
|
|
102
|
+
body: JSON.stringify({ name: "Kojee", wakeMode: "now", ...body }),
|
|
99
103
|
signal: controller.signal
|
|
100
104
|
});
|
|
101
|
-
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status, error: `
|
|
105
|
+
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status, error: `agent POST ${res.status}` };
|
|
102
106
|
} catch (err) {
|
|
103
107
|
return { ok: false, error: err.message };
|
|
104
108
|
} finally {
|
|
@@ -109,5 +113,5 @@ async function postOpenclawWake(target, body, deps = {}) {
|
|
|
109
113
|
export {
|
|
110
114
|
resolveOpenclawWakeTarget,
|
|
111
115
|
createCachedOpenclawWakeResolver,
|
|
112
|
-
|
|
116
|
+
postOpenclawAgent
|
|
113
117
|
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
kojeeHomeDir
|
|
3
|
+
} from "./chunk-SQL56SEB.js";
|
|
4
|
+
|
|
5
|
+
// src/runtime/flap-log.ts
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
var MAX_FLAP_ENTRIES = 25;
|
|
9
|
+
function defaultFlapLogPath() {
|
|
10
|
+
return path.join(kojeeHomeDir(), ".kojee", "daemon-flaps.json");
|
|
11
|
+
}
|
|
12
|
+
function realRead(p) {
|
|
13
|
+
try {
|
|
14
|
+
return fs.readFileSync(p, "utf8");
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function realWrite(p, content) {
|
|
20
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
21
|
+
fs.writeFileSync(p, content, "utf8");
|
|
22
|
+
}
|
|
23
|
+
function readFlaps(deps = {}) {
|
|
24
|
+
const p = deps.filePath ?? defaultFlapLogPath();
|
|
25
|
+
const raw = (deps.readFile ?? realRead)(p);
|
|
26
|
+
if (raw === null) return [];
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
if (!Array.isArray(parsed)) return [];
|
|
30
|
+
return parsed.filter(
|
|
31
|
+
(e) => !!e && typeof e.ts === "number" && typeof e.instanceKey === "string"
|
|
32
|
+
);
|
|
33
|
+
} catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function recordFlap(entry, deps = {}) {
|
|
38
|
+
try {
|
|
39
|
+
const p = deps.filePath ?? defaultFlapLogPath();
|
|
40
|
+
const existing = readFlaps(deps);
|
|
41
|
+
const next = [...existing, entry].slice(-MAX_FLAP_ENTRIES);
|
|
42
|
+
(deps.writeFile ?? realWrite)(p, JSON.stringify(next, null, 2));
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function flapRateExceeds(flaps, opts) {
|
|
47
|
+
const cutoff = opts.now - opts.windowMs;
|
|
48
|
+
return flaps.filter((f) => f.ts >= cutoff).length >= opts.count;
|
|
49
|
+
}
|
|
50
|
+
function recentFlapCount(flaps, windowMs, now) {
|
|
51
|
+
const cutoff = now - windowMs;
|
|
52
|
+
return flaps.filter((f) => f.ts >= cutoff).length;
|
|
53
|
+
}
|
|
54
|
+
var FLAP_WINDOW_MS = 15 * 6e4;
|
|
55
|
+
var FLAP_WARN_COUNT = 3;
|
|
56
|
+
function buildFlapDoctorCheck(deps = {}) {
|
|
57
|
+
const name = deps.name ?? "daemon flapping";
|
|
58
|
+
const history = (deps.readFlaps ?? readFlaps)();
|
|
59
|
+
const now = deps.now?.() ?? Date.now();
|
|
60
|
+
const recent = recentFlapCount(history, FLAP_WINDOW_MS, now);
|
|
61
|
+
if (flapRateExceeds(history, { count: FLAP_WARN_COUNT, windowMs: FLAP_WINDOW_MS, now })) {
|
|
62
|
+
const last = history[history.length - 1];
|
|
63
|
+
return {
|
|
64
|
+
name,
|
|
65
|
+
ok: "warn",
|
|
66
|
+
detail: `the stream-liveness watchdog self-exited ${recent} times in the last 15min \u2014 the SSE reconnect loop is repeatedly wedging. This is a CHRONIC failure being masked by auto-restart; root-cause it (last reason: "${last?.reason ?? "unknown"}").`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
name,
|
|
71
|
+
ok: true,
|
|
72
|
+
detail: recent === 0 ? "no stream-liveness self-exits recently (healthy)" : `${recent} self-exit(s) in the last 15min \u2014 below the ${FLAP_WARN_COUNT}-in-15min chronic threshold`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
MAX_FLAP_ENTRIES,
|
|
78
|
+
defaultFlapLogPath,
|
|
79
|
+
readFlaps,
|
|
80
|
+
recordFlap,
|
|
81
|
+
flapRateExceeds,
|
|
82
|
+
recentFlapCount,
|
|
83
|
+
FLAP_WINDOW_MS,
|
|
84
|
+
FLAP_WARN_COUNT,
|
|
85
|
+
buildFlapDoctorCheck
|
|
86
|
+
};
|
|
@@ -11,6 +11,7 @@ var STALE_FLOOR_MS = 9e4;
|
|
|
11
11
|
var STALE_INTERVAL_MULTIPLIER = 3;
|
|
12
12
|
var STALE_CHECK_INTERVAL_MS = 5e3;
|
|
13
13
|
var UNARMED_FALLBACK_MS = 12 * 6e4;
|
|
14
|
+
var CONNECT_TIMEOUT_MS = 3e4;
|
|
14
15
|
var UNDICI_DEFAULT_BODY_TIMEOUT_MS = 3e5;
|
|
15
16
|
function createAdaptiveWatchdog(options = {}) {
|
|
16
17
|
const floorMs = options.floorMs ?? STALE_FLOOR_MS;
|
|
@@ -165,10 +166,16 @@ async function startEventStream(opts) {
|
|
|
165
166
|
}
|
|
166
167
|
async function connectAndConsume(opts, cursors, controller, isReconnect, state, dispatcher, onCursor, onOpen) {
|
|
167
168
|
const mapSince = cursors.size > 0 ? serializeCursorMap(cursors) : null;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
res = await openStream(opts, controller,
|
|
169
|
+
const connectTimer = setTimeout(() => controller.abort(), CONNECT_TIMEOUT_MS);
|
|
170
|
+
let res;
|
|
171
|
+
try {
|
|
172
|
+
res = await openStream(opts, controller, mapSince, dispatcher);
|
|
173
|
+
if (res.status === 400 && mapSince !== null) {
|
|
174
|
+
console.error("[event-stream] per-room ?since rejected (400) \u2014 falling back to bare cursor");
|
|
175
|
+
res = await openStream(opts, controller, String(Math.min(...cursors.values())), dispatcher);
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
clearTimeout(connectTimer);
|
|
172
179
|
}
|
|
173
180
|
if (!res.ok) throw new Error(`SSE connect failed: ${res.status}`);
|
|
174
181
|
if (!res.body) throw new Error("SSE response has no body");
|
|
@@ -448,6 +455,7 @@ function normalizeBackendEvent(raw, sseEventType) {
|
|
|
448
455
|
export {
|
|
449
456
|
undiciFetch,
|
|
450
457
|
UNARMED_FALLBACK_MS,
|
|
458
|
+
CONNECT_TIMEOUT_MS,
|
|
451
459
|
UNDICI_DEFAULT_BODY_TIMEOUT_MS,
|
|
452
460
|
createAdaptiveWatchdog,
|
|
453
461
|
createBackoffController,
|
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-EF466Y7P.js";
|
|
8
8
|
import "./chunk-XYDNRVOU.js";
|
|
9
9
|
import {
|
|
10
10
|
pairedConfigPath
|
|
@@ -47,7 +47,7 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
|
|
|
47
47
|
program.command("connect <code>").description(
|
|
48
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."
|
|
49
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) => {
|
|
50
|
-
const { runConnect } = await import("./connect-handler-
|
|
50
|
+
const { runConnect } = await import("./connect-handler-J5RA3EJJ.js");
|
|
51
51
|
const result = await runConnect({
|
|
52
52
|
code,
|
|
53
53
|
runtime: opts.runtime,
|
|
@@ -57,6 +57,15 @@ program.command("connect <code>").description(
|
|
|
57
57
|
console.error(result.output);
|
|
58
58
|
process.exit(result.exitCode);
|
|
59
59
|
});
|
|
60
|
+
program.command("reconnect").description(
|
|
61
|
+
"Re-run configure for an already-paired runtime, KEEPING the token + enrolled key (no re-pair). Re-writes configs/hooks to this install and refreshes the agent instructions."
|
|
62
|
+
).option("--runtime <id>", "Target runtime (defaults to the recorded runtime from the last connect/init)").action(async (opts) => {
|
|
63
|
+
const { runReconnect } = await import("./connect-handler-J5RA3EJJ.js");
|
|
64
|
+
const result = await runReconnect({ ...opts.runtime ? { runtime: opts.runtime } : {} });
|
|
65
|
+
for (const m of result.messages) console.error(m);
|
|
66
|
+
console.error(result.output);
|
|
67
|
+
process.exit(result.exitCode);
|
|
68
|
+
});
|
|
60
69
|
program.command("hook").description("Run a kojee MCP hook script (called by Claude Code via ~/.claude/settings.json)").requiredOption(
|
|
61
70
|
"--type <type>",
|
|
62
71
|
"Hook type: stop, user-prompt-submit, codex-stop, or codex-prompt-submit"
|
|
@@ -85,7 +94,7 @@ program.command("hook").description("Run a kojee MCP hook script (called by Clau
|
|
|
85
94
|
}
|
|
86
95
|
});
|
|
87
96
|
program.command("install-hooks").description("Install kojee Stop + UserPromptSubmit hooks in ~/.claude/settings.json (idempotent)").option("--hooks-path <path>", "Override default ~/.claude/settings.json").option("--uninstall", "Remove kojee hook entries instead of installing them").action(async (opts) => {
|
|
88
|
-
const { installHooks, uninstallHooks } = await import("./install-
|
|
97
|
+
const { installHooks, uninstallHooks } = await import("./install-NG366AOQ.js");
|
|
89
98
|
if (opts.uninstall) {
|
|
90
99
|
const removed = uninstallHooks({ hooksPath: opts.hooksPath });
|
|
91
100
|
console.error(removed ? "Removed kojee hook entries." : "No kojee hook entries found.");
|
|
@@ -117,7 +126,7 @@ program.command("tail <path>").description("Stream a file's contents and follow
|
|
|
117
126
|
"--wake-only",
|
|
118
127
|
"Suppress routine heartbeat warn/recover telemetry; surface only a sustained stream outage"
|
|
119
128
|
).action(async (filePath, opts) => {
|
|
120
|
-
const { runTail } = await import("./tail-stream-
|
|
129
|
+
const { runTail } = await import("./tail-stream-EIMLRRUP.js");
|
|
121
130
|
try {
|
|
122
131
|
await runTail(filePath, { wakeOnly: opts.wakeOnly === true });
|
|
123
132
|
} catch (err) {
|
|
@@ -126,7 +135,7 @@ program.command("tail <path>").description("Stream a file's contents and follow
|
|
|
126
135
|
}
|
|
127
136
|
});
|
|
128
137
|
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 () => {
|
|
129
|
-
const { runDoctor } = await import("./doctor-
|
|
138
|
+
const { runDoctor } = await import("./doctor-7Y7PWE54.js");
|
|
130
139
|
const code = await runDoctor();
|
|
131
140
|
process.exit(code);
|
|
132
141
|
});
|
|
@@ -145,7 +154,7 @@ function addInstallOptions(cmd, runtimeHelp) {
|
|
|
145
154
|
function makeInstallAction(verb) {
|
|
146
155
|
return async (opts) => {
|
|
147
156
|
const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
|
|
148
|
-
const { runSetup, resolvePairCode } = await import("./setup-handler-
|
|
157
|
+
const { runSetup, resolvePairCode } = await import("./setup-handler-VWP6D74B.js");
|
|
149
158
|
const pairCode = resolvePairCode(opts);
|
|
150
159
|
const result = await runSetup({
|
|
151
160
|
verb,
|
|
@@ -2,17 +2,19 @@ import {
|
|
|
2
2
|
DEFAULT_BROKER_URL,
|
|
3
3
|
reconcileConnectPairSlot,
|
|
4
4
|
runWizard
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-7DCZNZSP.js";
|
|
6
|
+
import "./chunk-7LKCTUBY.js";
|
|
7
7
|
import "./chunk-E6WMFMM2.js";
|
|
8
8
|
import "./chunk-GEGUWQYT.js";
|
|
9
|
-
import "./chunk-77HWBSRH.js";
|
|
10
|
-
import "./chunk-TMCNB4JH.js";
|
|
11
|
-
import "./chunk-D6JKFJ6A.js";
|
|
12
9
|
import {
|
|
13
10
|
CONNECT_RUNTIMES
|
|
14
11
|
} from "./chunk-EIH2LNF4.js";
|
|
15
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
readRecordedRuntime
|
|
14
|
+
} from "./chunk-RDLF4NQC.js";
|
|
15
|
+
import "./chunk-77HWBSRH.js";
|
|
16
|
+
import "./chunk-D6JKFJ6A.js";
|
|
17
|
+
import "./chunk-COM6435C.js";
|
|
16
18
|
import "./chunk-SQL56SEB.js";
|
|
17
19
|
import "./chunk-V5VZPYMZ.js";
|
|
18
20
|
import {
|
|
@@ -33,6 +35,7 @@ import "./chunk-U5HHHRXA.js";
|
|
|
33
35
|
import "./chunk-O4JQY7IF.js";
|
|
34
36
|
|
|
35
37
|
// src/wizard/connect-handler.ts
|
|
38
|
+
import fs from "fs";
|
|
36
39
|
import os from "os";
|
|
37
40
|
import path from "path";
|
|
38
41
|
function absConfigPath(runtime) {
|
|
@@ -139,7 +142,63 @@ Start (or restart) the hermes daemon to pick up the connection.`,
|
|
|
139
142
|
exitCode: 0
|
|
140
143
|
};
|
|
141
144
|
}
|
|
145
|
+
async function runReconnect(opts = {}) {
|
|
146
|
+
const messages = [];
|
|
147
|
+
const runtime = opts.runtime ?? readRecordedRuntime() ?? null;
|
|
148
|
+
if (!runtime) {
|
|
149
|
+
return {
|
|
150
|
+
messages,
|
|
151
|
+
output: `reconnect: no runtime recorded on this box \u2014 pass --runtime (${CONNECT_RUNTIMES.join(" | ")} | hermes), or run \`kojee-mcp connect <code> --runtime <id>\` first.`,
|
|
152
|
+
exitCode: 2
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const isHermes = runtime === "hermes";
|
|
156
|
+
if (!isHermes && !CONNECT_RUNTIMES.includes(runtime)) {
|
|
157
|
+
return {
|
|
158
|
+
messages,
|
|
159
|
+
output: `reconnect supports: ${CONNECT_RUNTIMES.join(", ")}, hermes. Unknown --runtime "${runtime}".`,
|
|
160
|
+
exitCode: 2
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const configPath = isHermes ? pairedConfigPath() : absConfigPath(runtime);
|
|
164
|
+
const paired = loadPairedConfig(configPath);
|
|
165
|
+
if (!paired?.token) {
|
|
166
|
+
return {
|
|
167
|
+
messages,
|
|
168
|
+
output: `reconnect: no paired config at ${configPath} \u2014 nothing to keep. Run \`kojee-mcp connect <code> --runtime ${runtime}\` to pair first.`,
|
|
169
|
+
exitCode: 2
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const keystorePath = isHermes ? defaultPairedKeystorePath() : deriveKeystorePath(paired.token);
|
|
173
|
+
if (!fs.existsSync(keystorePath)) {
|
|
174
|
+
messages.push(
|
|
175
|
+
`\u26A0 enrolled keystore MISSING at ${keystorePath} \u2014 configs will be refreshed, but the proxy cannot authenticate with a new key on this token (backend rejects double-enroll). If sends fail, re-pair with a fresh code.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const wizard = opts.runWizard ?? runWizard;
|
|
179
|
+
const wiz = await wizard({
|
|
180
|
+
runtime,
|
|
181
|
+
...isHermes ? {} : { pairedConfigPath: configPath },
|
|
182
|
+
interactive: false
|
|
183
|
+
});
|
|
184
|
+
if (wiz.exitCode !== 0) {
|
|
185
|
+
return { messages, output: wiz.output, exitCode: wiz.exitCode };
|
|
186
|
+
}
|
|
187
|
+
const label = identityLabel(paired);
|
|
188
|
+
return {
|
|
189
|
+
messages,
|
|
190
|
+
output: [
|
|
191
|
+
`\u21BB reconfigured ${runtime} as ${label} \u2014 token KEPT (no re-pair, no key re-enrollment).`,
|
|
192
|
+
"Configs + hooks re-written to this install; agent instructions refreshed below.",
|
|
193
|
+
`Restart ${runtime} to pick up the changes.`,
|
|
194
|
+
"",
|
|
195
|
+
wiz.output
|
|
196
|
+
].join("\n"),
|
|
197
|
+
exitCode: 0
|
|
198
|
+
};
|
|
199
|
+
}
|
|
142
200
|
export {
|
|
143
201
|
runConnect,
|
|
144
|
-
runConnectHermes
|
|
202
|
+
runConnectHermes,
|
|
203
|
+
runReconnect
|
|
145
204
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyHookCommand,
|
|
3
|
+
isKojeeHookCommand
|
|
4
|
+
} from "./chunk-COM6435C.js";
|
|
1
5
|
import "./chunk-XLKGPGZT.js";
|
|
2
6
|
import {
|
|
3
7
|
loadControlToken
|
|
@@ -149,6 +153,53 @@ async function resolveDefaultPairedScope() {
|
|
|
149
153
|
}
|
|
150
154
|
return { scope: resolution.scope, present, ...runtime ? { runtime } : {} };
|
|
151
155
|
}
|
|
156
|
+
function defaultReadHookCommands() {
|
|
157
|
+
try {
|
|
158
|
+
const p = path.join(os.homedir(), ".claude", "settings.json");
|
|
159
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
160
|
+
const cfg = JSON.parse(raw);
|
|
161
|
+
const out = [];
|
|
162
|
+
for (const event of ["Stop", "UserPromptSubmit"]) {
|
|
163
|
+
for (const matcher of cfg.hooks?.[event] ?? []) {
|
|
164
|
+
for (const h of matcher.hooks ?? []) {
|
|
165
|
+
if (typeof h.command === "string" && isKojeeHookCommand(h.command)) out.push(h.command);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
var HOOK_REPIN_FIX = "fix: `npm i -g kojee-mcp` then re-run `kojee-mcp setup` (re-pins in place, no re-pair)";
|
|
175
|
+
function hookPinCheck(commands) {
|
|
176
|
+
const name = "hook pin (settings.json Stop/UserPromptSubmit)";
|
|
177
|
+
if (commands === null) {
|
|
178
|
+
return { name, ok: "warn", detail: `no ~/.claude/settings.json readable \u2014 run \`kojee-mcp setup\` to install the wake hooks` };
|
|
179
|
+
}
|
|
180
|
+
if (commands.length === 0) {
|
|
181
|
+
return { name, ok: "warn", detail: `no kojee hooks installed \u2014 run \`kojee-mcp setup\` (the stop-hook is the Monitor self-heal)` };
|
|
182
|
+
}
|
|
183
|
+
const states = commands.map((c) => classifyHookCommand(c));
|
|
184
|
+
if (states.includes("pinned-broken")) {
|
|
185
|
+
return { name, ok: false, detail: `pinned cli.js is GONE \u2014 hooks are failing with exit 127 RIGHT NOW; ${HOOK_REPIN_FIX}` };
|
|
186
|
+
}
|
|
187
|
+
if (states.includes("pinned-ephemeral")) {
|
|
188
|
+
return {
|
|
189
|
+
name,
|
|
190
|
+
ok: false,
|
|
191
|
+
detail: `pinned INTO the npx cache (~/.npm/_npx/\u2026) \u2014 npx cache GC will silently kill the hooks (exit 127 every turn); ${HOOK_REPIN_FIX}`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (states.includes("npx")) {
|
|
195
|
+
return {
|
|
196
|
+
name,
|
|
197
|
+
ok: "warn",
|
|
198
|
+
detail: `unpinned (npx -y) \u2014 pays npx resolution every turn and can stall on a registry fetch; ${HOOK_REPIN_FIX}`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return { name, ok: true, detail: `pinned to a durable binary (${commands.length} hook${commands.length === 1 ? "" : "s"})` };
|
|
202
|
+
}
|
|
152
203
|
async function collectDoctorReport(deps = {}) {
|
|
153
204
|
const fetchFn = deps.fetchFn ?? fetch;
|
|
154
205
|
const isPidAlive = deps.isPidAlive ?? defaultIsPidAlive;
|
|
@@ -183,6 +234,8 @@ async function collectDoctorReport(deps = {}) {
|
|
|
183
234
|
detail: pairedScope.present ? "present (legacy global slot)" : "MISSING \u2014 run `kojee-mcp connect <code> --runtime <id>` (or `kojee-mcp pair`)"
|
|
184
235
|
});
|
|
185
236
|
}
|
|
237
|
+
const hookCommands = (deps.readHookCommands ?? defaultReadHookCommands)();
|
|
238
|
+
checks.push(hookPinCheck(hookCommands));
|
|
186
239
|
let logPath = null;
|
|
187
240
|
if (!discovery) {
|
|
188
241
|
checks.push({
|
|
@@ -354,18 +407,18 @@ function formatDoctorReport(report) {
|
|
|
354
407
|
async function runDoctor() {
|
|
355
408
|
const { readRecordedRuntime } = await import("./runtime-record-OXRLTOLC.js");
|
|
356
409
|
if (readRecordedRuntime() === "codex") {
|
|
357
|
-
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-
|
|
410
|
+
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-OEYUM7DE.js");
|
|
358
411
|
const report2 = collectCodexDoctorReport();
|
|
359
412
|
console.error(formatCodexDoctorReport(report2));
|
|
360
413
|
return report2.verdict === "broken" ? 1 : 0;
|
|
361
414
|
}
|
|
362
415
|
if (readRecordedRuntime() === "openclaw") {
|
|
363
|
-
const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-
|
|
416
|
+
const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-B5KADVHD.js");
|
|
364
417
|
const report2 = collectOpenclawDoctorReport();
|
|
365
418
|
console.error(formatOpenclawDoctorReport(report2));
|
|
366
419
|
return report2.verdict === "broken" ? 1 : 0;
|
|
367
420
|
}
|
|
368
|
-
const { hermesArtifactsPresent, collectHermesDoctorReport, formatHermesDoctorReport } = await import("./doctor-hermes-
|
|
421
|
+
const { hermesArtifactsPresent, collectHermesDoctorReport, formatHermesDoctorReport } = await import("./doctor-hermes-KMHM27Y3.js");
|
|
369
422
|
if (hermesArtifactsPresent()) {
|
|
370
423
|
const hermesReport = collectHermesDoctorReport();
|
|
371
424
|
console.error(formatHermesDoctorReport(hermesReport));
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CODEX_TRUST_FIX_STEPS,
|
|
2
3
|
defaultCodexConfigPath,
|
|
3
4
|
defaultCodexHooksPath,
|
|
4
5
|
isPlaceholderWebhookUrl
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-TMCNB4JH.js";
|
|
6
|
+
} from "./chunk-7LKCTUBY.js";
|
|
7
7
|
import "./chunk-D6JKFJ6A.js";
|
|
8
|
+
import {
|
|
9
|
+
classifyHookCommand,
|
|
10
|
+
isKojeeHookCommand
|
|
11
|
+
} from "./chunk-COM6435C.js";
|
|
8
12
|
import {
|
|
9
13
|
CODEX_STATUS_STALE_MS,
|
|
10
14
|
readAllCodexStatusRaw,
|
|
@@ -22,6 +26,7 @@ import {
|
|
|
22
26
|
|
|
23
27
|
// src/doctor-codex.ts
|
|
24
28
|
import fs from "fs";
|
|
29
|
+
import path from "path";
|
|
25
30
|
|
|
26
31
|
// src/hooks/codex-trust-hash.ts
|
|
27
32
|
import crypto from "crypto";
|
|
@@ -127,15 +132,33 @@ function extractPairedConfigPath(toml) {
|
|
|
127
132
|
const m = toml.match(/args\s*=\s*\[[^\]]*"--paired-config"\s*,\s*"([^"]+)"/);
|
|
128
133
|
return m ? m[1].replace(/\\\\/g, "\\") : null;
|
|
129
134
|
}
|
|
130
|
-
function defaultReadFile(
|
|
135
|
+
function defaultReadFile(filePath) {
|
|
131
136
|
return () => {
|
|
132
137
|
try {
|
|
133
|
-
return fs.readFileSync(
|
|
138
|
+
return fs.readFileSync(filePath, "utf8");
|
|
134
139
|
} catch {
|
|
135
140
|
return null;
|
|
136
141
|
}
|
|
137
142
|
};
|
|
138
143
|
}
|
|
144
|
+
function defaultDiscoverProjectCodexConfigs(cwd = process.cwd()) {
|
|
145
|
+
const globalToml = path.resolve(defaultCodexConfigPath());
|
|
146
|
+
const found = [];
|
|
147
|
+
let dir = cwd;
|
|
148
|
+
for (let hops = 0; hops < 64; hops++) {
|
|
149
|
+
const candidate = path.join(dir, ".codex", "config.toml");
|
|
150
|
+
if (path.resolve(candidate) !== globalToml) {
|
|
151
|
+
try {
|
|
152
|
+
found.push({ path: candidate, toml: fs.readFileSync(candidate, "utf8") });
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const parent = path.dirname(dir);
|
|
157
|
+
if (parent === dir) break;
|
|
158
|
+
dir = parent;
|
|
159
|
+
}
|
|
160
|
+
return found;
|
|
161
|
+
}
|
|
139
162
|
function collectCodexDoctorReport(deps = {}) {
|
|
140
163
|
const readConfigToml = deps.readConfigToml ?? defaultReadFile(defaultCodexConfigPath());
|
|
141
164
|
const readHooksJson = deps.readHooksJson ?? defaultReadFile(defaultCodexHooksPath());
|
|
@@ -150,6 +173,25 @@ function collectCodexDoctorReport(deps = {}) {
|
|
|
150
173
|
ok: configOk,
|
|
151
174
|
detail: configOk ? 'present with env.KOJEE_RUNTIME="codex" (Tier-1 contract)' : `MISSING [mcp_servers.kojee] or env.KOJEE_RUNTIME="codex" \u2014 ${WIZARD_RERUN}`
|
|
152
175
|
});
|
|
176
|
+
const projectConfigs = deps.discoverProjectCodexConfigs?.() ?? defaultDiscoverProjectCodexConfigs();
|
|
177
|
+
const shadowing = projectConfigs.filter(
|
|
178
|
+
(c) => c.toml.includes("[mcp_servers.kojee]") || /\[hooks\.state/.test(c.toml) || /hook --type=codex-/.test(c.toml)
|
|
179
|
+
);
|
|
180
|
+
if (shadowing.length > 0) {
|
|
181
|
+
const pins = shadowing.map((s) => /kojee-mcp@([^\s"']+)/.exec(s.toml)?.[1]).filter((v) => !!v);
|
|
182
|
+
const pinNote = pins.length ? ` (pins kojee-mcp@${[...new Set(pins)].join(", ")})` : "";
|
|
183
|
+
checks.push({
|
|
184
|
+
name: "Codex config shadowing",
|
|
185
|
+
ok: "warn",
|
|
186
|
+
detail: `project-local ${shadowing.map((s) => s.path).join(", ")} defines Kojee/hook config${pinNote} that OVERLAYS the global ~/.codex/config.toml \u2014 Codex may run it instead of the reconnected global entry (split-brain), and this doctor's other checks read only the global file so they may not reflect what Codex actually runs. Reconcile or move the project file(s) aside (e.g. rename to config.toml.bak) so the global reconnect takes effect.`
|
|
187
|
+
});
|
|
188
|
+
} else {
|
|
189
|
+
checks.push({
|
|
190
|
+
name: "Codex config shadowing",
|
|
191
|
+
ok: true,
|
|
192
|
+
detail: "no project-local .codex/config.toml overlaying the global"
|
|
193
|
+
});
|
|
194
|
+
}
|
|
153
195
|
const hooksRaw = readHooksJson() ?? "{}";
|
|
154
196
|
let hooksJson = {};
|
|
155
197
|
try {
|
|
@@ -173,6 +215,39 @@ function collectCodexDoctorReport(deps = {}) {
|
|
|
173
215
|
ok: upsPresent ? true : "warn",
|
|
174
216
|
detail: upsPresent ? "present (pending-wake catch-up at the next prompt)" : `MISSING \u2014 a silently-dropped Stop block is never re-surfaced. ${WIZARD_RERUN}`
|
|
175
217
|
});
|
|
218
|
+
const codexHookCommands = [];
|
|
219
|
+
for (const event of ["Stop", "UserPromptSubmit"]) {
|
|
220
|
+
for (const e of hooksJson.hooks?.[event] ?? []) {
|
|
221
|
+
for (const h of e.hooks ?? []) {
|
|
222
|
+
if (typeof h.command === "string" && isKojeeHookCommand(h.command)) codexHookCommands.push(h.command);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (codexHookCommands.length > 0) {
|
|
227
|
+
const states = codexHookCommands.map((c) => classifyHookCommand(c));
|
|
228
|
+
const repin = "fix: `npm i -g kojee-mcp` then re-run connect for codex (re-pins in place, no re-pair)";
|
|
229
|
+
if (states.includes("pinned-broken")) {
|
|
230
|
+
checks.push({
|
|
231
|
+
name: "hook pin (hooks.json)",
|
|
232
|
+
ok: false,
|
|
233
|
+
detail: `pinned cli.js is GONE \u2014 hooks fail with exit 127 RIGHT NOW; ${repin}`
|
|
234
|
+
});
|
|
235
|
+
} else if (states.includes("pinned-ephemeral")) {
|
|
236
|
+
checks.push({
|
|
237
|
+
name: "hook pin (hooks.json)",
|
|
238
|
+
ok: false,
|
|
239
|
+
detail: `pinned INTO the npx cache (~/.npm/_npx/\u2026) \u2014 cache GC will silently kill the hooks (exit 127 every turn); ${repin}`
|
|
240
|
+
});
|
|
241
|
+
} else if (states.includes("npx")) {
|
|
242
|
+
checks.push({
|
|
243
|
+
name: "hook pin (hooks.json)",
|
|
244
|
+
ok: "warn",
|
|
245
|
+
detail: `unpinned (npx -y) \u2014 pays npx resolution every turn and can stall on a registry fetch; ${repin}`
|
|
246
|
+
});
|
|
247
|
+
} else {
|
|
248
|
+
checks.push({ name: "hook pin (hooks.json)", ok: true, detail: "pinned to a durable binary" });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
176
251
|
const featureHooks = parseCodexFeatureHooks(toml);
|
|
177
252
|
if (featureHooks !== true) {
|
|
178
253
|
checks.push({
|
|
@@ -210,13 +285,13 @@ function collectCodexDoctorReport(deps = {}) {
|
|
|
210
285
|
checks.push({
|
|
211
286
|
name: `hook trust (${event})`,
|
|
212
287
|
ok: "warn",
|
|
213
|
-
detail: `${label} is UNTRUSTED \u2014 no [hooks.state]."${key}" trusted_hash. Codex will NOT run it (wakes silently stop)
|
|
288
|
+
detail: `${label} is UNTRUSTED \u2014 no [hooks.state]."${key}" trusted_hash. Codex will NOT run it (wakes silently stop) \u2014 ${CODEX_TRUST_FIX_STEPS}.`
|
|
214
289
|
});
|
|
215
290
|
} else {
|
|
216
291
|
checks.push({
|
|
217
292
|
name: `hook trust (${event})`,
|
|
218
293
|
ok: "warn",
|
|
219
|
-
detail: `${label} trust is STALE \u2014 trusted_hash no longer matches the current hooks.json entry (a rewrite invalidated it). Codex will NOT run it
|
|
294
|
+
detail: `${label} trust is STALE \u2014 trusted_hash no longer matches the current hooks.json entry (a rewrite invalidated it). Codex will NOT run it \u2014 ${CODEX_TRUST_FIX_STEPS}.`
|
|
220
295
|
});
|
|
221
296
|
}
|
|
222
297
|
}
|
|
@@ -363,6 +438,7 @@ function formatCodexDoctorReport(report) {
|
|
|
363
438
|
}
|
|
364
439
|
export {
|
|
365
440
|
collectCodexDoctorReport,
|
|
441
|
+
defaultDiscoverProjectCodexConfigs,
|
|
366
442
|
formatCodexDoctorReport,
|
|
367
443
|
parseCodexEnvFromToml
|
|
368
444
|
};
|
|
@@ -2,6 +2,10 @@ import {
|
|
|
2
2
|
HERMES_INSTANCE_KEY,
|
|
3
3
|
HERMES_MCP_SERVER_NAME
|
|
4
4
|
} from "./chunk-GEGUWQYT.js";
|
|
5
|
+
import {
|
|
6
|
+
buildFlapDoctorCheck
|
|
7
|
+
} from "./chunk-P64UR3SE.js";
|
|
8
|
+
import "./chunk-SQL56SEB.js";
|
|
5
9
|
|
|
6
10
|
// src/doctor-hermes.ts
|
|
7
11
|
import fs from "fs";
|
|
@@ -513,6 +517,19 @@ function collectHermesDoctorReport(deps = {}) {
|
|
|
513
517
|
);
|
|
514
518
|
}
|
|
515
519
|
}
|
|
520
|
+
const flapCheck = buildFlapDoctorCheck({
|
|
521
|
+
name: "webhook.daemon-flapping",
|
|
522
|
+
// hermes namespaces every check `webhook.*`
|
|
523
|
+
...deps.readFlaps ? { readFlaps: deps.readFlaps } : {},
|
|
524
|
+
...deps.now ? { now: deps.now } : {}
|
|
525
|
+
});
|
|
526
|
+
emit(
|
|
527
|
+
checks,
|
|
528
|
+
collector,
|
|
529
|
+
flapCheck.name,
|
|
530
|
+
flapCheck.ok === "warn" ? "warn" : "ok",
|
|
531
|
+
flapCheck.detail
|
|
532
|
+
);
|
|
516
533
|
return { checks, findings: collector.findings, verdict: collector.verdict() };
|
|
517
534
|
}
|
|
518
535
|
var CHECK_GROUP = {
|
|
@@ -5,7 +5,10 @@ import {
|
|
|
5
5
|
import "./chunk-D6JKFJ6A.js";
|
|
6
6
|
import {
|
|
7
7
|
resolveOpenclawWakeTarget
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LKGKDZSO.js";
|
|
9
|
+
import {
|
|
10
|
+
buildFlapDoctorCheck
|
|
11
|
+
} from "./chunk-P64UR3SE.js";
|
|
9
12
|
import "./chunk-SQL56SEB.js";
|
|
10
13
|
import {
|
|
11
14
|
loadPairedConfig,
|
|
@@ -60,6 +63,7 @@ function collectOpenclawDoctorReport(deps = {}) {
|
|
|
60
63
|
ok: paired !== null,
|
|
61
64
|
detail: paired ? `resolves \u2014 source: ${paired.source} paired slot (token value never printed)` : `NONE resolves (no ~/.kojee/agents/openclaw/config.json or ~/.kojee/config.json token) \u2014 ${WIZARD_RERUN}`
|
|
62
65
|
});
|
|
66
|
+
checks.push(buildFlapDoctorCheck({ ...deps.readFlaps ? { readFlaps: deps.readFlaps } : {}, ...deps.now ? { now: deps.now } : {} }));
|
|
63
67
|
const verdict = checks.some((c) => c.ok === false) ? "broken" : checks.some((c) => c.ok === "warn") ? "degraded" : "healthy";
|
|
64
68
|
return { checks, verdict };
|
|
65
69
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CONNECT_TIMEOUT_MS,
|
|
2
3
|
LOG_HEARTBEAT_INTERVAL_MS,
|
|
3
4
|
UNARMED_FALLBACK_MS,
|
|
4
5
|
UNDICI_DEFAULT_BODY_TIMEOUT_MS,
|
|
@@ -10,10 +11,11 @@ import {
|
|
|
10
11
|
startEventStream,
|
|
11
12
|
statusReason,
|
|
12
13
|
undiciFetch
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-QDQGN5XP.js";
|
|
14
15
|
import "./chunk-Z5LPNJQ6.js";
|
|
15
16
|
import "./chunk-MIEI4PLB.js";
|
|
16
17
|
export {
|
|
18
|
+
CONNECT_TIMEOUT_MS,
|
|
17
19
|
LOG_HEARTBEAT_INTERVAL_MS,
|
|
18
20
|
UNARMED_FALLBACK_MS,
|
|
19
21
|
UNDICI_DEFAULT_BODY_TIMEOUT_MS,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FLAP_WARN_COUNT,
|
|
3
|
+
FLAP_WINDOW_MS,
|
|
4
|
+
MAX_FLAP_ENTRIES,
|
|
5
|
+
buildFlapDoctorCheck,
|
|
6
|
+
defaultFlapLogPath,
|
|
7
|
+
flapRateExceeds,
|
|
8
|
+
readFlaps,
|
|
9
|
+
recentFlapCount,
|
|
10
|
+
recordFlap
|
|
11
|
+
} from "./chunk-P64UR3SE.js";
|
|
12
|
+
import "./chunk-SQL56SEB.js";
|
|
13
|
+
export {
|
|
14
|
+
FLAP_WARN_COUNT,
|
|
15
|
+
FLAP_WINDOW_MS,
|
|
16
|
+
MAX_FLAP_ENTRIES,
|
|
17
|
+
buildFlapDoctorCheck,
|
|
18
|
+
defaultFlapLogPath,
|
|
19
|
+
flapRateExceeds,
|
|
20
|
+
readFlaps,
|
|
21
|
+
recentFlapCount,
|
|
22
|
+
recordFlap
|
|
23
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildHookCommand,
|
|
3
|
-
isKojeeHookCommand
|
|
4
|
-
} from "./chunk-TMCNB4JH.js";
|
|
5
1
|
import {
|
|
6
2
|
wrapNpxLauncher
|
|
7
3
|
} from "./chunk-D6JKFJ6A.js";
|
|
4
|
+
import {
|
|
5
|
+
buildHookCommand,
|
|
6
|
+
isKojeeHookCommand
|
|
7
|
+
} from "./chunk-COM6435C.js";
|
|
8
8
|
import {
|
|
9
9
|
secureFile
|
|
10
10
|
} from "./chunk-U5HHHRXA.js";
|
package/dist/lib.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
} from "./chunk-XLRF5ATG.js";
|
|
6
6
|
import {
|
|
7
7
|
createCachedOpenclawWakeResolver,
|
|
8
|
-
|
|
9
|
-
} from "./chunk-
|
|
8
|
+
postOpenclawAgent
|
|
9
|
+
} from "./chunk-LKGKDZSO.js";
|
|
10
10
|
import "./chunk-PHXO5P25.js";
|
|
11
11
|
import {
|
|
12
12
|
recordDelivered
|
|
@@ -125,7 +125,7 @@ function createClaudeCodeDelivery() {
|
|
|
125
125
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
126
126
|
const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
|
|
127
127
|
const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
|
|
128
|
-
const { startEventStream } = await import("./event-stream-
|
|
128
|
+
const { startEventStream } = await import("./event-stream-KQHPW42G.js");
|
|
129
129
|
const { createMcpServer } = await import("./server-RK4UK6SP.js");
|
|
130
130
|
const { deriveDiscoveryKey } = await import("./ancestry-A2F5KQ6A.js");
|
|
131
131
|
const { resolveSharedSessionId } = await import("./cc-session-id-NNCZCHZZ.js");
|
|
@@ -322,7 +322,7 @@ function createWebhookDelivery(name, pendingLedger) {
|
|
|
322
322
|
const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
|
|
323
323
|
const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
|
|
324
324
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
325
|
-
const { startEventStream } = await import("./event-stream-
|
|
325
|
+
const { startEventStream } = await import("./event-stream-KQHPW42G.js");
|
|
326
326
|
const { createMcpServer } = await import("./server-RK4UK6SP.js");
|
|
327
327
|
sweepStaleEventLogs();
|
|
328
328
|
eventLog = startEventLog({
|
|
@@ -500,14 +500,13 @@ function createCodexDelivery(onDelivered) {
|
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
// src/delivery/openclaw.ts
|
|
503
|
-
function
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
return `New Kojee Tandem activity in ${event.tandem_id} from ${who}: "${body}". Catch up with tandem_messages / tandem_listen and reply via tandem_send.`;
|
|
503
|
+
function formatOpenclawAgentPrompt(event) {
|
|
504
|
+
const since = Math.max(0, (event.cursor ?? 1) - 1);
|
|
505
|
+
return `You were mentioned in Kojee Tandem ${event.tandem_id}. Read it to see who and what \u2014 tandem_messages(tandem_id="${event.tandem_id}", since=${since}) \u2014 then REPLY IN THE FORUM now with tandem_send(tandem_id="${event.tandem_id}", body=..., reply_to="${event.id}"). Do this now; don't just read or ack.`;
|
|
507
506
|
}
|
|
508
507
|
function createOpenclawDelivery(deps = {}) {
|
|
509
508
|
const resolveTarget = deps.resolveTarget ?? createCachedOpenclawWakeResolver();
|
|
510
|
-
const
|
|
509
|
+
const postAgent = deps.postAgent ?? postOpenclawAgent;
|
|
511
510
|
let eventLog = null;
|
|
512
511
|
let streamHandle = null;
|
|
513
512
|
let warnedUnconfigured = false;
|
|
@@ -516,7 +515,7 @@ function createOpenclawDelivery(deps = {}) {
|
|
|
516
515
|
async start(ctx) {
|
|
517
516
|
const { startEventLog, sweepStaleEventLogs } = await import("./event-log-2NBJEIEP.js");
|
|
518
517
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
519
|
-
const { startEventStream } = await import("./event-stream-
|
|
518
|
+
const { startEventStream } = await import("./event-stream-KQHPW42G.js");
|
|
520
519
|
const { createMcpServer } = await import("./server-RK4UK6SP.js");
|
|
521
520
|
sweepStaleEventLogs();
|
|
522
521
|
eventLog = startEventLog({
|
|
@@ -586,8 +585,13 @@ function createOpenclawDelivery(deps = {}) {
|
|
|
586
585
|
return;
|
|
587
586
|
}
|
|
588
587
|
warnedUnconfigured = false;
|
|
589
|
-
const res = await
|
|
590
|
-
|
|
588
|
+
const res = await postAgent(r.target, {
|
|
589
|
+
message: formatOpenclawAgentPrompt(event),
|
|
590
|
+
channel: "last",
|
|
591
|
+
name: "Kojee",
|
|
592
|
+
wakeMode: "now"
|
|
593
|
+
});
|
|
594
|
+
if (!res.ok) console.error(`[openclaw] agent wake POST failed: ${res.error ?? res.status}`);
|
|
591
595
|
})().catch((err) => {
|
|
592
596
|
console.error("[openclaw] wake delivery failed:", err);
|
|
593
597
|
});
|
|
@@ -614,7 +618,7 @@ function selectDelivery(runtime, opts) {
|
|
|
614
618
|
return createCodexDelivery(opts.onDelivered);
|
|
615
619
|
}
|
|
616
620
|
if (runtime === "openclaw") {
|
|
617
|
-
return createOpenclawDelivery();
|
|
621
|
+
return opts.headless ? createOpenclawDelivery() : null;
|
|
618
622
|
}
|
|
619
623
|
if (!needsWebhookEventStream(opts.env)) {
|
|
620
624
|
return null;
|
|
@@ -3,17 +3,17 @@ import {
|
|
|
3
3
|
pairSlotFor,
|
|
4
4
|
reconcileConnectPairSlot,
|
|
5
5
|
runWizard
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-7DCZNZSP.js";
|
|
7
|
+
import "./chunk-7LKCTUBY.js";
|
|
8
8
|
import "./chunk-E6WMFMM2.js";
|
|
9
9
|
import "./chunk-GEGUWQYT.js";
|
|
10
|
+
import "./chunk-EIH2LNF4.js";
|
|
11
|
+
import "./chunk-RDLF4NQC.js";
|
|
10
12
|
import {
|
|
11
13
|
isWizardRuntime
|
|
12
14
|
} from "./chunk-77HWBSRH.js";
|
|
13
|
-
import "./chunk-TMCNB4JH.js";
|
|
14
15
|
import "./chunk-D6JKFJ6A.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-RDLF4NQC.js";
|
|
16
|
+
import "./chunk-COM6435C.js";
|
|
17
17
|
import "./chunk-SQL56SEB.js";
|
|
18
18
|
import "./chunk-V5VZPYMZ.js";
|
|
19
19
|
import {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/runtime/stream-watchdog.ts
|
|
2
|
+
var DEFAULT_STREAM_THRESHOLD_MS = 9e4;
|
|
3
|
+
var DEFAULT_STREAM_INTERVAL_MS = 15e3;
|
|
4
|
+
function createStreamWatchdog(opts) {
|
|
5
|
+
const thresholdMs = opts.thresholdMs ?? DEFAULT_STREAM_THRESHOLD_MS;
|
|
6
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_STREAM_INTERVAL_MS;
|
|
7
|
+
const now = opts.now ?? Date.now;
|
|
8
|
+
let timer = null;
|
|
9
|
+
let fired = false;
|
|
10
|
+
let lastHealthyAt = now();
|
|
11
|
+
let lastSeenEventAt = null;
|
|
12
|
+
function stop() {
|
|
13
|
+
if (timer !== null) {
|
|
14
|
+
clearInterval(timer);
|
|
15
|
+
timer = null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function check() {
|
|
19
|
+
if (fired) return;
|
|
20
|
+
let state;
|
|
21
|
+
try {
|
|
22
|
+
state = opts.getState();
|
|
23
|
+
} catch {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const t = now();
|
|
27
|
+
const eventAdvanced = state.lastEventAt !== null && state.lastEventAt !== lastSeenEventAt;
|
|
28
|
+
if (state.lastEventAt !== null) lastSeenEventAt = state.lastEventAt;
|
|
29
|
+
if (state.connected || eventAdvanced) {
|
|
30
|
+
lastHealthyAt = t;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const disconnectedForMs = t - lastHealthyAt;
|
|
34
|
+
if (disconnectedForMs > thresholdMs) {
|
|
35
|
+
fired = true;
|
|
36
|
+
stop();
|
|
37
|
+
try {
|
|
38
|
+
opts.onUnhealthy({
|
|
39
|
+
disconnectedForMs,
|
|
40
|
+
reason: `SSE stream unhealthy for ${Math.round(disconnectedForMs / 1e3)}s (connected=false, no new events) \u2014 reconnect loop presumed wedged`
|
|
41
|
+
});
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
start() {
|
|
48
|
+
if (timer !== null || fired) return;
|
|
49
|
+
timer = setInterval(() => check(), intervalMs);
|
|
50
|
+
timer.unref?.();
|
|
51
|
+
},
|
|
52
|
+
stop
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
DEFAULT_STREAM_INTERVAL_MS,
|
|
57
|
+
DEFAULT_STREAM_THRESHOLD_MS,
|
|
58
|
+
createStreamWatchdog
|
|
59
|
+
};
|
package/package.json
CHANGED
package/dist/chunk-TMCNB4JH.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// src/hooks/hook-command.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
function npxHookCommand(type) {
|
|
6
|
-
return `npx -y kojee-mcp hook --type=${type}`;
|
|
7
|
-
}
|
|
8
|
-
function resolveCliEntry(moduleUrl = import.meta.url) {
|
|
9
|
-
try {
|
|
10
|
-
const here = fileURLToPath(moduleUrl);
|
|
11
|
-
const candidate = path.join(path.dirname(here), "cli.js");
|
|
12
|
-
return fs.existsSync(candidate) ? candidate : null;
|
|
13
|
-
} catch {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function buildHookCommand(type, opts = {}) {
|
|
18
|
-
const cliEntry = opts.cliEntry !== void 0 ? opts.cliEntry : resolveCliEntry();
|
|
19
|
-
if (!cliEntry) return npxHookCommand(type);
|
|
20
|
-
const execPath = opts.execPath ?? process.execPath;
|
|
21
|
-
return `"${execPath}" "${cliEntry}" hook --type=${type}`;
|
|
22
|
-
}
|
|
23
|
-
var PINNED_HOOK_RE = /^"[^"]+" "[^"]+" hook --type=(stop|user-prompt-submit|codex-stop|codex-prompt-submit)$/;
|
|
24
|
-
function isKojeeHookCommand(command) {
|
|
25
|
-
if (command.startsWith("npx -y kojee-mcp hook --type=")) return true;
|
|
26
|
-
return PINNED_HOOK_RE.test(command);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export {
|
|
30
|
-
npxHookCommand,
|
|
31
|
-
buildHookCommand,
|
|
32
|
-
isKojeeHookCommand
|
|
33
|
-
};
|