kojee-mcp 0.7.5-staging.2 → 0.7.5-staging.21

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.
Files changed (48) hide show
  1. package/README.md +27 -6
  2. package/dist/{cc-session-id-RURNIHHC.js → cc-session-id-5HHQJ7VC.js} +3 -1
  3. package/dist/{chunk-ZSHCHOPL.js → chunk-676WQSWZ.js} +163 -7
  4. package/dist/{chunk-FJUAMJHU.js → chunk-6NPMVZD7.js} +38 -3
  5. package/dist/{chunk-247WFMCJ.js → chunk-AGZ7ZGLY.js} +30 -3
  6. package/dist/chunk-AXBJQJKA.js +65 -0
  7. package/dist/{chunk-TCWIXG5C.js → chunk-CCVRXJJ2.js} +1 -1
  8. package/dist/{chunk-WBXU27BF.js → chunk-FNMKP34Y.js} +1 -1
  9. package/dist/chunk-GEGUWQYT.js +90 -0
  10. package/dist/{chunk-NE2F5CKS.js → chunk-LKGKDZSO.js} +9 -5
  11. package/dist/{chunk-XPIW4N55.js → chunk-N2YAZRAD.js} +178 -43
  12. package/dist/{chunk-LSUB6QMP.js → chunk-NIIBVKLP.js} +3 -0
  13. package/dist/chunk-P64UR3SE.js +86 -0
  14. package/dist/{chunk-A4IOKD4Z.js → chunk-QYZP6QT2.js} +2 -4
  15. package/dist/{chunk-KNEJTD6G.js → chunk-SZLNNMIZ.js} +6 -2
  16. package/dist/{chunk-CUOJHP2S.js → chunk-UGEBGCYU.js} +48 -12
  17. package/dist/{chunk-6XWTUDWW.js → chunk-WDWVBPXA.js} +7 -5
  18. package/dist/{chunk-TNZITJAB.js → chunk-WVQU3QQ7.js} +14 -4
  19. package/dist/cli.js +42 -23
  20. package/dist/{codex-prompt-submit-hook-J5PZEJSK.js → codex-prompt-submit-hook-UQJI6A54.js} +2 -2
  21. package/dist/{codex-stop-hook-32W4BIOM.js → codex-stop-hook-GTGRQNVV.js} +2 -2
  22. package/dist/{connect-handler-NZINEMG3.js → connect-handler-GTFXNZUC.js} +68 -9
  23. package/dist/{doctor-WUU5BVPT.js → doctor-54S7JGOE.js} +64 -5
  24. package/dist/{doctor-codex-PJFWIABF.js → doctor-codex-6H4OOS6O.js} +84 -10
  25. package/dist/doctor-hermes-KMHM27Y3.js +597 -0
  26. package/dist/{doctor-openclaw-LL6NSPLC.js → doctor-openclaw-B5KADVHD.js} +5 -1
  27. package/dist/{event-log-2NBJEIEP.js → event-log-M22X3FAI.js} +1 -1
  28. package/dist/{event-stream-ATKYXDBV.js → event-stream-5ZEL7FXN.js} +5 -1
  29. package/dist/flap-log-D4KG7RK4.js +23 -0
  30. package/dist/{gateway-client-CbM2OC_w.d.ts → gateway-client-CLmQghON.d.ts} +11 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +6 -7
  33. package/dist/{install-JQNDGAAQ.js → install-GJVZEWLI.js} +8 -7
  34. package/dist/lib.d.ts +30 -1
  35. package/dist/lib.js +3 -3
  36. package/dist/plugins/hermes/adapter.py +7 -2
  37. package/dist/{registry-XJ67EO22.js → registry-MCJAVXJW.js} +36 -24
  38. package/dist/{send-cli-45RLGYIC.js → send-cli-WTY7H3BR.js} +2 -2
  39. package/dist/{server-DU3LFS32.js → server-VM3ZIGSZ.js} +2 -3
  40. package/dist/session-start-hook-VL3KYDTT.js +25 -0
  41. package/dist/{setup-handler-44ASXYMS.js → setup-handler-GNAIWAYU.js} +7 -7
  42. package/dist/{stop-hook-N6TX4YQT.js → stop-hook-BLPYRE7T.js} +5 -5
  43. package/dist/stream-watchdog-K2AWNCQW.js +64 -0
  44. package/dist/{tail-stream-G4WGTXDS.js → tail-stream-PQXEY3TI.js} +120 -9
  45. package/dist/{user-prompt-submit-hook-DTXXFDSD.js → user-prompt-submit-hook-YQXGE3JU.js} +3 -3
  46. package/package.json +3 -2
  47. package/dist/chunk-5DHIUN73.js +0 -25
  48. package/dist/chunk-TMCNB4JH.js +0 -33
