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.
- package/README.md +13 -8
- package/dist/ancestry-A2F5KQ6A.js +10 -0
- package/dist/cc-session-id-RURNIHHC.js +10 -0
- package/dist/{chunk-KH5YEJIM.js → chunk-247WFMCJ.js} +7 -20
- package/dist/chunk-5DHIUN73.js +25 -0
- package/dist/{chunk-YH27B6SW.js → chunk-5SZHXYPK.js} +16 -3
- package/dist/{chunk-DO42NPNR.js → chunk-67F67AQ6.js} +14 -3
- package/dist/{chunk-CH32ELFX.js → chunk-6G6YYST6.js} +1 -1
- package/dist/chunk-D6JKFJ6A.js +11 -0
- package/dist/chunk-E6WMFMM2.js +100 -0
- package/dist/chunk-EBYUJM3H.js +14 -0
- package/dist/chunk-EIAUW6KO.js +82 -0
- package/dist/chunk-EIH2LNF4.js +45 -0
- package/dist/{chunk-BYV7CWNY.js → chunk-FBJCPRVH.js} +22 -9
- package/dist/{chunk-XN3IC4B5.js → chunk-FSKGQ6GT.js} +30 -5
- package/dist/{chunk-GI2CKKBL.js → chunk-GKXHKR3B.js} +1 -1
- package/dist/{chunk-ALZX3SGI.js → chunk-GNLCUJBK.js} +13 -27
- package/dist/{chunk-JXMVZEQ7.js → chunk-I67C2HYA.js} +1 -1
- package/dist/chunk-KNEJTD6G.js +31 -0
- package/dist/{chunk-65KRRDHP.js → chunk-KPMD72FY.js} +94 -28
- package/dist/{chunk-NR4Y54OL.js → chunk-MIEI4PLB.js} +1 -19
- package/dist/chunk-NE2F5CKS.js +113 -0
- package/dist/chunk-QJFMU4QC.js +33 -0
- package/dist/{chunk-EW72ZNQL.js → chunk-RDLF4NQC.js} +1 -1
- package/dist/{chunk-Q7T3WAST.js → chunk-SSW5AQSR.js} +6 -1
- package/dist/{chunk-DURRQDYI.js → chunk-TNZITJAB.js} +45 -15
- package/dist/chunk-U5HHHRXA.js +103 -0
- package/dist/{setup-handler-7HOFAD7C.js → chunk-UFHGZUST.js} +431 -253
- package/dist/{chunk-N35X22G7.js → chunk-UPJV7GBE.js} +1 -1
- package/dist/chunk-WBXU27BF.js +35 -0
- package/dist/chunk-XJEBJIQE.js +52 -0
- package/dist/chunk-Z5LPNJQ6.js +20 -0
- package/dist/cli.js +63 -25
- package/dist/codex-stop-hook-BMOJVM6O.js +96 -0
- package/dist/connect-handler-4DRTFMOB.js +138 -0
- package/dist/{control-token-4BUCTYQB.js → control-token-KXSZ3GBY.js} +2 -2
- package/dist/{doctor-WVX3WCYX.js → doctor-5QJ3HGNR.js} +48 -22
- package/dist/{doctor-codex-Z5M4SFBG.js → doctor-codex-VGJKTX2E.js} +33 -7
- package/dist/doctor-openclaw-LL6NSPLC.js +86 -0
- package/dist/{event-log-HOBO3ECB.js → event-log-2NBJEIEP.js} +5 -3
- package/dist/{event-stream-SYBUJ5EH.js → event-stream-KRYWEYWO.js} +7 -4
- package/dist/index.js +13 -10
- package/dist/{install-LJY2CHKG.js → install-V7LSQCYZ.js} +37 -22
- package/dist/{keystore-XLEV3FL5.js → keystore-LQNFDHGO.js} +2 -2
- package/dist/lib.d.ts +4 -1
- package/dist/lib.js +11 -8
- package/dist/pair-HTCVSMR6.js +11 -0
- package/dist/{paired-config-JTFLHMZ2.js → paired-config-GUDDO2CH.js} +4 -2
- package/dist/{parent-watchdog-TLU355FB.js → parent-watchdog-LTUNF7PD.js} +24 -5
- package/dist/{registry-YLOFZDI2.js → registry-ZZZ26WGA.js} +197 -24
- package/dist/resolve-slot-BFIB46Y5.js +13 -0
- package/dist/{runtime-record-WO4IECM6.js → runtime-record-OXRLTOLC.js} +2 -2
- package/dist/{send-cli-IYJF32PE.js → send-cli-45RLGYIC.js} +22 -10
- package/dist/{server-R24AVNTI.js → server-ITPFQVTK.js} +5 -2
- package/dist/{session-discovery-FNMJGFPM.js → session-discovery-NHCZ2XWA.js} +2 -2
- package/dist/setup-handler-JFM45NCN.js +160 -0
- package/dist/{stop-hook-TCPJFGFF.js → stop-hook-5ABGTC2O.js} +17 -20
- package/dist/{tail-stream-4IMXI44S.js → tail-stream-NBGHHBS4.js} +40 -22
- package/dist/{user-prompt-submit-hook-FQ43NIHC.js → user-prompt-submit-hook-DTXXFDSD.js} +11 -14
- package/package.json +3 -2
- package/skills/using-tandems/SKILL.md +52 -0
- package/dist/ancestry-ONFBQEP5.js +0 -8
- package/dist/chunk-BLEGIR35.js +0 -43
- package/dist/chunk-CLKCNV2A.js +0 -100
- package/dist/chunk-KFDZWNQO.js +0 -73
- package/dist/chunk-OGHDTFAX.js +0 -50
- package/dist/chunk-VHKPWUX7.js +0 -41
- package/dist/codex-stop-hook-RWQSZTST.js +0 -16
- package/dist/doctor-openclaw-SS2TMQOX.js +0 -95
- 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
|
-
|
|
166
|
-
|
|
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
|
-
- **
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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.
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createDPoPProof,
|
|
3
2
|
deriveStableSessionId,
|
|
4
3
|
getSessionId,
|
|
5
4
|
setStableSessionId
|
|
6
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
26
|
-
|
|
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-
|
|
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(
|
|
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
|
-
|
|
82
|
+
const code = err.code;
|
|
83
|
+
if (code === "EPERM" || code === "EACCES") return true;
|
|
73
84
|
return false;
|
|
74
85
|
}
|
|
75
86
|
}
|
|
@@ -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-
|
|
3
|
+
} from "./chunk-UPJV7GBE.js";
|
|
4
4
|
import {
|
|
5
5
|
GatewayClient,
|
|
6
6
|
applyStableSessionId
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-247WFMCJ.js";
|
|
8
8
|
import {
|
|
9
9
|
AuthModule
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-I67C2HYA.js";
|
|
11
11
|
import {
|
|
12
12
|
createMcpServer,
|
|
13
13
|
startMcpServer
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-GNLCUJBK.js";
|
|
15
15
|
import {
|
|
16
16
|
findClaudeAncestorPid
|
|
17
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
|
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-
|
|
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(
|
|
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
|
|
304
|
-
|
|
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
|