kojee-mcp 0.6.0 → 0.7.1

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 (70) hide show
  1. package/README.md +13 -8
  2. package/dist/ancestry-A2F5KQ6A.js +10 -0
  3. package/dist/cc-session-id-RURNIHHC.js +10 -0
  4. package/dist/{chunk-KH5YEJIM.js → chunk-247WFMCJ.js} +7 -20
  5. package/dist/chunk-5DHIUN73.js +25 -0
  6. package/dist/{chunk-YH27B6SW.js → chunk-5SZHXYPK.js} +16 -3
  7. package/dist/{chunk-DO42NPNR.js → chunk-67F67AQ6.js} +14 -3
  8. package/dist/{chunk-CH32ELFX.js → chunk-6G6YYST6.js} +1 -1
  9. package/dist/chunk-D6JKFJ6A.js +11 -0
  10. package/dist/chunk-E6WMFMM2.js +100 -0
  11. package/dist/chunk-EBYUJM3H.js +14 -0
  12. package/dist/chunk-EIAUW6KO.js +82 -0
  13. package/dist/chunk-EIH2LNF4.js +45 -0
  14. package/dist/{chunk-BYV7CWNY.js → chunk-FBJCPRVH.js} +22 -9
  15. package/dist/{chunk-XN3IC4B5.js → chunk-FSKGQ6GT.js} +30 -5
  16. package/dist/{chunk-GI2CKKBL.js → chunk-GKXHKR3B.js} +1 -1
  17. package/dist/{chunk-ALZX3SGI.js → chunk-GNLCUJBK.js} +13 -27
  18. package/dist/{chunk-JXMVZEQ7.js → chunk-I67C2HYA.js} +1 -1
  19. package/dist/chunk-KNEJTD6G.js +31 -0
  20. package/dist/{chunk-65KRRDHP.js → chunk-KPMD72FY.js} +94 -28
  21. package/dist/{chunk-NR4Y54OL.js → chunk-MIEI4PLB.js} +1 -19
  22. package/dist/chunk-NE2F5CKS.js +113 -0
  23. package/dist/chunk-QJFMU4QC.js +33 -0
  24. package/dist/{chunk-EW72ZNQL.js → chunk-RDLF4NQC.js} +1 -1
  25. package/dist/{chunk-Q7T3WAST.js → chunk-SSW5AQSR.js} +6 -1
  26. package/dist/{chunk-DURRQDYI.js → chunk-TNZITJAB.js} +45 -15
  27. package/dist/chunk-U5HHHRXA.js +103 -0
  28. package/dist/{setup-handler-7HOFAD7C.js → chunk-UFHGZUST.js} +431 -253
  29. package/dist/{chunk-N35X22G7.js → chunk-UPJV7GBE.js} +1 -1
  30. package/dist/chunk-WBXU27BF.js +35 -0
  31. package/dist/chunk-XJEBJIQE.js +52 -0
  32. package/dist/chunk-Z5LPNJQ6.js +20 -0
  33. package/dist/cli.js +63 -25
  34. package/dist/codex-stop-hook-BMOJVM6O.js +96 -0
  35. package/dist/connect-handler-4DRTFMOB.js +138 -0
  36. package/dist/{control-token-4BUCTYQB.js → control-token-KXSZ3GBY.js} +2 -2
  37. package/dist/{doctor-WVX3WCYX.js → doctor-5QJ3HGNR.js} +48 -22
  38. package/dist/{doctor-codex-Z5M4SFBG.js → doctor-codex-VGJKTX2E.js} +33 -7
  39. package/dist/doctor-openclaw-LL6NSPLC.js +86 -0
  40. package/dist/{event-log-HOBO3ECB.js → event-log-2NBJEIEP.js} +5 -3
  41. package/dist/{event-stream-SYBUJ5EH.js → event-stream-KRYWEYWO.js} +7 -4
  42. package/dist/index.js +13 -10
  43. package/dist/{install-LJY2CHKG.js → install-V7LSQCYZ.js} +37 -22
  44. package/dist/{keystore-XLEV3FL5.js → keystore-LQNFDHGO.js} +2 -2
  45. package/dist/lib.d.ts +4 -1
  46. package/dist/lib.js +11 -8
  47. package/dist/pair-HTCVSMR6.js +11 -0
  48. package/dist/{paired-config-JTFLHMZ2.js → paired-config-GUDDO2CH.js} +4 -2
  49. package/dist/{parent-watchdog-TLU355FB.js → parent-watchdog-LTUNF7PD.js} +24 -5
  50. package/dist/{registry-YLOFZDI2.js → registry-ZZZ26WGA.js} +197 -24
  51. package/dist/resolve-slot-BFIB46Y5.js +13 -0
  52. package/dist/{runtime-record-WO4IECM6.js → runtime-record-OXRLTOLC.js} +2 -2
  53. package/dist/{send-cli-IYJF32PE.js → send-cli-45RLGYIC.js} +22 -10
  54. package/dist/{server-R24AVNTI.js → server-ITPFQVTK.js} +5 -2
  55. package/dist/{session-discovery-FNMJGFPM.js → session-discovery-NHCZ2XWA.js} +2 -2
  56. package/dist/setup-handler-JFM45NCN.js +160 -0
  57. package/dist/{stop-hook-TCPJFGFF.js → stop-hook-5ABGTC2O.js} +17 -20
  58. package/dist/{tail-stream-4IMXI44S.js → tail-stream-NBGHHBS4.js} +40 -22
  59. package/dist/{user-prompt-submit-hook-FQ43NIHC.js → user-prompt-submit-hook-DTXXFDSD.js} +11 -14
  60. package/package.json +3 -2
  61. package/skills/using-tandems/SKILL.md +52 -0
  62. package/dist/ancestry-ONFBQEP5.js +0 -8
  63. package/dist/chunk-BLEGIR35.js +0 -43
  64. package/dist/chunk-CLKCNV2A.js +0 -100
  65. package/dist/chunk-KFDZWNQO.js +0 -73
  66. package/dist/chunk-OGHDTFAX.js +0 -50
  67. package/dist/chunk-VHKPWUX7.js +0 -41
  68. package/dist/codex-stop-hook-RWQSZTST.js +0 -16
  69. package/dist/doctor-openclaw-SS2TMQOX.js +0 -95
  70. package/dist/pair-P4ILCMT7.js +0 -10
