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.
@@ -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-376ZI3J7.js";
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
- "IMPORTANT: Codex will re-prompt to trust hooks on its next launch \u2014 approve it, or wakes silently stop."
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-NLRXVII7.js");
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
- let res = await openStream(opts, controller, mapSince, dispatcher);
169
- if (res.status === 400 && mapSince !== null) {
170
- console.error("[event-stream] per-room ?since rejected (400) \u2014 falling back to bare cursor");
171
- res = await openStream(opts, controller, String(Math.min(...cursors.values())), dispatcher);
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-HRDASFRF.js";
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-NJ4FZIAM.js");
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-NJ4FZIAM.js");
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-HG4BKJOG.js");
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-KMYKRYPE.js");
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-I7ZEZTKW.js");
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-ZUF47CPP.js";
6
- import "./chunk-376ZI3J7.js";
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-HB442SFA.js");
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-IWY64I2E.js");
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-WLDI4R6P.js");
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-376ZI3J7.js";
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(path) {
135
+ function defaultReadFile(filePath) {
134
136
  return () => {
135
137
  try {
136
- return fs.readFileSync(path, "utf8");
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) until you launch Codex and APPROVE its trust prompt.`
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 until you re-approve the trust prompt.`
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-CUOJHP2S.js";
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,7 +1,7 @@
1
1
  import {
2
2
  listTandemIds,
3
3
  startProxy
4
- } from "./chunk-HRDASFRF.js";
4
+ } from "./chunk-EF466Y7P.js";
5
5
  import "./chunk-XYDNRVOU.js";
6
6
  import "./chunk-TBVJOXIR.js";
7
7
  import "./chunk-Z5LPNJQ6.js";
package/dist/lib.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  normalizeBackendEvent,
3
3
  sanitizeDisplayname,
4
4
  startEventStream
5
- } from "./chunk-CUOJHP2S.js";
5
+ } from "./chunk-QDQGN5XP.js";
6
6
  import {
7
7
  resubscribeMemberships
8
8
  } from "./chunk-OT2GILXC.js";
@@ -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-ATKYXDBV.js");
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-ATKYXDBV.js");
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-ATKYXDBV.js");
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-ZUF47CPP.js";
7
- import "./chunk-376ZI3J7.js";
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
+ };
@@ -9,7 +9,7 @@ import {
9
9
  LOG_HEARTBEAT_INTERVAL_MS,
10
10
  UNARMED_FALLBACK_MS,
11
11
  createAdaptiveWatchdog
12
- } from "./chunk-CUOJHP2S.js";
12
+ } from "./chunk-QDQGN5XP.js";
13
13
  import "./chunk-Z5LPNJQ6.js";
14
14
  import "./chunk-MIEI4PLB.js";
15
15
  import "./chunk-U5HHHRXA.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.7.5-staging.8",
3
+ "version": "0.7.5-staging.9",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {