kojee-mcp 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -317,11 +317,42 @@ async function startProxy(config) {
317
317
  `[kojee-mcp] Ready \u2014 ${registry.toolCount} tools available from ${config.url}`
318
318
  );
319
319
  let activeStreamHandle = null;
320
- const { createJoinReconnectScheduler } = await import("./reconnect-scheduler-JSXCJKQP.js");
320
+ const { createJoinReconnectScheduler } = await import("./reconnect-scheduler-ARV6JIWK.js");
321
321
  const joinReconnect = createJoinReconnectScheduler({
322
- reconnect: () => activeStreamHandle?.reconnect()
322
+ // BOOT-RACE (Bug B): report whether the stream handle was actually ready.
323
+ // `false` ⇒ activeStreamHandle is still null (tandem_join fired before the
324
+ // stream was set up) → the scheduler queues the reconnect and flushes it on
325
+ // notifyReady() once the handle is assigned (see below), instead of silently
326
+ // dropping it as the old `activeStreamHandle?.reconnect()` no-op did.
327
+ reconnect: () => {
328
+ if (!activeStreamHandle) return false;
329
+ activeStreamHandle.reconnect();
330
+ return true;
331
+ }
323
332
  });
324
333
  const onTandemJoin = () => joinReconnect.requestReconnect();
334
+ const teardownSteps = [];
335
+ let shuttingDown = false;
336
+ function shutdown(reason) {
337
+ if (shuttingDown) return;
338
+ shuttingDown = true;
339
+ activeStreamHandle?.();
340
+ for (const step of teardownSteps) {
341
+ try {
342
+ const maybe = step();
343
+ if (maybe && typeof maybe.catch === "function") {
344
+ maybe.catch((err) => {
345
+ console.error("[kojee-mcp] async shutdown step failed:", err?.message ?? err);
346
+ });
347
+ }
348
+ } catch (err) {
349
+ console.error("[kojee-mcp] shutdown step failed:", err?.message ?? err);
350
+ }
351
+ }
352
+ console.error(`[kojee-mcp] shutting down (${reason}), exiting`);
353
+ process.exit(0);
354
+ }
355
+ const ccPid = await findClaudeAncestorPid();
325
356
  const { ensureJoinTandems } = await import("./ensure-join-7AEDJMPE.js");
326
357
  await ensureJoinTandems({
327
358
  gateway,
@@ -352,7 +383,6 @@ async function startProxy(config) {
352
383
  const { createWebhookSink } = await import("./webhook-sink-NWGCUDGY.js");
353
384
  sweepStaleDiscovery();
354
385
  sweepStaleEventLogs();
355
- const ccPid = await findClaudeAncestorPid();
356
386
  const projectDir = process.env["CLAUDE_PROJECT_DIR"];
357
387
  const discoveryKey = deriveDiscoveryKey(projectDir, ccPid);
358
388
  const eventLog = startEventLog({ key: discoveryKey });
@@ -436,16 +466,16 @@ async function startProxy(config) {
436
466
  authMode: config.authMode ?? "paired"
437
467
  });
438
468
  const cleanupDiscoveryFile = () => cleanupDiscoveryByKey(discoveryKey);
439
- process.on("exit", () => cleanupDiscoveryFile());
440
- process.on("SIGINT", () => {
469
+ process.on("exit", () => {
441
470
  cleanupDiscoveryFile();
442
- process.exit(0);
471
+ eventLog.cleanup();
443
472
  });
444
- process.on("SIGTERM", () => {
445
- cleanupDiscoveryFile();
446
- process.exit(0);
473
+ teardownSteps.push(() => {
474
+ void webhookSink?.stop();
447
475
  });
448
- process.on("exit", () => eventLog.cleanup());
476
+ teardownSteps.push(() => cleanupDiscoveryFile());
477
+ teardownSteps.push(() => eventLog.cleanup());
478
+ teardownSteps.push(() => hookServer.stop());
449
479
  streamHandle = await startEventStream({
450
480
  brokerUrl: config.url,
451
481
  token: config.token,
@@ -482,24 +512,13 @@ async function startProxy(config) {
482
512
  })()
483
513
  });
484
514
  activeStreamHandle = streamHandle;
485
- const cancelStream = streamHandle;
486
- process.stdin.on("end", () => {
487
- cancelStream();
488
- void webhookSink?.stop();
489
- cleanupDiscoveryFile();
490
- eventLog.cleanup();
491
- hookServer.stop().finally(() => {
492
- console.error("[kojee-mcp] stdin closed, exiting");
493
- process.exit(0);
494
- });
495
- });
515
+ joinReconnect.notifyReady();
496
516
  } else if (needsWebhookEventStream()) {
497
517
  const { startEventLog, sweepStaleEventLogs } = await import("./event-log-RSTM4PLL.js");
498
518
  const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
499
519
  const { createWebhookSink } = await import("./webhook-sink-NWGCUDGY.js");
500
520
  const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
501
521
  sweepStaleEventLogs();
502
- const ccPid = await findClaudeAncestorPid();
503
522
  const projectDir = process.env["CLAUDE_PROJECT_DIR"];
504
523
  const discoveryKey = deriveDiscoveryKey(projectDir, ccPid);
505
524
  const eventLog = startEventLog({ key: discoveryKey });
@@ -529,6 +548,10 @@ async function startProxy(config) {
529
548
  onTandemJoin
530
549
  });
531
550
  process.on("exit", () => eventLog.cleanup());
551
+ teardownSteps.push(() => {
552
+ void webhookSink?.stop();
553
+ });
554
+ teardownSteps.push(() => eventLog.cleanup());
532
555
  const streamHandle = await startEventStream({
533
556
  brokerUrl: config.url,
534
557
  token: config.token,
@@ -550,19 +573,27 @@ async function startProxy(config) {
550
573
  })()
551
574
  });
552
575
  activeStreamHandle = streamHandle;
553
- process.stdin.on("end", () => {
554
- streamHandle();
555
- void webhookSink?.stop();
556
- eventLog.cleanup();
557
- console.error("[kojee-mcp] stdin closed, exiting");
558
- process.exit(0);
559
- });
576
+ joinReconnect.notifyReady();
560
577
  } else {
561
578
  server = createMcpServer(registry, adapter, tandemMembershipCount);
562
- process.stdin.on("end", () => {
563
- console.error("[kojee-mcp] stdin closed, exiting");
564
- process.exit(0);
579
+ }
580
+ process.stdin.on("end", () => shutdown("stdin end"));
581
+ process.stdin.on("close", () => shutdown("stdin close"));
582
+ process.on("SIGHUP", () => shutdown("SIGHUP"));
583
+ process.on("SIGINT", () => shutdown("SIGINT"));
584
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
585
+ if (ccPid !== null) {
586
+ const { createParentWatchdog } = await import("./parent-watchdog-RZLHYP7T.js");
587
+ const watchdog = createParentWatchdog({
588
+ ccPid,
589
+ onParentGone: () => shutdown("parent (Claude Code) gone")
565
590
  });
591
+ watchdog.start();
592
+ teardownSteps.push(() => watchdog.stop());
593
+ } else {
594
+ console.error(
595
+ "[kojee-mcp] no Claude Code ancestor found \u2014 parent-liveness watchdog NOT armed (stdin/signal handlers still cover clean exits)"
596
+ );
566
597
  }
567
598
  await startMcpServer(server);
568
599
  }
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  VERSION,
4
4
  startProxy
5
- } from "./chunk-2BDAM3TH.js";
5
+ } from "./chunk-GATXJ6UT.js";
6
6
  import "./chunk-X672ZN7V.js";
7
7
  import "./chunk-BJMASMKX.js";
8
8
  import {
@@ -83,7 +83,7 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
83
83
  });
84
84
  program.command("hook").description("Run a kojee MCP hook script (called by Claude Code via ~/.claude/settings.json)").requiredOption("--type <type>", "Hook type: stop, user-prompt-submit, or codex-stop").action(async (opts) => {
85
85
  if (opts.type === "stop") {
86
- const { runStopHook } = await import("./stop-hook-TRAMQYNE.js");
86
+ const { runStopHook } = await import("./stop-hook-46BJD55B.js");
87
87
  await runStopHook();
88
88
  process.exit(0);
89
89
  } else if (opts.type === "user-prompt-submit") {
@@ -158,7 +158,7 @@ program.command("init").description(
158
158
  console.error("Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run `init`.");
159
159
  process.exit(1);
160
160
  }
161
- const { runWizard } = await import("./wizard-OSOAY4GO.js");
161
+ const { runWizard } = await import("./wizard-UOXQYJLP.js");
162
162
  const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
163
163
  const result = await runWizard({
164
164
  ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  listTandemIds,
3
3
  startProxy
4
- } from "./chunk-2BDAM3TH.js";
4
+ } from "./chunk-GATXJ6UT.js";
5
5
  import "./chunk-X672ZN7V.js";
6
6
  import "./chunk-BJMASMKX.js";
7
7
  import "./chunk-6SK6ITFE.js";
@@ -0,0 +1,65 @@
1
+ import {
2
+ findClaudeAncestorPid
3
+ } from "./chunk-BJMASMKX.js";
4
+
5
+ // src/runtime/parent-watchdog.ts
6
+ function defaultIsPidAlive(pid) {
7
+ try {
8
+ process.kill(pid, 0);
9
+ return true;
10
+ } catch (err) {
11
+ if (err.code === "EPERM") return true;
12
+ return false;
13
+ }
14
+ }
15
+ function createParentWatchdog(opts) {
16
+ const intervalMs = opts.intervalMs ?? 7e3;
17
+ const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
18
+ const resolveAncestor = opts.resolveAncestor ?? (() => findClaudeAncestorPid());
19
+ let timer = null;
20
+ let fired = false;
21
+ let checking = false;
22
+ function stop() {
23
+ if (timer !== null) {
24
+ clearInterval(timer);
25
+ timer = null;
26
+ }
27
+ }
28
+ async function check() {
29
+ if (fired || checking) return;
30
+ checking = true;
31
+ try {
32
+ const ccPidAlive = isPidAlive(opts.ccPid);
33
+ if (ccPidAlive) return;
34
+ let ancestor = null;
35
+ try {
36
+ ancestor = await resolveAncestor();
37
+ } catch {
38
+ ancestor = null;
39
+ }
40
+ if (ancestor !== null) return;
41
+ fired = true;
42
+ stop();
43
+ try {
44
+ opts.onParentGone();
45
+ } catch {
46
+ }
47
+ } finally {
48
+ checking = false;
49
+ }
50
+ }
51
+ return {
52
+ start() {
53
+ if (timer !== null || fired) return;
54
+ timer = setInterval(() => {
55
+ void check();
56
+ }, intervalMs);
57
+ timer.unref?.();
58
+ },
59
+ stop
60
+ };
61
+ }
62
+ export {
63
+ createParentWatchdog,
64
+ defaultIsPidAlive
65
+ };
@@ -3,21 +3,31 @@ var DEFAULT_DEBOUNCE_MS = 1e3;
3
3
  function createJoinReconnectScheduler(opts) {
4
4
  const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
5
5
  let pending = null;
6
+ let queued = false;
7
+ function fire() {
8
+ try {
9
+ return opts.reconnect() !== false;
10
+ } catch (err) {
11
+ console.error(
12
+ "[join-reconnect] reconnect action failed:",
13
+ err?.message ?? String(err)
14
+ );
15
+ return true;
16
+ }
17
+ }
6
18
  return {
7
19
  requestReconnect() {
8
20
  if (pending !== null) return;
9
21
  pending = setTimeout(() => {
10
22
  pending = null;
11
- try {
12
- opts.reconnect();
13
- } catch (err) {
14
- console.error(
15
- "[join-reconnect] reconnect action failed:",
16
- err?.message ?? String(err)
17
- );
18
- }
23
+ if (!fire()) queued = true;
19
24
  }, debounceMs);
20
25
  pending.unref?.();
26
+ },
27
+ notifyReady() {
28
+ if (!queued) return;
29
+ queued = false;
30
+ if (!fire()) queued = true;
21
31
  }
22
32
  };
23
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -1,10 +1,10 @@
1
+ import {
2
+ readHookStdin
3
+ } from "./chunk-LSUB6QMP.js";
1
4
  import {
2
5
  monitorHeartbeatPath,
3
6
  nudgeSentinelPath
4
7
  } from "./chunk-2TUAFAIW.js";
5
- import {
6
- readHookStdin
7
- } from "./chunk-LSUB6QMP.js";
8
8
  import {
9
9
  controlTokenAuthHeaders
10
10
  } from "./chunk-GI2CKKBL.js";
@@ -1,3 +1,7 @@
1
+ import {
2
+ WIZARD_RUNTIMES,
3
+ isWizardRuntime
4
+ } from "./chunk-LVL25VLO.js";
1
5
  import {
2
6
  clearRuntimeRecord,
3
7
  readRecordedRuntime,
@@ -12,10 +16,6 @@ import {
12
16
  import {
13
17
  kojeeHomeDir
14
18
  } from "./chunk-SQL56SEB.js";
15
- import {
16
- WIZARD_RUNTIMES,
17
- isWizardRuntime
18
- } from "./chunk-LVL25VLO.js";
19
19
  import {
20
20
  resolveSignatureEmission,
21
21
  resolveWebhookConfig