package/README.md CHANGED
@@ -162,8 +162,9 @@ npx kojee-mcp init --runtime claude-code --token gw_abc... --url https://rosie-s
162
162
  npx kojee-mcp init --runtime codex --webhook-url https://your-receiver/kojee
163
163
  # Codex: writes ~/.codex/config.toml + a codex-stop hook
164
164
  npx kojee-mcp init --runtime hermes --webhook-url https://your-receiver/kojee
165
- npx kojee-mcp init --runtime openclaw --webhook-url https://your-receiver/kojee
166
- # daemon runtimes: print/record the webhook-sink env to export
165
+ # hermes daemon: print/record the webhook-sink env to export
166
+ npx kojee-mcp connect ABCD-1234 --runtime openclaw
167
+ # OpenClaw: writes mcp.servers.kojee + hooks into ~/.openclaw/openclaw.json
167
168
  ```
168
169
 
169
170
  Auth flags (all runtimes): `--token <gw_…>` + `--url <broker>` (token mode), or
@@ -180,12 +181,16 @@ Per-runtime config-path / hooks-path overrides: `--config-path`, `--hooks-path`.
180
181
  one. Codex has no Claude-style channel injection, so its wake is the **webhook
181
182
  sink + a stop-hook fast PEEK + a model-chosen bounded `tandem_listen` (cap 8s,
182
183
  never a blanket long-poll)**. See `docs/RUNTIMES.md`.
183
- - **hermes / openclaw** — these runtimes have **native channel plugins** (see
184
- [Native gateway runtimes](#native-gateway-runtimes-openclaw--hermes)). When you
185
- instead run the proxy as a **daemon** feeding a webhook receiver, `init --runtime
186
- hermes|openclaw` writes no MCP-config/hooks of ours, validates the webhook env,
187
- and prints + records the env to export (secret redacted) into a source-able
188
- `~/.kojee/<runtime>.env`.
184
+ - **openclaw** — `connect --runtime openclaw` writes `mcp.servers.kojee` (OpenClaw
185
+ spawns kojee-mcp AS its MCP server, threading the per-runtime paired slot via
186
+ `--paired-config`) **and** enables the gateway `hooks` (`hooks.enabled=true` +
187
+ a `hooks.token`) into `~/.openclaw/openclaw.json`. Wake is a POST to
188
+ `<gateway>/hooks/wake` (Bearer `hooks.token`) from the openclaw `WakeDelivery`
189
+ injector — no webhook receiver, no `--webhook-url`.
190
+ - **hermes** — a **daemon** feeding a webhook receiver. `init --runtime hermes`
191
+ (or `connect --runtime hermes`) validates the webhook env and prints + records
192
+ the env to export (secret redacted) into a source-able `~/.kojee/hermes.env`.
193
+ `--webhook-url` is optional (defaults to the hermes-plugin loopback receiver).
189
194
 
190
195
  `kojee-mcp init --uninstall` is runtime-aware (uses the recorded runtime when
191
196
  `--runtime` is omitted). `kojee-mcp doctor` is runtime-aware too.
@@ -0,0 +1,10 @@
1
+ import {
2
+ deriveDiscoveryKey,
3
+ findClaudeAncestorPid,
4
+ pidLooksLikeClaude
5
+ } from "./chunk-XJEBJIQE.js";
6
+ export {
7
+ deriveDiscoveryKey,
8
+ findClaudeAncestorPid,
9
+ pidLooksLikeClaude
10
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ SESSION_ID_ENV_VARS,
3
+ resolveInstanceKey,
4
+ resolveSharedSessionId
5
+ } from "./chunk-KNEJTD6G.js";
6
+ export {
7
+ SESSION_ID_ENV_VARS,
8
+ resolveInstanceKey,
9
+ resolveSharedSessionId
10
+ };
@@ -1,14 +1,19 @@
1
1
  import {
2
- createDPoPProof,
3
2
  deriveStableSessionId,
4
3
  getSessionId,
5
4
  setStableSessionId
6
- } from "./chunk-NR4Y54OL.js";
5
+ } from "./chunk-Z5LPNJQ6.js";
6
+ import {
7
+ createDPoPProof
8
+ } from "./chunk-MIEI4PLB.js";
7
9
  import {
8
10
  translateHttpError,
9
11
  translateJsonRpcError,
10
12
  translateNetworkError
11
13
  } from "./chunk-PPTKGWFF.js";
14
+ import {
15
+ resolveInstanceKey
16
+ } from "./chunk-KNEJTD6G.js";
12
17
 
13
18
  // src/gateway-client.ts
14
19
  import crypto from "crypto";
@@ -189,24 +194,6 @@ var GatewayClient = class {
189
194
  }
190
195
  };
191
196
 
192
- // src/runtime/cc-session-id.ts
193
- import { randomUUID } from "crypto";
194
- var SESSION_ID_ENV_VARS = ["CLAUDE_CODE_SESSION_ID"];
195
- function sanitizeKey(value) {
196
- return value.replace(/[^A-Za-z0-9_-]/g, "");
197
- }
198
- function resolveInstanceKey(deps = {}) {
199
- const env = deps.env ?? process.env;
200
- for (const name of SESSION_ID_ENV_VARS) {
201
- const sid = sanitizeKey((env[name] ?? "").trim());
202
- if (sid) return sid;
203
- }
204
- const explicit = sanitizeKey((env.KOJEE_INSTANCE ?? "").trim());
205
- if (explicit) return `inst-${explicit}`;
206
- const mint = deps.randomUUID ?? randomUUID;
207
- return `mint-${mint()}`;
208
- }
209
-
210
197
  // src/runtime/stable-session.ts
211
198
  async function applyStableSessionId(token, deps = {}) {
212
199
  let instanceKey = deps.instanceKey;
@@ -0,0 +1,25 @@
1
+ // src/version.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ var FALLBACK_VERSION = "0.0.0-unknown";
6
+ function resolveVersion() {
7
+ try {
8
+ const here = path.dirname(fileURLToPath(import.meta.url));
9
+ const parsed = JSON.parse(
10
+ fs.readFileSync(path.join(here, "..", "package.json"), "utf8")
11
+ );
12
+ return typeof parsed?.version === "string" && parsed.version ? parsed.version : FALLBACK_VERSION;
13
+ } catch (err) {
14
+ process.stderr.write(
15
+ `kojee-mcp: could not resolve version from package.json, falling back to ${FALLBACK_VERSION}: ${String(err)}
16
+ `
17
+ );
18
+ return FALLBACK_VERSION;
19
+ }
20
+ }
21
+ var VERSION = resolveVersion();
22
+
23
+ export {
24
+ VERSION
25
+ };
@@ -1,7 +1,9 @@
1
1
  import {
2
+ createSecureFileSync,
3
+ renameSyncRetry,
2
4
  secureDir,
3
5
  secureFile
4
- } from "./chunk-BLEGIR35.js";
6
+ } from "./chunk-U5HHHRXA.js";
5
7
 
6
8
  // src/auth/paired-config.ts
7
9
  import fs from "fs";
@@ -10,6 +12,9 @@ import path from "path";
10
12
  function pairedConfigPath() {
11
13
  return path.join(os.homedir(), ".kojee", "config.json");
12
14
  }
15
+ function perRuntimeConfigPath(runtime) {
16
+ return path.join(os.homedir(), ".kojee", "agents", runtime, "config.json");
17
+ }
13
18
  function loadPairedConfig(filePath = pairedConfigPath()) {
14
19
  try {
15
20
  const raw = fs.readFileSync(filePath, "utf8");
@@ -21,13 +26,21 @@ function loadPairedConfig(filePath = pairedConfigPath()) {
21
26
  function savePairedConfig(filePath, config) {
22
27
  const dir = path.dirname(filePath);
23
28
  fs.mkdirSync(dir, { recursive: true, mode: 448 });
29
+ const kojeeRoot = path.join(os.homedir(), ".kojee");
30
+ if (fs.existsSync(kojeeRoot)) secureDir(kojeeRoot);
31
+ const agentsRoot = path.join(kojeeRoot, "agents");
32
+ if (fs.existsSync(agentsRoot)) secureDir(agentsRoot);
24
33
  secureDir(dir);
25
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 384 });
26
- secureFile(filePath);
34
+ const tmp = filePath + ".tmp";
35
+ createSecureFileSync(tmp);
36
+ fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
37
+ secureFile(tmp);
38
+ renameSyncRetry(tmp, filePath);
27
39
  }
28
40
 
29
41
  export {
30
42
  pairedConfigPath,
43
+ perRuntimeConfigPath,
31
44
  loadPairedConfig,
32
45
  savePairedConfig
33
46
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  secureDir,
3
3
  secureFile
4
- } from "./chunk-BLEGIR35.js";
4
+ } from "./chunk-U5HHHRXA.js";
5
5
 
6
6
  // src/tandem/session-discovery.ts
7
7
  import fs from "fs";
@@ -50,8 +50,18 @@ function writeDiscoveryByKey(key, entry) {
50
50
  secureFile(filePath);
51
51
  }
52
52
  function cleanupDiscoveryByKey(key) {
53
+ const filePath = discoveryPathForKey(key);
54
+ try {
55
+ const raw = fs.readFileSync(filePath, "utf8");
56
+ const data = JSON.parse(raw);
57
+ const owner = data.proxyPid ?? data.pid;
58
+ if (typeof owner === "number" && owner !== process.pid && isProcessAlive(owner)) {
59
+ return;
60
+ }
61
+ } catch {
62
+ }
53
63
  try {
54
- fs.unlinkSync(discoveryPathForKey(key));
64
+ fs.unlinkSync(filePath);
55
65
  } catch {
56
66
  }
57
67
  }
@@ -69,7 +79,8 @@ function isProcessAlive(pid) {
69
79
  process.kill(pid, 0);
70
80
  return true;
71
81
  } catch (err) {
72
- if (err.code === "EPERM") return true;
82
+ const code = err.code;
83
+ if (code === "EPERM" || code === "EACCES") return true;
73
84
  return false;
74
85
  }
75
86
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  secureDir,
3
3
  secureFile
4
- } from "./chunk-BLEGIR35.js";
4
+ } from "./chunk-U5HHHRXA.js";
5
5
 
6
6
  // src/auth/keystore.ts
7
7
  import { importJWK, exportJWK, generateKeyPair } from "jose";
@@ -0,0 +1,11 @@
1
+ // src/runtime/npx-launcher.ts
2
+ function wrapNpxLauncher(command, args) {
3
+ if (process.platform === "win32" && command === "npx") {
4
+ return { command: "cmd", args: ["/c", "npx", ...args] };
5
+ }
6
+ return { command, args };
7
+ }
8
+
9
+ export {
10
+ wrapNpxLauncher
11
+ };
@@ -0,0 +1,100 @@
1
+ import {
2
+ wrapNpxLauncher
3
+ } from "./chunk-D6JKFJ6A.js";
4
+ import {
5
+ kojeeHomeDir
6
+ } from "./chunk-SQL56SEB.js";
7
+ import {
8
+ secureFile
9
+ } from "./chunk-U5HHHRXA.js";
10
+
11
+ // src/wizard/capabilities/openclaw-mcp-config.ts
12
+ import crypto from "crypto";
13
+ import fs from "fs";
14
+ import path from "path";
15
+ import os from "os";
16
+ function defaultOpenclawConfigPath() {
17
+ const p = path.join(kojeeHomeDir(), ".openclaw", "openclaw.json");
18
+ if (process.platform === "win32" && !/^[a-zA-Z]:/.test(p)) {
19
+ return path.win32.resolve(path.win32.join(os.homedir(), ".openclaw", "openclaw.json"));
20
+ }
21
+ return p;
22
+ }
23
+ var MCP_SERVER_NAME = "kojee";
24
+ function buildKojeeMcpBlock(configPath, pairedConfigPath) {
25
+ const base = ["-y", "kojee-mcp", ...pairedConfigPath ? ["--paired-config", pairedConfigPath] : []];
26
+ const { command, args } = wrapNpxLauncher("npx", base);
27
+ return { command, args, env: { KOJEE_RUNTIME: "openclaw", KOJEE_OPENCLAW_CONFIG: configPath } };
28
+ }
29
+ function readState(configPath) {
30
+ let raw;
31
+ try {
32
+ raw = fs.readFileSync(configPath, "utf8");
33
+ } catch {
34
+ return { cfg: {}, unparseable: false };
35
+ }
36
+ try {
37
+ const p = JSON.parse(raw);
38
+ return { cfg: p && typeof p === "object" ? p : {}, unparseable: false };
39
+ } catch {
40
+ return { cfg: {}, unparseable: true };
41
+ }
42
+ }
43
+ function atomicWrite(filePath, content) {
44
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
45
+ const tmp = `${filePath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
46
+ fs.writeFileSync(tmp, content, { mode: 384 });
47
+ secureFile(tmp);
48
+ fs.renameSync(tmp, filePath);
49
+ secureFile(filePath);
50
+ }
51
+ function writeOpenclawMcpConfig(configPath, opts) {
52
+ const { cfg, unparseable } = readState(configPath);
53
+ let backedUp;
54
+ if (unparseable) {
55
+ const stamp = (opts.now ? opts.now() : /* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
56
+ backedUp = `${configPath}.corrupt-${stamp}`;
57
+ fs.copyFileSync(configPath, backedUp);
58
+ secureFile(backedUp);
59
+ }
60
+ const next = { ...cfg };
61
+ const prevMcp = next.mcp && typeof next.mcp === "object" ? next.mcp : {};
62
+ const prevServers = prevMcp.servers && typeof prevMcp.servers === "object" ? prevMcp.servers : {};
63
+ next.mcp = {
64
+ ...prevMcp,
65
+ servers: { ...prevServers, [MCP_SERVER_NAME]: buildKojeeMcpBlock(configPath, opts.pairedConfigPath) }
66
+ };
67
+ const prevHooks = next.hooks && typeof next.hooks === "object" ? next.hooks : {};
68
+ const existingToken = typeof prevHooks.token === "string" && prevHooks.token.trim() ? prevHooks.token.trim() : "";
69
+ const hooksTokenGenerated = existingToken === "";
70
+ const wakeToken = existingToken || crypto.randomBytes(32).toString("hex");
71
+ next.hooks = { ...prevHooks, enabled: true, token: wakeToken };
72
+ atomicWrite(configPath, JSON.stringify(next, null, 2) + "\n");
73
+ return { hooksTokenGenerated, wakeToken, ...backedUp ? { backedUp } : {} };
74
+ }
75
+ function removeOpenclawMcpServer(configPath) {
76
+ const { cfg, unparseable } = readState(configPath);
77
+ if (unparseable) return false;
78
+ const mcp = cfg.mcp;
79
+ const servers = mcp?.servers;
80
+ if (!servers || !(MCP_SERVER_NAME in servers)) return false;
81
+ const { [MCP_SERVER_NAME]: _drop, ...restServers } = servers;
82
+ const next = { ...cfg };
83
+ if (Object.keys(restServers).length > 0) {
84
+ next.mcp = { ...mcp, servers: restServers };
85
+ } else {
86
+ const nextMcp = { ...mcp };
87
+ delete nextMcp.servers;
88
+ if (Object.keys(nextMcp).length > 0) next.mcp = nextMcp;
89
+ else delete next.mcp;
90
+ }
91
+ atomicWrite(configPath, JSON.stringify(next, null, 2) + "\n");
92
+ return true;
93
+ }
94
+
95
+ export {
96
+ defaultOpenclawConfigPath,
97
+ MCP_SERVER_NAME,
98
+ writeOpenclawMcpConfig,
99
+ removeOpenclawMcpServer
100
+ };
@@ -0,0 +1,14 @@
1
+ // src/hooks/codex-pending-path.ts
2
+ import os from "os";
3
+ import path from "path";
4
+ function codexPendingMarkerPath() {
5
+ return path.join(os.homedir(), ".kojee", "codex-pending");
6
+ }
7
+ function codexPendingAckPath() {
8
+ return codexPendingMarkerPath() + ".ack";
9
+ }
10
+
11
+ export {
12
+ codexPendingMarkerPath,
13
+ codexPendingAckPath
14
+ };
@@ -0,0 +1,82 @@
1
+ import {
2
+ loadPairedConfig,
3
+ savePairedConfig
4
+ } from "./chunk-5SZHXYPK.js";
5
+ import {
6
+ AuthModule
7
+ } from "./chunk-I67C2HYA.js";
8
+ import {
9
+ createDPoPProof
10
+ } from "./chunk-MIEI4PLB.js";
11
+ import {
12
+ loadKeystore
13
+ } from "./chunk-6G6YYST6.js";
14
+ import {
15
+ renameSyncRetry
16
+ } from "./chunk-U5HHHRXA.js";
17
+
18
+ // src/tandem/pair.ts
19
+ import fs from "fs";
20
+ async function fetchDisplayIdentity(url, code, keystorePath) {
21
+ try {
22
+ const ks = await loadKeystore(keystorePath, url).catch(() => null);
23
+ if (!ks) return {};
24
+ const meUrl = `${url}/api/v1/users/me/`;
25
+ const doFetch = async (nonce) => {
26
+ const proof = await createDPoPProof(ks.privateKey, ks.kid, "GET", meUrl, nonce, code);
27
+ return fetch(meUrl, { headers: { Authorization: `DPoP ${code}`, DPoP: proof } });
28
+ };
29
+ let res = await doFetch();
30
+ if (res.status === 401) {
31
+ const nonce = res.headers.get("DPoP-Nonce");
32
+ if (nonce) res = await doFetch(nonce);
33
+ }
34
+ if (res.ok) {
35
+ const body = await res.json();
36
+ return {
37
+ ...body.principal_id ? { principal_id: body.principal_id } : {},
38
+ ...body.agent_id ? { agent_id: body.agent_id } : {}
39
+ };
40
+ }
41
+ } catch {
42
+ }
43
+ return {};
44
+ }
45
+ async function runPair(opts) {
46
+ if (!opts.allowOverwrite && loadPairedConfig(opts.configPath) !== null) {
47
+ throw new Error(
48
+ `Already paired (config exists at ${opts.configPath}). To re-pair this slot, delete the config file first. For a second account, use --keystore-path /custom/keypair.json.`
49
+ );
50
+ }
51
+ const existing = await loadKeystore(opts.keystorePath, opts.url).catch(() => null);
52
+ if (!existing) {
53
+ const tmpKeystore = `${opts.keystorePath}.tmp-${process.pid}`;
54
+ try {
55
+ fs.unlinkSync(tmpKeystore);
56
+ } catch {
57
+ }
58
+ const auth = new AuthModule(opts.code, opts.url, tmpKeystore);
59
+ await auth.ensureEnrolled();
60
+ renameSyncRetry(tmpKeystore, opts.keystorePath);
61
+ }
62
+ const { principal_id, agent_id } = await fetchDisplayIdentity(
63
+ opts.url,
64
+ opts.code,
65
+ opts.keystorePath
66
+ );
67
+ const config = {
68
+ token: opts.code,
69
+ broker_url: opts.url,
70
+ paired_at: (/* @__PURE__ */ new Date()).toISOString(),
71
+ ...opts.runtime ? { runtime: opts.runtime } : {},
72
+ ...principal_id ? { principal_id } : {},
73
+ ...agent_id ? { agent_id } : {}
74
+ };
75
+ savePairedConfig(opts.configPath, config);
76
+ const message = principal_id && agent_id ? `Paired as ${principal_id} (${agent_id}). Keypair: ${opts.keystorePath}. Config: ${opts.configPath}.` : `Paired. Keypair: ${opts.keystorePath}. Config: ${opts.configPath}.`;
77
+ return { message };
78
+ }
79
+
80
+ export {
81
+ runPair
82
+ };
@@ -0,0 +1,45 @@
1
+ import {
2
+ loadPairedConfig
3
+ } from "./chunk-5SZHXYPK.js";
4
+ import {
5
+ deriveKeystorePath
6
+ } from "./chunk-6G6YYST6.js";
7
+
8
+ // src/auth/resolve-slot.ts
9
+ import fs from "fs";
10
+ import os from "os";
11
+ import path from "path";
12
+ var CONNECT_RUNTIMES = ["claude-code", "codex", "openclaw"];
13
+ function isConnectRuntime(v) {
14
+ return v !== void 0 && CONNECT_RUNTIMES.includes(v);
15
+ }
16
+ function resolvePairedSlot(opts) {
17
+ if (opts.pairedConfig) {
18
+ return { configPath: path.resolve(opts.pairedConfig), scope: "paired" };
19
+ }
20
+ const dir = opts.kojeeDir ?? path.join(os.homedir(), ".kojee");
21
+ if (isConnectRuntime(opts.runtime)) {
22
+ const perRuntime = path.join(dir, "agents", opts.runtime, "config.json");
23
+ if (fs.existsSync(perRuntime)) return { configPath: perRuntime, scope: "paired" };
24
+ }
25
+ return { configPath: path.join(dir, "config.json"), scope: "legacy" };
26
+ }
27
+ function resolveLaunchCreds(opts) {
28
+ const { configPath } = resolvePairedSlot({ pairedConfig: opts.pairedConfig });
29
+ const cfg = loadPairedConfig(configPath);
30
+ if (!cfg || typeof cfg.token !== "string" || cfg.token === "") {
31
+ return {
32
+ ok: false,
33
+ configPath,
34
+ error: `Not connected for this runtime (paired-config: ${configPath}) \u2014 run \`kojee-mcp connect <code> --runtime <id>\`.`
35
+ };
36
+ }
37
+ const url = (opts.url ?? cfg.broker_url).replace(/\/+$/, "");
38
+ return { ok: true, token: cfg.token, url, keystorePath: deriveKeystorePath(cfg.token), configPath };
39
+ }
40
+
41
+ export {
42
+ CONNECT_RUNTIMES,
43
+ resolvePairedSlot,
44
+ resolveLaunchCreds
45
+ };
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  claudeCodeAdapter
3
- } from "./chunk-N35X22G7.js";
3
+ } from "./chunk-UPJV7GBE.js";
4
4
  import {
5
5
  GatewayClient,
6
6
  applyStableSessionId
7
- } from "./chunk-KH5YEJIM.js";
7
+ } from "./chunk-247WFMCJ.js";
8
8
  import {
9
9
  AuthModule
10
- } from "./chunk-JXMVZEQ7.js";
10
+ } from "./chunk-I67C2HYA.js";
11
11
  import {
12
12
  createMcpServer,
13
13
  startMcpServer
14
- } from "./chunk-ALZX3SGI.js";
14
+ } from "./chunk-GNLCUJBK.js";
15
15
  import {
16
16
  findClaudeAncestorPid
17
- } from "./chunk-VHKPWUX7.js";
17
+ } from "./chunk-XJEBJIQE.js";
18
18
 
