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.
- package/README.md +27 -6
- package/dist/{cc-session-id-RURNIHHC.js → cc-session-id-5HHQJ7VC.js} +3 -1
- package/dist/{chunk-ZSHCHOPL.js → chunk-676WQSWZ.js} +163 -7
- package/dist/{chunk-FJUAMJHU.js → chunk-6NPMVZD7.js} +38 -3
- package/dist/{chunk-247WFMCJ.js → chunk-AGZ7ZGLY.js} +30 -3
- package/dist/chunk-AXBJQJKA.js +65 -0
- package/dist/{chunk-TCWIXG5C.js → chunk-CCVRXJJ2.js} +1 -1
- package/dist/{chunk-WBXU27BF.js → chunk-FNMKP34Y.js} +1 -1
- package/dist/chunk-GEGUWQYT.js +90 -0
- package/dist/{chunk-NE2F5CKS.js → chunk-LKGKDZSO.js} +9 -5
- package/dist/{chunk-XPIW4N55.js → chunk-N2YAZRAD.js} +178 -43
- package/dist/{chunk-LSUB6QMP.js → chunk-NIIBVKLP.js} +3 -0
- package/dist/chunk-P64UR3SE.js +86 -0
- package/dist/{chunk-A4IOKD4Z.js → chunk-QYZP6QT2.js} +2 -4
- package/dist/{chunk-KNEJTD6G.js → chunk-SZLNNMIZ.js} +6 -2
- package/dist/{chunk-CUOJHP2S.js → chunk-UGEBGCYU.js} +48 -12
- package/dist/{chunk-6XWTUDWW.js → chunk-WDWVBPXA.js} +7 -5
- package/dist/{chunk-TNZITJAB.js → chunk-WVQU3QQ7.js} +14 -4
- package/dist/cli.js +42 -23
- package/dist/{codex-prompt-submit-hook-J5PZEJSK.js → codex-prompt-submit-hook-UQJI6A54.js} +2 -2
- package/dist/{codex-stop-hook-32W4BIOM.js → codex-stop-hook-GTGRQNVV.js} +2 -2
- package/dist/{connect-handler-NZINEMG3.js → connect-handler-GTFXNZUC.js} +68 -9
- package/dist/{doctor-WUU5BVPT.js → doctor-54S7JGOE.js} +64 -5
- package/dist/{doctor-codex-PJFWIABF.js → doctor-codex-6H4OOS6O.js} +84 -10
- package/dist/doctor-hermes-KMHM27Y3.js +597 -0
- package/dist/{doctor-openclaw-LL6NSPLC.js → doctor-openclaw-B5KADVHD.js} +5 -1
- package/dist/{event-log-2NBJEIEP.js → event-log-M22X3FAI.js} +1 -1
- package/dist/{event-stream-ATKYXDBV.js → event-stream-5ZEL7FXN.js} +5 -1
- package/dist/flap-log-D4KG7RK4.js +23 -0
- package/dist/{gateway-client-CbM2OC_w.d.ts → gateway-client-CLmQghON.d.ts} +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -7
- package/dist/{install-JQNDGAAQ.js → install-GJVZEWLI.js} +8 -7
- package/dist/lib.d.ts +30 -1
- package/dist/lib.js +3 -3
- package/dist/plugins/hermes/adapter.py +7 -2
- package/dist/{registry-XJ67EO22.js → registry-MCJAVXJW.js} +36 -24
- package/dist/{send-cli-45RLGYIC.js → send-cli-WTY7H3BR.js} +2 -2
- package/dist/{server-DU3LFS32.js → server-VM3ZIGSZ.js} +2 -3
- package/dist/session-start-hook-VL3KYDTT.js +25 -0
- package/dist/{setup-handler-44ASXYMS.js → setup-handler-GNAIWAYU.js} +7 -7
- package/dist/{stop-hook-N6TX4YQT.js → stop-hook-BLPYRE7T.js} +5 -5
- package/dist/stream-watchdog-K2AWNCQW.js +64 -0
- package/dist/{tail-stream-G4WGTXDS.js → tail-stream-PQXEY3TI.js} +120 -9
- package/dist/{user-prompt-submit-hook-DTXXFDSD.js → user-prompt-submit-hook-YQXGE3JU.js} +3 -3
- package/package.json +3 -2
- package/dist/chunk-5DHIUN73.js +0 -25
- 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-
|
|
9
|
+
} from "./chunk-WDWVBPXA.js";
|
|
9
10
|
import {
|
|
10
11
|
removeOpenclawMcpServer,
|
|
11
12
|
writeOpenclawMcpConfig
|
|
12
13
|
} from "./chunk-E6WMFMM2.js";
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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-
|
|
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
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
564
|
-
"
|
|
565
|
-
"
|
|
566
|
-
"
|
|
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
|
|
592
|
-
"Wake mode: kojee-
|
|
593
|
-
" OpenClaw gateway /hooks/
|
|
594
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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 +
|
|
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-
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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-
|
|
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({
|
|
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
|
-
|
|
1066
|
-
|
|
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-
|
|
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
|
|
14
|
-
if (
|
|
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
|
};
|