@@ -1,19 +1,22 @@
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-6XWTUDWW.js";
9
+ } from "./chunk-WDWVBPXA.js";
9
10
  import {
10
11
  removeOpenclawMcpServer,
11
12
  writeOpenclawMcpConfig
12
13
  } from "./chunk-E6WMFMM2.js";
13
14
  import {
14
- WIZARD_RUNTIMES,
15
- isWizardRuntime
16
- } from "./chunk-77HWBSRH.js";
15
+ HERMES_INSTANCE_KEY,
16
+ defaultHermesConfigPath,
17
+ removeHermesMcpServer,
18
+ writeHermesMcpConfig
19
+ } from "./chunk-GEGUWQYT.js";
17
20
  import {
18
21
  CONNECT_RUNTIMES
19
22
  } from "./chunk-EIH2LNF4.js";
@@ -22,6 +25,10 @@ import {
22
25
  readRecordedRuntime,
23
26
  recordRuntime
24
27
  } from "./chunk-RDLF4NQC.js";
28
+ import {
29
+ WIZARD_RUNTIMES,
30
+ isWizardRuntime
31
+ } from "./chunk-77HWBSRH.js";
25
32
  import {
26
33
  kojeeHomeDir
27
34
  } from "./chunk-SQL56SEB.js";
@@ -42,14 +49,12 @@ import {
42
49
  secureFile,
43
50
  unlinkSyncRetry
44
51
  } from "./chunk-U5HHHRXA.js";
45
- import {
46
- VERSION
47
- } from "./chunk-5DHIUN73.js";
48
52
  import {
49
53
  CODEX_LISTEN_CAP_MS,
54
+ VERSION,
50
55
  buildCondensedTandemRules,
51
56
  buildWebhookReceiverNote
52
- } from "./chunk-FJUAMJHU.js";
57
+ } from "./chunk-6NPMVZD7.js";
53
58
 
54
59
  // src/wizard/pair-slot.ts
55
60
  import fs from "fs";
@@ -350,11 +355,11 @@ function writeWebhookSecretBothSides(opts) {
350
355
  ["KOJEE_RUNTIME", opts.runtime],
351
356
  ["KOJEE_WEBHOOK_URL", opts.webhookUrl],
352
357
  ["KOJEE_WEBHOOK_SECRET", secret],
353
- ...opts.signatureEnv ?? []
358
+ ...opts.signatureEnv ?? [],
359
+ ...opts.daemonExtraEnv ?? []
354
360
  ];
355
361
  upsertEnvFile(opts.daemonEnvPath, daemonVars, {
356
- exportPrefix: true,
357
- header: `# kojee daemon env for runtime=${opts.runtime} (source this before starting the daemon)`
362
+ header: `# kojee daemon env for runtime=${opts.runtime} (load with: set -a; . <this file>; set +a)`
358
363
  });
359
364
  secureFile(opts.daemonEnvPath);