19
19
  // src/index.ts
20
20
  import fs from "fs";
@@ -93,6 +93,7 @@ import psList from "ps-list";
93
93
  async function detectRuntime(env = process.env) {
94
94
  if (env["KOJEE_RUNTIME"] === "claude-code") return "claude-code";
95
95
  if (env["KOJEE_RUNTIME"] === "codex") return "codex";
96
+ if (env["KOJEE_RUNTIME"] === "openclaw") return "openclaw";
96
97
  if (env["CLAUDE_CODE_SESSION_ID"]) return "claude-code";
97
98
  const ancestor = await detectRuntimeFromAncestry();
98
99
  if (ancestor) return ancestor;
@@ -132,6 +133,17 @@ var codexAdapter = {
132
133
  }
133
134
  };
134
135
 
136
+ // src/adapters/openclaw.ts
137
+ var openclawAdapter = {
138
+ runtime: "openclaw",
139
+ supportsChannels: false,
140
+ formatTandemEvent() {
141
+ throw new Error(
142
+ "openclawAdapter.formatTandemEvent() is unreachable \u2014 OpenClaw receives events via the /wake injector delivery, not channels; server.ts gates this on supportsChannels."
143
+ );
144
+ }
145
+ };
146
+
135
147
  // src/adapters/unknown.ts
