kojee-mcp 0.7.5-staging.8 → 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-ZUF47CPP.js → chunk-7DCZNZSP.js} +7 -6
- package/dist/{chunk-376ZI3J7.js → chunk-7LKCTUBY.js} +2 -0
- package/dist/{chunk-HRDASFRF.js → chunk-EF466Y7P.js} +28 -1
- package/dist/chunk-P64UR3SE.js +86 -0
- package/dist/{chunk-CUOJHP2S.js → chunk-QDQGN5XP.js} +12 -4
- package/dist/cli.js +6 -6
- package/dist/{connect-handler-NJ4FZIAM.js → connect-handler-J5RA3EJJ.js} +5 -5
- package/dist/{doctor-KMYKRYPE.js → doctor-7Y7PWE54.js} +3 -3
- package/dist/{doctor-codex-HB442SFA.js → doctor-codex-OEYUM7DE.js} +45 -5
- package/dist/{doctor-hermes-WLDI4R6P.js → doctor-hermes-KMHM27Y3.js} +17 -0
- package/dist/{doctor-openclaw-IWY64I2E.js → doctor-openclaw-B5KADVHD.js} +4 -0
- 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/lib.js +1 -1
- package/dist/{registry-NLRXVII7.js → registry-PNL4VGNK.js} +3 -3
- package/dist/{setup-handler-I7ZEZTKW.js → setup-handler-VWP6D74B.js} +4 -4
- 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
|
@@ -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";
|
|
@@ -972,7 +973,7 @@ function configureCodex(opts) {
|
|
|
972
973
|
lines.push("");
|
|
973
974
|
if (writeResult.hooksChanged) {
|
|
974
975
|
lines.push(
|
|
975
|
-
|
|
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}.`
|
|
976
977
|
);
|
|
977
978
|
lines.push("");
|
|
978
979
|
}
|
|
@@ -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,
|
|
@@ -296,7 +296,7 @@ async function startProxy(config) {
|
|
|
296
296
|
console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
|
|
297
297
|
let server;
|
|
298
298
|
const isHeadlessDaemon = !!process.env["KOJEE_HEADLESS"] || !!process.env["KOJEE_WEBHOOK_EXPECTED"];
|
|
299
|
-
const { selectDelivery } = await import("./registry-
|
|
299
|
+
const { selectDelivery } = await import("./registry-PNL4VGNK.js");
|
|
300
300
|
const delivery = selectDelivery(adapter.runtime, {
|
|
301
301
|
supportsChannels: adapter.supportsChannels,
|
|
302
302
|
headless: isHeadlessDaemon,
|
|
@@ -364,6 +364,33 @@ async function startProxy(config) {
|
|
|
364
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."
|
|
365
365
|
);
|
|
366
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
|
+
}
|
|
367
394
|
await startMcpServer(server);
|
|
368
395
|
}
|
|
369
396
|
async function enrollAndDiscover(config, keystorePath, isRetry = false) {
|
|
@@ -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,
|
|
@@ -60,7 +60,7 @@ program.command("connect <code>").description(
|
|
|
60
60
|
program.command("reconnect").description(
|
|
61
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
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-
|
|
63
|
+
const { runReconnect } = await import("./connect-handler-J5RA3EJJ.js");
|
|
64
64
|
const result = await runReconnect({ ...opts.runtime ? { runtime: opts.runtime } : {} });
|
|
65
65
|
for (const m of result.messages) console.error(m);
|
|
66
66
|
console.error(result.output);
|
|
@@ -126,7 +126,7 @@ program.command("tail <path>").description("Stream a file's contents and follow
|
|
|
126
126
|
"--wake-only",
|
|
127
127
|
"Suppress routine heartbeat warn/recover telemetry; surface only a sustained stream outage"
|
|
128
128
|
).action(async (filePath, opts) => {
|
|
129
|
-
const { runTail } = await import("./tail-stream-
|
|
129
|
+
const { runTail } = await import("./tail-stream-EIMLRRUP.js");
|
|
130
130
|
try {
|
|
131
131
|
await runTail(filePath, { wakeOnly: opts.wakeOnly === true });
|
|
132
132
|
} catch (err) {
|
|
@@ -135,7 +135,7 @@ program.command("tail <path>").description("Stream a file's contents and follow
|
|
|
135
135
|
}
|
|
136
136
|
});
|
|
137
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 () => {
|
|
138
|
-
const { runDoctor } = await import("./doctor-
|
|
138
|
+
const { runDoctor } = await import("./doctor-7Y7PWE54.js");
|
|
139
139
|
const code = await runDoctor();
|
|
140
140
|
process.exit(code);
|
|
141
141
|
});
|
|
@@ -154,7 +154,7 @@ function addInstallOptions(cmd, runtimeHelp) {
|
|
|
154
154
|
function makeInstallAction(verb) {
|
|
155
155
|
return async (opts) => {
|
|
156
156
|
const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
|
|
157
|
-
const { runSetup, resolvePairCode } = await import("./setup-handler-
|
|
157
|
+
const { runSetup, resolvePairCode } = await import("./setup-handler-VWP6D74B.js");
|
|
158
158
|
const pairCode = resolvePairCode(opts);
|
|
159
159
|
const result = await runSetup({
|
|
160
160
|
verb,
|
|
@@ -2,19 +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-D6JKFJ6A.js";
|
|
11
|
-
import "./chunk-COM6435C.js";
|
|
12
9
|
import {
|
|
13
10
|
CONNECT_RUNTIMES
|
|
14
11
|
} from "./chunk-EIH2LNF4.js";
|
|
15
12
|
import {
|
|
16
13
|
readRecordedRuntime
|
|
17
14
|
} from "./chunk-RDLF4NQC.js";
|
|
15
|
+
import "./chunk-77HWBSRH.js";
|
|
16
|
+
import "./chunk-D6JKFJ6A.js";
|
|
17
|
+
import "./chunk-COM6435C.js";
|
|
18
18
|
import "./chunk-SQL56SEB.js";
|
|
19
19
|
import "./chunk-V5VZPYMZ.js";
|
|
20
20
|
import {
|
|
@@ -407,18 +407,18 @@ function formatDoctorReport(report) {
|
|
|
407
407
|
async function runDoctor() {
|
|
408
408
|
const { readRecordedRuntime } = await import("./runtime-record-OXRLTOLC.js");
|
|
409
409
|
if (readRecordedRuntime() === "codex") {
|
|
410
|
-
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-
|
|
410
|
+
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-OEYUM7DE.js");
|
|
411
411
|
const report2 = collectCodexDoctorReport();
|
|
412
412
|
console.error(formatCodexDoctorReport(report2));
|
|
413
413
|
return report2.verdict === "broken" ? 1 : 0;
|
|
414
414
|
}
|
|
415
415
|
if (readRecordedRuntime() === "openclaw") {
|
|
416
|
-
const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-
|
|
416
|
+
const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-B5KADVHD.js");
|
|
417
417
|
const report2 = collectOpenclawDoctorReport();
|
|
418
418
|
console.error(formatOpenclawDoctorReport(report2));
|
|
419
419
|
return report2.verdict === "broken" ? 1 : 0;
|
|
420
420
|
}
|
|
421
|
-
const { hermesArtifactsPresent, collectHermesDoctorReport, formatHermesDoctorReport } = await import("./doctor-hermes-
|
|
421
|
+
const { hermesArtifactsPresent, collectHermesDoctorReport, formatHermesDoctorReport } = await import("./doctor-hermes-KMHM27Y3.js");
|
|
422
422
|
if (hermesArtifactsPresent()) {
|
|
423
423
|
const hermesReport = collectHermesDoctorReport();
|
|
424
424
|
console.error(formatHermesDoctorReport(hermesReport));
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CODEX_TRUST_FIX_STEPS,
|
|
2
3
|
defaultCodexConfigPath,
|
|
3
4
|
defaultCodexHooksPath,
|
|
4
5
|
isPlaceholderWebhookUrl
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7LKCTUBY.js";
|
|
6
7
|
import "./chunk-D6JKFJ6A.js";
|
|
7
8
|
import {
|
|
8
9
|
classifyHookCommand,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
|
|
26
27
|
// src/doctor-codex.ts
|
|
27
28
|
import fs from "fs";
|
|
29
|
+
import path from "path";
|
|
28
30
|
|
|
29
31
|
// src/hooks/codex-trust-hash.ts
|
|
30
32
|
import crypto from "crypto";
|
|
@@ -130,15 +132,33 @@ function extractPairedConfigPath(toml) {
|
|
|
130
132
|
const m = toml.match(/args\s*=\s*\[[^\]]*"--paired-config"\s*,\s*"([^"]+)"/);
|
|
131
133
|
return m ? m[1].replace(/\\\\/g, "\\") : null;
|
|
132
134
|
}
|
|
133
|
-
function defaultReadFile(
|
|
135
|
+
function defaultReadFile(filePath) {
|
|
134
136
|
return () => {
|
|
135
137
|
try {
|
|
136
|
-
return fs.readFileSync(
|
|
138
|
+
return fs.readFileSync(filePath, "utf8");
|
|
137
139
|
} catch {
|
|
138
140
|
return null;
|
|
139
141
|
}
|
|
140
142
|
};
|
|
141
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
|
+
}
|
|
142
162
|
function collectCodexDoctorReport(deps = {}) {
|
|
143
163
|
const readConfigToml = deps.readConfigToml ?? defaultReadFile(defaultCodexConfigPath());
|
|
144
164
|
const readHooksJson = deps.readHooksJson ?? defaultReadFile(defaultCodexHooksPath());
|
|
@@ -153,6 +173,25 @@ function collectCodexDoctorReport(deps = {}) {
|
|
|
153
173
|
ok: configOk,
|
|
154
174
|
detail: configOk ? 'present with env.KOJEE_RUNTIME="codex" (Tier-1 contract)' : `MISSING [mcp_servers.kojee] or env.KOJEE_RUNTIME="codex" \u2014 ${WIZARD_RERUN}`
|
|
155
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
|
+
}
|
|
156
195
|
const hooksRaw = readHooksJson() ?? "{}";
|
|
157
196
|
let hooksJson = {};
|
|
158
197
|
try {
|
|
@@ -246,13 +285,13 @@ function collectCodexDoctorReport(deps = {}) {
|
|
|
246
285
|
checks.push({
|
|
247
286
|
name: `hook trust (${event})`,
|
|
248
287
|
ok: "warn",
|
|
249
|
-
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}.`
|
|
250
289
|
});
|
|
251
290
|
} else {
|
|
252
291
|
checks.push({
|
|
253
292
|
name: `hook trust (${event})`,
|
|
254
293
|
ok: "warn",
|
|
255
|
-
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}.`
|
|
256
295
|
});
|
|
257
296
|
}
|
|
258
297
|
}
|
|
@@ -399,6 +438,7 @@ function formatCodexDoctorReport(report) {
|
|
|
399
438
|
}
|
|
400
439
|
export {
|
|
401
440
|
collectCodexDoctorReport,
|
|
441
|
+
defaultDiscoverProjectCodexConfigs,
|
|
402
442
|
formatCodexDoctorReport,
|
|
403
443
|
parseCodexEnvFromToml
|
|
404
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 = {
|
|
@@ -6,6 +6,9 @@ import "./chunk-D6JKFJ6A.js";
|
|
|
6
6
|
import {
|
|
7
7
|
resolveOpenclawWakeTarget
|
|
8
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
package/dist/lib.js
CHANGED
|
@@ -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({
|
|
@@ -515,7 +515,7 @@ function createOpenclawDelivery(deps = {}) {
|
|
|
515
515
|
async start(ctx) {
|
|
516
516
|
const { startEventLog, sweepStaleEventLogs } = await import("./event-log-2NBJEIEP.js");
|
|
517
517
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
518
|
-
const { startEventStream } = await import("./event-stream-
|
|
518
|
+
const { startEventStream } = await import("./event-stream-KQHPW42G.js");
|
|
519
519
|
const { createMcpServer } = await import("./server-RK4UK6SP.js");
|
|
520
520
|
sweepStaleEventLogs();
|
|
521
521
|
eventLog = startEventLog({
|
|
@@ -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
15
|
import "./chunk-D6JKFJ6A.js";
|
|
14
16
|
import "./chunk-COM6435C.js";
|
|
15
|
-
import "./chunk-EIH2LNF4.js";
|
|
16
|
-
import "./chunk-RDLF4NQC.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
|
+
};
|