360
365
  const adapterVars = [
@@ -418,6 +423,22 @@ function installBundledPayload(opts) {
418
423
  // src/wizard/service.ts
419
424
  import fs5 from "fs";
420
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
+ }
433
+ function daemonSourceExecCommand(spec) {
434
+ const target = spec.nodePath ? `${spec.nodePath} ${spec.binPath}` : spec.binPath;
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}`;
438
+ }
439
+ function xmlEscape(value) {
440
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
441
+ }
421
442
  function systemdUnit(spec) {
422
443
  return [
423
444
  "[Unit]",
@@ -427,9 +448,14 @@ function systemdUnit(spec) {
427
448
  "",
428
449
  "[Service]",
429
450
  "Type=simple",
430
- `EnvironmentFile=${spec.envFile}`,
431
- `Environment=KOJEE_RUNTIME=${spec.runtime}`,
432
- `ExecStart=${spec.binPath}`,
451
+ // §2b DISCRIMINATOR: KOJEE_WEBHOOK_EXPECTED lives in the WRAPPER env (this
452
+ // Environment= line), NOT in the sourced env file — a same-class drop would
453
+ // otherwise kill the marker too. It lets startProxy tell the "webhook was
454
+ // meant to be here" daemon apart from the tools-only child (which never sets
455
+ // it) when the sourced URL is absent → null delivery.
456
+ `Environment=${wrapperEnvEntries(spec).map(([k, v]) => `${k}=${v}`).join(" ")}`,
457
+ // §2a: source the env file inside a shell wrapper (replaces EnvironmentFile=).
458
+ `ExecStart=/bin/sh -c '${daemonSourceExecCommand(spec)}'`,
433
459
  "Restart=always",
434
460
  "RestartSec=5",
435
461
  "StandardOutput=journal",
@@ -450,11 +476,19 @@ function launchdPlist(spec, label) {
450
476
  '<plist version="1.0">',
451
477
  "<dict>",
452
478
  " <key>Label</key>",
453
- ` <string>${label}</string>`,
479
+ ` <string>${xmlEscape(label)}</string>`,
480
+ // §2a: same source-wrapper as the systemd unit so macOS actually loads the
481
+ // env file (the plist historically never did). The secret is never inlined
482
+ // here — it stays in the 0o600 sourced file.
454
483
  " <key>ProgramArguments</key>",
455
- ` <array><string>${spec.binPath}</string></array>`,
484
+ " <array>",
485
+ " <string>/bin/sh</string>",
486
+ " <string>-c</string>",
487
+ ` <string>${xmlEscape(daemonSourceExecCommand(spec))}</string>`,
488
+ " </array>",
489
+ // §2b: KOJEE_WEBHOOK_EXPECTED in the wrapper env (NOT the sourced file).
456
490
  " <key>EnvironmentVariables</key>",
457
- ` <dict><key>KOJEE_RUNTIME</key><string>${spec.runtime}</string></dict>`,
491
+ ` <dict>${wrapperEnvEntries(spec).map(([k, v]) => `<key>${xmlEscape(k)}</key><string>${xmlEscape(v)}</string>`).join("")}</dict>`,
458
492
  " <key>RunAtLoad</key><true/>",
459
493
  " <key>KeepAlive</key><true/>",
460
494
  "</dict>",
@@ -506,6 +540,15 @@ function writeService(platform, spec) {
506
540
  }
507
541
  return plan;
508
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
+ }
509
552
 
510
553
  // src/wizard/installers/hermes.ts
511
554
  function installHermes(inp) {
@@ -529,11 +572,14 @@ function installHermes(inp) {
529
572
  adapterEnvPath: adapterEnv,
530
573
  webhookUrl: inp.webhookUrl,
531
574
  runtime: "hermes",
575
+ daemonExtraEnv: [["KOJEE_INSTANCE", HERMES_INSTANCE_KEY]],
532
576
  ...inp.mcpDir ? { mcpDir: inp.mcpDir } : {},
533
577
  ...inp.allowAllUsers ? { allowAllUsers: true } : {},
534
578
  ...inp.signatureEnv && inp.signatureEnv.length > 0 ? { signatureEnv: inp.signatureEnv } : {},
535
579
  ...inp.webhookSecret ? { existingSecret: inp.webhookSecret } : {}
536
580
  });
581
+ const hermesConfig = defaultHermesConfigPath(inp.homeDir);
582
+ const mcp = writeHermesMcpConfig(hermesConfig, { binPath: inp.binPath });
537
583
  const staged = inp.skipPayload ? [] : installBundledPayload({
538
584
  runtime: "hermes",
539
585
  baseDir: inp.payloadBaseDir,
@@ -543,6 +589,7 @@ function installHermes(inp) {
543
589
  const svc = writeService(inp.platform, {
544
590
  serviceName: "kojee-hermes",
545
591
  binPath: inp.binPath,
592
+ ...inp.nodePath ? { nodePath: inp.nodePath } : {},
546
593
  envFile: daemonEnv,
547
594
  runtime: "hermes",
548
595
  homeDir: inp.homeDir
@@ -552,6 +599,8 @@ function installHermes(inp) {
552
599
  ` secret: ${sec.reused ? "reused existing" : "generated"} (matched on daemon + adapter env)`,
553
600
  ` daemon env: ${daemonEnv}`,
554
601
  ` adapter env: ${adapterEnv}`,
602
+ ` mcp config: ${hermesConfig} (mcp_servers.kojee \u2014 agent explores kojee via mcp_kojee_*)`,
603
+ ...mcp.backedUp ? [` NOTE: prior ${hermesConfig} was unparseable; backed up to ${mcp.backedUp} before writing.`] : [],
555
604
  inp.skipPayload ? ` plugin: SKIPPED \u2014 no bundled payload (run \`npm run build\` to stage dist/plugins/hermes)` : ` plugin: ${pluginDir} (${staged.length} files)`,
556
605
  svc.supported ? ` service: ${svc.unitPath}` : ` service: manual \u2014 ${svc.note ?? "unsupported platform"}`,
557
606
  // NB: the "Receiver contract" section is intentionally NOT printed here.
@@ -559,11 +608,18 @@ function installHermes(inp) {
559
608
  // every daemon runtime) on the same URL-present path before this output, so
560
609
  // emitting it here too would print the contract twice.
561
610
  "",
562
- "Next:",
563
- ` - start the daemon service: ${svc.activateCmd}`,
564
- " - reload the Hermes gateway to load the plugin: systemctl --user restart hermes-gateway (or: hermes gateway restart)",
565
- " - join a tandem once: tandem_join <tandem_id>",
566
- " - verify: kojee-mcp doctor"
611
+ "Next \u2014 ACTIVATE (owner steps; the receiver never binds until you do these, in order):",
612
+ " 1. enable the Tandem receiver plugin (WITHOUT this, the gateway restart loads nothing):",
613
+ " hermes plugins enable kojee-tandem",
614
+ " 2. restart the gateway to bind the receiver (:8645) AND load the kojee MCP tools:",
615
+ " hermes gateway restart (or: systemctl --user restart hermes-gateway)",
616
+ ` 3. start the pusher daemon: ${svc.activateCmd}`,
617
+ " 4. verify end-to-end (should read HEALTHY): kojee-mcp doctor --runtime hermes",
618
+ "",
619
+ " Notes:",
620
+ " - the agent joins rooms from its own tools: mcp_kojee_tandem_join <tandem_id>",
621
+ " - if tools don't register: ensure Hermes has the MCP extra",
622
+ ' (cd ~/.hermes/hermes-agent && uv pip install -e ".[mcp]") and re-check connect_timeout.'
567
623
  ];
568
624
  return {
569
625
  runtime: "hermes",
@@ -580,6 +636,16 @@ function installOpenclaw(inp) {
580
636
  ...inp.pairedConfigPath ? { pairedConfigPath: inp.pairedConfigPath } : {},
581
637
  ...inp.now ? { now: inp.now } : {}
582
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;
583
649
  const lines = [];
584
650
  if (r.backedUp) {
585
651
  lines.push(
@@ -588,17 +654,22 @@ function installOpenclaw(inp) {
588
654
  );
589
655
  }
590
656
  lines.push(
591
- "Configured runtime: openclaw (MCP server + /wake injector)",
592
- "Wake mode: kojee-mcp runs as OpenClaw's MCP server; Tandem events POST to the local",
593
- " OpenClaw gateway /hooks/wake (Bearer hooks.token) \u2192 the agent wakes.",
594
- ` mcp config: ${inp.openclawConfigPath} (mcp.servers.kojee)`,
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)`,
595
662
  inp.pairedConfigPath ? ` credentials: per-runtime paired slot (${inp.pairedConfigPath})` : ` credentials: shared ~/.kojee/config.json (paired)`,
596
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",
597
665
  "",
598
666
  "Next steps (openclaw):",
599
- " - Run OpenClaw as its GATEWAY DAEMON (its default mode) \u2014 the /wake injector only",
600
- " wakes a running gateway. If the gateway is down, nudges are dropped until it restarts.",
601
- " - Restart the OpenClaw gateway to spawn the kojee MCP server: openclaw gateway restart",
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",
602
673
  " - Verify: openclaw mcp list (kojee should be listed) / kojee-mcp doctor",
603
674
  "",
604
675
  // Belt-and-suspenders: the same condensed rules ride the kojee MCP server's
@@ -613,11 +684,21 @@ function installOpenclaw(inp) {
613
684
  }
614
685
  function uninstallOpenclaw(inp) {
615
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;
616
693
  const lines = [
617
- "Uninstalling runtime: openclaw (MCP server + /wake injector)",
694
+ "Uninstalling runtime: openclaw (MCP server + wake daemon)",
618
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
+ ] : [],
619
699
  "",
620
700
  "Next steps (openclaw):",
701
+ " - Stop the wake daemon if still running (see the deactivate command above).",
621
702
  " - Restart the OpenClaw gateway: openclaw gateway restart",
622
703
  " - hooks.enabled/token were left intact (you may use hooks for other integrations)."
623
704
  ];
@@ -790,7 +871,7 @@ function whatHappensNext(runtime, opts) {
790
871
  return lines.join("\n");
791
872
  }
792
873
  async function configureClaudeCode(opts) {
793
- const { runInit } = await import("./install-JQNDGAAQ.js");
874
+ const { runInit } = await import("./install-GJVZEWLI.js");
794
875
  const report = runInit({
795
876
  ...opts.configPath ? { configPath: opts.configPath } : {},
796
877
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
@@ -892,7 +973,7 @@ function configureCodex(opts) {
892
973
  lines.push("");
893
974
  if (writeResult.hooksChanged) {
894
975
  lines.push(
895
- "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}.`
896
977
  );
897
978
  lines.push("");
898
979
  }
@@ -923,7 +1004,7 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
923
1004
  lines.push(` export KOJEE_WEBHOOK_SECRET=<generated; in ${envFile}>`);
924
1005
  for (const [k, v] of wh.signatureEnv) lines.push(` export ${k}=${shellSingleQuote(v)}`);
925
1006
  lines.push(` (validated: ${wh.redactedSummary})`);
926
- lines.push(` source ${envFile}`);
1007
+ lines.push(` set -a; . ${envFile}; set +a`);
927
1008
  } else {
928
1009
  lines.push("Set the daemon env (no receiver URL supplied yet):");
929
1010
  lines.push(` export KOJEE_RUNTIME=${shellSingleQuote(runtime)}`);
@@ -941,7 +1022,18 @@ function distDir() {
941
1022
  }
942
1023
  function resolveBinPath() {
943
1024
  const entry = process.argv[1];
944
- return entry && entry.length > 0 ? entry : "kojee-mcp";
1025
+ if (!entry || entry.length === 0) return "kojee-mcp";
1026
+ try {
1027
+ return fs6.realpathSync(entry);
1028
+ } catch {
1029
+ return entry;
1030
+ }
1031
+ }
1032
+ function resolveDaemonExec() {
1033
+ const nodePath = process.execPath;
1034
+ const binPath = resolveBinPath();
1035
+ const ephemeral = /[\\/]_npx[\\/]/.test(binPath);
1036
+ return { nodePath, binPath, ephemeral };
945
1037
  }
946
1038
  function configureHermes(opts) {
947
1039
  const runtime = "hermes";
@@ -955,7 +1047,10 @@ function configureHermes(opts) {
955
1047
  opts = effectiveOpts;
956
1048
  const lines = [];
957
1049
  lines.push(`Configured runtime: ${runtime}`);
958
- lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
1050
+ lines.push(
1051
+ "Wake mode: webhook sink (daemon-consumed). ALSO registers kojee as the Hermes agent's",
1052
+ "MCP server (mcp_servers.kojee) so the agent can explore kojee tools. NO hooks written."
1053
+ );
959
1054
  lines.push("");
960
1055
  if (!suppliedUrl) {
961
1056
  lines.push(
@@ -981,11 +1076,13 @@ function configureHermes(opts) {
981
1076
  }
982
1077
  const env = opts.env ?? process.env;
983
1078
  const suppliedSecret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
1079
+ const daemonExec = resolveDaemonExec();
984
1080
  const install = installHermes({
985
1081
  homeDir: home,
986
1082
  payloadBaseDir: base,
987
1083
  platform: process.platform,
988
- binPath: resolveBinPath(),
1084
+ binPath: daemonExec.binPath,
1085
+ nodePath: daemonExec.nodePath,
989
1086
  webhookUrl: wh.url,
990
1087
  ...suppliedSecret ? { webhookSecret: suppliedSecret } : {},
991
1088
  ...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
@@ -998,6 +1095,15 @@ function configureHermes(opts) {
998
1095
  return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
999
1096
  }
1000
1097
  recordRuntime(runtime);
1098
+ if (daemonExec.ephemeral) {
1099
+ lines.push(
1100
+ "\u26A0 DURABILITY: this ran from an ephemeral npx cache, so the daemon points at a",
1101
+ " path npx will eventually delete (the daemon would then fail to start).",
1102
+ " For a durable daemon, install globally and re-run init:",
1103
+ " npm i -g kojee-mcp && kojee-mcp init --runtime hermes",
1104
+ ""
1105
+ );
1106
+ }
1001
1107
  const envFile = path7.join(home, ".kojee", "hermes.env");
1002
1108
  lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
1003
1109
  lines.push("");
@@ -1024,18 +1130,35 @@ function openclawConfigPath(opts) {
1024
1130
  }
1025
1131
  function configureOpenclaw(opts) {
1026
1132
  const runtime = "openclaw";
1133
+ const daemonExec = resolveDaemonExec();
1027
1134
  const install = installOpenclaw({
1028
1135
  openclawConfigPath: openclawConfigPath(opts),
1029
- ...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
1030
1144
  });
1031
1145
  recordRuntime(runtime);
1032
- return { runtime, output: install.output, exitCode: install.exitCode };
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 };
1033
1156
  }
1034
1157
  async function runWizardUninstall(runtime, opts) {
1035
1158
  const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
1036
1159
  const lines = [`Uninstalling runtime: ${effective}`];
1037
1160
  if (effective === "claude-code") {
1038
- const { runUninstall } = await import("./install-JQNDGAAQ.js");
1161
+ const { runUninstall } = await import("./install-GJVZEWLI.js");
1039
1162
  const report = runUninstall({
1040
1163
  ...opts.configPath ? { configPath: opts.configPath } : {},
1041
1164
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
@@ -1059,11 +1182,19 @@ async function runWizardUninstall(runtime, opts) {
1059
1182
  lines.push(` config.toml [mcp_servers.kojee]: ${removed.mcpServer ? "removed" : "not found"}`);
1060
1183
  lines.push(` hooks.json Stop: ${removed.stopHook ? "removed" : "not found"}`);
1061
1184
  } else if (effective === "openclaw") {
1062
- const un = uninstallOpenclaw({ openclawConfigPath: openclawConfigPath(opts) });
1185
+ const un = uninstallOpenclaw({
1186
+ openclawConfigPath: openclawConfigPath(opts),
1187
+ homeDir: kojeeHomeDir(),
1188
+ platform: process.platform
1189
+ });
1063
1190
  lines.push(un.output);
1064
1191
  } else {
1065
- lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
1066
- lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
1192
+ const hermesConfig = defaultHermesConfigPath(kojeeHomeDir());
1193
+ const removedMcp = removeHermesMcpServer(hermesConfig);
1194
+ lines.push(
1195
+ removedMcp ? ` removed mcp_servers.kojee from ${hermesConfig} (operator config preserved)` : ` no mcp_servers.kojee in ${hermesConfig} \u2014 nothing to remove`
1196
+ );
1197
+ lines.push(" (stop the daemon service + reload the gateway to disable the webhook wake path.)");
1067
1198
  const envPath = path7.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
1068
1199
  try {
1069
1200
  fs6.unlinkSync(envPath);
@@ -1109,7 +1240,11 @@ registerBuiltinInstaller(
1109
1240
  "webhook",
1110
1241
  "webhook-secret-both-sides",
1111
1242
  "plugin-payload-copy",
1112
- "service-install"
1243
+ "service-install",
1244
+ // kojee registered as the Hermes agent's MCP server (mcp_servers.kojee in
1245
+ // ~/.hermes/config.yaml) so the agent can EXPLORE + call kojee tools —
1246
+ // additive to the channel plugin, does not change the webhook wake path.
1247
+ "mcp-config-write"
1113
1248
  ],
1114
1249
  (o) => configureHermes(o)
1115
1250
  );
@@ -5,6 +5,7 @@ var NULL_RESULT = {
5
5
  sessionId: null,
6
6
  transcriptPath: null,
7
7
  hookEventName: null,
8
+ source: null,
8
9
  stopHookActive: false,
9
10
  raw: ""
10
11
  };
@@ -22,6 +23,7 @@ async function readHookStdin() {
22
23
  sessionId: stringOrNull(parsed["session_id"]),
23
24
  transcriptPath: stringOrNull(parsed["transcript_path"]),
24
25
  hookEventName: stringOrNull(parsed["hook_event_name"]),
26
+ source: stringOrNull(parsed["source"]),
25
27
  stopHookActive: parsed["stop_hook_active"] === true,
26
28
  raw
27
29
  };
@@ -30,6 +32,7 @@ async function readHookStdin() {
30
32
  sessionId: null,
31
33
  transcriptPath: null,
32
34
  hookEventName: null,
35
+ source: null,
33
36
  stopHookActive: false,
34
37
  raw
35
38
  };
@@ -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
+ };
@@ -1,12 +1,10 @@
1
1
  import {
2
- VERSION
3
- } from "./chunk-5DHIUN73.js";
4
- import {
2
+ VERSION,
5
3
  buildCatchUpNote,
6
4
  buildCondensedTandemRules,
7
5
  buildMonitorSpawn,
8
6
  buildReplyRecipe
9
- } from "./chunk-FJUAMJHU.js";
7
+ } from "./chunk-6NPMVZD7.js";
10
8
  import {
11
9
  translateToolCallResult
12
10
  } from "./chunk-PPTKGWFF.js";
@@ -1,17 +1,20 @@
1
1
  // src/runtime/cc-session-id.ts
2
2
  import { randomUUID } from "crypto";
3
3
  var SESSION_ID_ENV_VARS = ["CLAUDE_CODE_SESSION_ID"];
4
+ var STABLE_RUNTIMES = /* @__PURE__ */ new Set(["openclaw", "open_claw"]);
4
5
  function sanitizeKey(value) {
5
6
  return value.replace(/[^A-Za-z0-9_-]/g, "");
6
7
  }
7
8
  function resolveInstanceKey(deps = {}) {
8
9
  const env = deps.env ?? process.env;
10
+ const explicit = sanitizeKey((env.KOJEE_INSTANCE ?? "").trim());
11
+ if (explicit) return `inst-${explicit}`;
9
12
  for (const name of SESSION_ID_ENV_VARS) {
10
13
  const sid = sanitizeKey((env[name] ?? "").trim());
11
14
  if (sid) return sid;
12
15
  }
13
- const explicit = sanitizeKey((env.KOJEE_INSTANCE ?? "").trim());
14
- if (explicit) return `inst-${explicit}`;
16
+ const runtime = sanitizeKey((env.KOJEE_RUNTIME ?? "").trim().toLowerCase());
17
+ if (STABLE_RUNTIMES.has(runtime)) return `rt-${runtime}`;
15
18
  const mint = deps.randomUUID ?? randomUUID;
16
19
  return `mint-${mint()}`;
17
20
  }
@@ -26,6 +29,7 @@ function resolveSharedSessionId(deps = {}) {
26
29
 
27
30
  export {
28
31
  SESSION_ID_ENV_VARS,
32
+ STABLE_RUNTIMES,
29
33
  resolveInstanceKey,
30
34
  resolveSharedSessionId
31
35
  };