136
148
  var unknownAdapter = {
137
149
  runtime: "unknown",
@@ -155,6 +167,7 @@ async function selectAdapter() {
155
167
  const runtime = await detectRuntime();
156
168
  if (runtime === "claude-code") return claudeCodeAdapter;
157
169
  if (runtime === "codex") return codexAdapter;
170
+ if (runtime === "openclaw") return openclawAdapter;
158
171
  return unknownAdapter;
159
172
  }
160
173
  async function listTandemIds(gateway) {
@@ -238,7 +251,7 @@ async function startProxy(config) {
238
251
  }
239
252
  console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
240
253
  let server;
241
- const { selectDelivery } = await import("./registry-YLOFZDI2.js");
254
+ const { selectDelivery } = await import("./registry-ZZZ26WGA.js");
242
255
  const delivery = selectDelivery(adapter.runtime, {
243
256
  supportsChannels: adapter.supportsChannels
244
257
  });
@@ -274,8 +287,8 @@ async function startProxy(config) {
274
287
  process.on("SIGHUP", () => shutdown("SIGHUP"));
275
288
  process.on("SIGINT", () => shutdown("SIGINT"));
276
289
  process.on("SIGTERM", () => shutdown("SIGTERM"));
277
- if (ccPid !== null) {
278
- const { createParentWatchdog } = await import("./parent-watchdog-TLU355FB.js");
290
+ const { createParentWatchdog, shouldArmWatchdog } = await import("./parent-watchdog-LTUNF7PD.js");
291
+ if (shouldArmWatchdog(ccPid)) {
279
292
  const watchdog = createParentWatchdog({
280
293
  ccPid,
281
294
  onParentGone: () => shutdown("parent (Claude Code) gone")
@@ -284,7 +297,7 @@ async function startProxy(config) {
284
297
  teardownSteps.push(() => watchdog.stop());
285
298
  } else {
286
299
  console.error(
287
- "[kojee-mcp] no Claude Code ancestor found \u2014 parent-liveness watchdog NOT armed (stdin/signal handlers still cover clean exits)"
300
+ "[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."
288
301
  );
289
302
  }
290
303
  await startMcpServer(server);
@@ -1,7 +1,9 @@
1
1
  import {
2
- createDPoPProof,
3
2
  getSessionId
4
- } from "./chunk-NR4Y54OL.js";
3
+ } from "./chunk-Z5LPNJQ6.js";
4
+ import {
5
+ createDPoPProof
6
+ } from "./chunk-MIEI4PLB.js";
5
7
 
6
8
  // src/tandem/event-stream.ts
7
9
  import { Agent } from "undici";
@@ -116,7 +118,13 @@ async function startEventStream(opts) {
116
118
  if (!opened && stopped) return;
117
119
  } catch (err) {
118
120
  if (stopped) return;
119
- console.error("[event-stream] disconnect:", err.message);
121
+ console.error(
122
+ "[event-stream] disconnect:",
123
+ err?.message,
124
+ // Node/undici stash the real transport error here (errno/TLS); the
125
+ // top-level message is a generic "fetch failed" that drops it.
126
+ err?.cause ?? ""
127
+ );
120
128
  state.connected = false;
121
129
  await emitStatus(opts, `status=disconnected reason=${statusReason(err)}`);
122
130
  }
@@ -300,8 +308,24 @@ async function emitStatus(opts, fields) {
300
308
  }
301
309
  }
302
310
  function statusReason(err) {
303
- const msg = err?.message ?? String(err);
304
- return msg.replace(/\s+/g, "_").slice(0, 60);
311
+ const sanitize = (s) => s.replace(/\s+/g, "_");
312
+ const base = sanitize(err?.message ?? String(err));
313
+ const cause = err?.cause;
314
+ let token = base;
315
+ if (cause != null) {
316
+ let causePart;
317
+ if (typeof cause === "string") {
318
+ causePart = cause;
319
+ } else {
320
+ const code = cause.code;
321
+ const message = cause.message;
322
+ if (typeof code === "string" && code) causePart = code;
323
+ else if (typeof message === "string" && message) causePart = message;
324
+ else causePart = String(cause);
325
+ }
326
+ token = `${base}:${sanitize(causePart)}`;
327
+ }
328
+ return token.slice(0, 80);
305
329
  }
306
330
  function serializeCursorMap(cursors) {
307
331
  return [...cursors.entries()].map(([tandemId, cursor]) => `${tandemId}:${cursor}`).join(",");
@@ -423,6 +447,7 @@ export {
423
447
  createAdaptiveWatchdog,
424
448
  createBackoffController,
425
449
  startEventStream,
450
+ statusReason,
426
451
  serializeCursorMap,
427
452
  sanitizeDisplayname,
428
453
  normalizeBackendEvent
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  secureDir,
3
3
  secureFile
4
- } from "./chunk-BLEGIR35.js";
4
+ } from "./chunk-U5HHHRXA.js";
5
5
 
6
6
  // src/tandem/control-token.ts
7
7
  import crypto from "crypto";