kojee-mcp 0.5.15 → 0.5.17
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/dist/{chunk-IMOEZ4NJ.js → chunk-34IRTWP6.js} +45 -14
- package/dist/chunk-CLKCNV2A.js +100 -0
- package/dist/{chunk-3H3TL34J.js → chunk-IRD26KZG.js} +2 -2
- package/dist/cli.js +5 -5
- package/dist/{doctor-FVTALRQD.js → doctor-SMFND2UW.js} +6 -0
- package/dist/doctor-openclaw-SS2TMQOX.js +95 -0
- package/dist/{gateway-client-C6yx1mfM.d.ts → gateway-client-CbM2OC_w.d.ts} +19 -0
- package/dist/{hook-server-T2Z444OV.js → hook-server-XK2NHLJV.js} +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/lib.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/{registry-TGALQP6M.js → registry-HPCRIWRF.js} +45 -1
- package/dist/{send-cli-RH7D4JDP.js → send-cli-T6RPZZQ4.js} +1 -1
- package/dist/{wizard-3FDEWEYO.js → wizard-OI7VDU27.js} +15 -85
- package/package.json +1 -1
|
@@ -120,22 +120,10 @@ var GatewayClient = class {
|
|
|
120
120
|
return result ?? { content: [{ type: "text", text: "No result" }] };
|
|
121
121
|
}
|
|
122
122
|
async sendHttpRequest(rpcRequest, signal) {
|
|
123
|
-
const
|
|
124
|
-
this.privateKey,
|
|
125
|
-
this.kid,
|
|
126
|
-
"POST",
|
|
127
|
-
this.endpoint,
|
|
128
|
-
this.currentNonce,
|
|
129
|
-
this.token
|
|
130
|
-
);
|
|
123
|
+
const headers = await this.buildAuthHeaders("POST", this.endpoint);
|
|
131
124
|
return fetch(this.endpoint, {
|
|
132
125
|
method: "POST",
|
|
133
|
-
headers
|
|
134
|
-
"Content-Type": "application/json",
|
|
135
|
-
Authorization: `DPoP ${this.token}`,
|
|
136
|
-
DPoP: proof,
|
|
137
|
-
"Mcp-Session-Id": getSessionId()
|
|
138
|
-
},
|
|
126
|
+
headers,
|
|
139
127
|
body: JSON.stringify(rpcRequest),
|
|
140
128
|
// ROUND-3 MAJOR A: the caller's AbortSignal rides HERE (a real fetch
|
|
141
129
|
// option), never inside the JSON-RPC body. `undefined` is a valid value
|
|
@@ -143,6 +131,49 @@ var GatewayClient = class {
|
|
|
143
131
|
...signal ? { signal } : {}
|
|
144
132
|
});
|
|
145
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Build the DPoP-authed request headers shared by every backend call: a fresh
|
|
136
|
+
* DPoP proof bound to (method, url) + the current nonce + the access token, the
|
|
137
|
+
* `Authorization: DPoP <token>` header, the stable `Mcp-Session-Id`, and the
|
|
138
|
+
* JSON content type. Factored out of sendHttpRequest so the REST helper below
|
|
139
|
+
* reuses the EXACT same auth construction (no auth-path divergence).
|
|
140
|
+
*/
|
|
141
|
+
async buildAuthHeaders(method, url) {
|
|
142
|
+
const proof = await createDPoPProof(
|
|
143
|
+
this.privateKey,
|
|
144
|
+
this.kid,
|
|
145
|
+
method,
|
|
146
|
+
url,
|
|
147
|
+
this.currentNonce,
|
|
148
|
+
this.token
|
|
149
|
+
);
|
|
150
|
+
return {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
Authorization: `DPoP ${this.token}`,
|
|
153
|
+
DPoP: proof,
|
|
154
|
+
"Mcp-Session-Id": getSessionId()
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Send an authed REST request (NOT JSON-RPC) to `<brokerUrl><path>`, reusing
|
|
159
|
+
* the SAME DPoP-proof + Authorization + Mcp-Session-Id header construction as
|
|
160
|
+
* the JSON-RPC path (buildAuthHeaders). Returns the RAW `Response` — callers
|
|
161
|
+
* own status handling; there is deliberately NO error-translation here, so a
|
|
162
|
+
* best-effort caller (presence) can fire-and-forget and swallow non-2xx.
|
|
163
|
+
*
|
|
164
|
+
* `body` is JSON-serialized when present (omitted otherwise). `signal` rides
|
|
165
|
+
* the real fetch option (per the JSON-RPC path's ROUND-3 convention).
|
|
166
|
+
*/
|
|
167
|
+
async sendRest(method, path, body, signal) {
|
|
168
|
+
const url = `${this.brokerUrl}${path}`;
|
|
169
|
+
const headers = await this.buildAuthHeaders(method, url);
|
|
170
|
+
return fetch(url, {
|
|
171
|
+
method,
|
|
172
|
+
headers,
|
|
173
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
174
|
+
...signal ? { signal } : {}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
146
177
|
trackNonce(response) {
|
|
147
178
|
const nonce = response.headers.get("DPoP-Nonce");
|
|
148
179
|
if (nonce) {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
kojeeHomeDir
|
|
3
|
+
} from "./chunk-SQL56SEB.js";
|
|
4
|
+
import {
|
|
5
|
+
secureFile
|
|
6
|
+
} from "./chunk-BLEGIR35.js";
|
|
7
|
+
|
|
8
|
+
// src/wizard/capabilities/openclaw-channel-config.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
function defaultOpenclawConfigPath() {
|
|
12
|
+
return path.join(kojeeHomeDir(), ".openclaw", "config.json");
|
|
13
|
+
}
|
|
14
|
+
var CHANNEL_ID = "kojee-tandem";
|
|
15
|
+
function mergeOpenclawChannelConfig(existing, block) {
|
|
16
|
+
const prevChannels = existing.channels && typeof existing.channels === "object" ? existing.channels : {};
|
|
17
|
+
return {
|
|
18
|
+
...existing,
|
|
19
|
+
channels: {
|
|
20
|
+
...prevChannels,
|
|
21
|
+
[CHANNEL_ID]: { ...block }
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function readOpenclawConfig(configPath) {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
28
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
29
|
+
} catch {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function readOpenclawConfigState(configPath) {
|
|
34
|
+
let raw;
|
|
35
|
+
try {
|
|
36
|
+
raw = fs.readFileSync(configPath, "utf8");
|
|
37
|
+
} catch {
|
|
38
|
+
return { cfg: {}, unparseable: false };
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
return {
|
|
43
|
+
cfg: parsed && typeof parsed === "object" ? parsed : {},
|
|
44
|
+
unparseable: false
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
return { cfg: {}, unparseable: true };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function atomicWrite(filePath, content, secret) {
|
|
51
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
52
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
53
|
+
fs.writeFileSync(tmp, content, { ...secret ? { mode: 384 } : {} });
|
|
54
|
+
if (secret) secureFile(tmp);
|
|
55
|
+
fs.renameSync(tmp, filePath);
|
|
56
|
+
if (secret) secureFile(filePath);
|
|
57
|
+
}
|
|
58
|
+
function writeOpenclawChannelConfig(configPath, block, opts = {}) {
|
|
59
|
+
const { cfg, unparseable } = readOpenclawConfigState(configPath);
|
|
60
|
+
let backedUp;
|
|
61
|
+
if (unparseable) {
|
|
62
|
+
const stamp = opts.timestamp ?? corruptStamp();
|
|
63
|
+
backedUp = `${configPath}.corrupt-${stamp}`;
|
|
64
|
+
fs.copyFileSync(configPath, backedUp);
|
|
65
|
+
}
|
|
66
|
+
const merged = mergeOpenclawChannelConfig(cfg, block);
|
|
67
|
+
atomicWrite(configPath, JSON.stringify(merged, null, 2) + "\n", Boolean(block.credential));
|
|
68
|
+
return backedUp ? { backedUp } : {};
|
|
69
|
+
}
|
|
70
|
+
function corruptStamp() {
|
|
71
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
72
|
+
}
|
|
73
|
+
function removeOpenclawChannel(configPath) {
|
|
74
|
+
let raw;
|
|
75
|
+
try {
|
|
76
|
+
raw = fs.readFileSync(configPath, "utf8");
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
let cfg;
|
|
81
|
+
try {
|
|
82
|
+
cfg = JSON.parse(raw);
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const channels = cfg.channels && typeof cfg.channels === "object" ? cfg.channels : void 0;
|
|
87
|
+
if (!channels || !(CHANNEL_ID in channels)) return false;
|
|
88
|
+
const { [CHANNEL_ID]: _removed, ...rest } = channels;
|
|
89
|
+
const next = { ...cfg, channels: rest };
|
|
90
|
+
atomicWrite(configPath, JSON.stringify(next, null, 2) + "\n", false);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
defaultOpenclawConfigPath,
|
|
96
|
+
CHANNEL_ID,
|
|
97
|
+
readOpenclawConfig,
|
|
98
|
+
writeOpenclawChannelConfig,
|
|
99
|
+
removeOpenclawChannel
|
|
100
|
+
};
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
GatewayClient,
|
|
6
6
|
applyStableSessionId
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-34IRTWP6.js";
|
|
8
8
|
import {
|
|
9
9
|
AuthModule
|
|
10
10
|
} from "./chunk-JXMVZEQ7.js";
|
|
@@ -312,7 +312,7 @@ async function startProxy(config) {
|
|
|
312
312
|
}
|
|
313
313
|
console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
|
|
314
314
|
let server;
|
|
315
|
-
const { selectDelivery } = await import("./registry-
|
|
315
|
+
const { selectDelivery } = await import("./registry-HPCRIWRF.js");
|
|
316
316
|
const delivery = selectDelivery(adapter.runtime, {
|
|
317
317
|
supportsChannels: adapter.supportsChannels
|
|
318
318
|
});
|
package/dist/cli.js
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
} from "./chunk-OGHDTFAX.js";
|
|
5
5
|
import {
|
|
6
6
|
startProxy
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IRD26KZG.js";
|
|
8
8
|
import "./chunk-XXFVWP6H.js";
|
|
9
9
|
import {
|
|
10
10
|
pairedConfigPath
|
|
11
11
|
} from "./chunk-YH27B6SW.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-34IRTWP6.js";
|
|
13
13
|
import "./chunk-JXMVZEQ7.js";
|
|
14
14
|
import "./chunk-NR4Y54OL.js";
|
|
15
15
|
import {
|
|
@@ -79,7 +79,7 @@ Restart Claude Code for hooks to take effect.`
|
|
|
79
79
|
program.command("send <tandem_id>").description(
|
|
80
80
|
"Send a Tandem message using this machine's paired credentials (~/.kojee). Prints one JSON envelope to stdout: {ok, message_id, cursor, text} on success, {ok:false, error:<typed code>, message} on failure (exit 1)."
|
|
81
81
|
).requiredOption("--body <text>", "Message body (required)").option("--reply-to <message_id>", "Message id this send replies to").option("--kind <kind>", "Message kind: message | status (default: backend default)").action(async (tandemId, opts) => {
|
|
82
|
-
const { runSendCli } = await import("./send-cli-
|
|
82
|
+
const { runSendCli } = await import("./send-cli-T6RPZZQ4.js");
|
|
83
83
|
const { exitCode, envelope } = await runSendCli({
|
|
84
84
|
tandemId,
|
|
85
85
|
body: opts.body,
|
|
@@ -99,7 +99,7 @@ program.command("tail <path>").description("Stream a file's contents and follow
|
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
program.command("doctor").description("Diagnose the kojee wake path (proxy, hook-server, SSE stream, event log, Monitor) and print the exact wake recipe").action(async () => {
|
|
102
|
-
const { runDoctor } = await import("./doctor-
|
|
102
|
+
const { runDoctor } = await import("./doctor-SMFND2UW.js");
|
|
103
103
|
const code = await runDoctor();
|
|
104
104
|
process.exit(code);
|
|
105
105
|
});
|
|
@@ -140,7 +140,7 @@ program.command("init").description(
|
|
|
140
140
|
console.error("Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run `init` \u2014 or pass --token/--pair-code, or run `init` in a terminal for the guided wizard.");
|
|
141
141
|
process.exit(1);
|
|
142
142
|
}
|
|
143
|
-
const { runWizard } = await import("./wizard-
|
|
143
|
+
const { runWizard } = await import("./wizard-OI7VDU27.js");
|
|
144
144
|
const result = await runWizard({
|
|
145
145
|
...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
|
|
146
146
|
...opts.uninstall ? { uninstall: true } : {},
|
|
@@ -333,6 +333,12 @@ async function runDoctor() {
|
|
|
333
333
|
console.error(formatCodexDoctorReport(report2));
|
|
334
334
|
return report2.verdict === "broken" ? 1 : 0;
|
|
335
335
|
}
|
|
336
|
+
if (readRecordedRuntime() === "openclaw") {
|
|
337
|
+
const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-SS2TMQOX.js");
|
|
338
|
+
const report2 = collectOpenclawDoctorReport();
|
|
339
|
+
console.error(formatOpenclawDoctorReport(report2));
|
|
340
|
+
return report2.verdict === "broken" ? 1 : 0;
|
|
341
|
+
}
|
|
336
342
|
const report = await collectDoctorReport();
|
|
337
343
|
console.error(formatDoctorReport(report));
|
|
338
344
|
return report.verdict === "broken" ? 1 : 0;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHANNEL_ID,
|
|
3
|
+
defaultOpenclawConfigPath,
|
|
4
|
+
readOpenclawConfig
|
|
5
|
+
} from "./chunk-CLKCNV2A.js";
|
|
6
|
+
import "./chunk-SQL56SEB.js";
|
|
7
|
+
import {
|
|
8
|
+
loadPairedConfig
|
|
9
|
+
} from "./chunk-YH27B6SW.js";
|
|
10
|
+
import "./chunk-BLEGIR35.js";
|
|
11
|
+
|
|
12
|
+
// src/doctor-openclaw.ts
|
|
13
|
+
import { execFileSync } from "child_process";
|
|
14
|
+
var WIZARD_RERUN = "re-run `kojee-mcp init --runtime openclaw`";
|
|
15
|
+
var VERIFY_HINT = "openclaw plugins inspect kojee-tandem / openclaw channels status";
|
|
16
|
+
function resolveCredentialSource(block, env, loadPaired) {
|
|
17
|
+
const channelCred = typeof block.credential === "string" ? block.credential.trim() : "";
|
|
18
|
+
if (channelCred) return "config";
|
|
19
|
+
if ((env["KOJEE_GATEWAY_TOKEN"] ?? "").trim()) return "env";
|
|
20
|
+
const paired = loadPaired();
|
|
21
|
+
if (paired?.token) return "paired-config";
|
|
22
|
+
return "none";
|
|
23
|
+
}
|
|
24
|
+
function defaultCliProbe() {
|
|
25
|
+
try {
|
|
26
|
+
execFileSync("openclaw", ["plugins", "inspect", CHANNEL_ID], { stdio: "ignore" });
|
|
27
|
+
return true;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err?.code === "ENOENT") return null;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function collectOpenclawDoctorReport(deps = {}) {
|
|
34
|
+
const readConfig = deps.readConfig ?? (() => readOpenclawConfig(defaultOpenclawConfigPath()));
|
|
35
|
+
const env = deps.env ?? process.env;
|
|
36
|
+
const loadPaired = deps.loadPaired ?? (() => loadPairedConfig());
|
|
37
|
+
const checks = [];
|
|
38
|
+
const cfg = readConfig();
|
|
39
|
+
const channels = cfg.channels && typeof cfg.channels === "object" ? cfg.channels : {};
|
|
40
|
+
const block = channels[CHANNEL_ID] && typeof channels[CHANNEL_ID] === "object" ? channels[CHANNEL_ID] : null;
|
|
41
|
+
const enabled = block?.enabled === true;
|
|
42
|
+
checks.push({
|
|
43
|
+
name: `~/.openclaw/config.json channels.${CHANNEL_ID}`,
|
|
44
|
+
ok: enabled,
|
|
45
|
+
detail: enabled ? "present with enabled:true (the wizard-written channel block)" : block ? `present but enabled:false \u2014 ${WIZARD_RERUN}` : `MISSING channels.${CHANNEL_ID} block \u2014 ${WIZARD_RERUN}`
|
|
46
|
+
});
|
|
47
|
+
const source = resolveCredentialSource(block ?? {}, env, loadPaired);
|
|
48
|
+
const credOk = source !== "none";
|
|
49
|
+
checks.push({
|
|
50
|
+
name: "gateway credential",
|
|
51
|
+
ok: credOk,
|
|
52
|
+
detail: credOk ? `resolves \u2014 source: ${source} (token value never printed)` : `NONE resolves (no channel credential, KOJEE_GATEWAY_TOKEN, or paired ~/.kojee/config.json) \u2014 ${WIZARD_RERUN}`
|
|
53
|
+
});
|
|
54
|
+
const probed = deps.openclawCliProbe ? deps.openclawCliProbe() : defaultCliProbe();
|
|
55
|
+
if (probed === true) {
|
|
56
|
+
checks.push({
|
|
57
|
+
name: "openclaw plugin (kojee-tandem)",
|
|
58
|
+
ok: true,
|
|
59
|
+
detail: `discoverable via OpenClaw's plugin manager (\`${VERIFY_HINT}\`)`
|
|
60
|
+
});
|
|
61
|
+
} else if (probed === false) {
|
|
62
|
+
checks.push({
|
|
63
|
+
name: "openclaw plugin (kojee-tandem)",
|
|
64
|
+
ok: "warn",
|
|
65
|
+
detail: `NOT found by OpenClaw's plugin manager \u2014 install it: \`openclaw plugins install\`, then \`${VERIFY_HINT}\``
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
checks.push({
|
|
69
|
+
name: "openclaw plugin (kojee-tandem)",
|
|
70
|
+
ok: "warn",
|
|
71
|
+
detail: `owner-verify step (delegated install): confirm with \`${VERIFY_HINT}\``
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const verdict = checks.some((c) => c.ok === false) ? "broken" : checks.some((c) => c.ok === "warn") ? "degraded" : "healthy";
|
|
75
|
+
return { checks, verdict };
|
|
76
|
+
}
|
|
77
|
+
function formatOpenclawDoctorReport(report) {
|
|
78
|
+
const mark = (ok) => ok === true ? "\u2713" : ok === "warn" ? "\u26A0" : ok === "unknown" ? "?" : "\u2717";
|
|
79
|
+
const lines = [];
|
|
80
|
+
lines.push(`kojee-mcp doctor (openclaw) \u2014 verdict: ${report.verdict.toUpperCase()}`);
|
|
81
|
+
lines.push("");
|
|
82
|
+
lines.push(" Wake mode: native OpenClaw channel plugin (in-process; the gateway streams Tandem events).");
|
|
83
|
+
lines.push(" Plugin install is delegated to OpenClaw's own plugin manager (the wizard owns only the channel config).");
|
|
84
|
+
lines.push("");
|
|
85
|
+
for (const c of report.checks) {
|
|
86
|
+
lines.push(` ${mark(c.ok)} ${c.name}: ${c.detail}`);
|
|
87
|
+
}
|
|
88
|
+
lines.push("");
|
|
89
|
+
lines.push(`NOTE: live openclaw verification (plugin loaded, gateway streaming) is an owner step: \`${VERIFY_HINT}\`.`);
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
collectOpenclawDoctorReport,
|
|
94
|
+
formatOpenclawDoctorReport
|
|
95
|
+
};
|
|
@@ -90,6 +90,25 @@ declare class GatewayClient {
|
|
|
90
90
|
sendRpc(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<ToolCallResult>;
|
|
91
91
|
private executeWithRetries;
|
|
92
92
|
private sendHttpRequest;
|
|
93
|
+
/**
|
|
94
|
+
* Build the DPoP-authed request headers shared by every backend call: a fresh
|
|
95
|
+
* DPoP proof bound to (method, url) + the current nonce + the access token, the
|
|
96
|
+
* `Authorization: DPoP <token>` header, the stable `Mcp-Session-Id`, and the
|
|
97
|
+
* JSON content type. Factored out of sendHttpRequest so the REST helper below
|
|
98
|
+
* reuses the EXACT same auth construction (no auth-path divergence).
|
|
99
|
+
*/
|
|
100
|
+
private buildAuthHeaders;
|
|
101
|
+
/**
|
|
102
|
+
* Send an authed REST request (NOT JSON-RPC) to `<brokerUrl><path>`, reusing
|
|
103
|
+
* the SAME DPoP-proof + Authorization + Mcp-Session-Id header construction as
|
|
104
|
+
* the JSON-RPC path (buildAuthHeaders). Returns the RAW `Response` — callers
|
|
105
|
+
* own status handling; there is deliberately NO error-translation here, so a
|
|
106
|
+
* best-effort caller (presence) can fire-and-forget and swallow non-2xx.
|
|
107
|
+
*
|
|
108
|
+
* `body` is JSON-serialized when present (omitted otherwise). `signal` rides
|
|
109
|
+
* the real fetch option (per the JSON-RPC path's ROUND-3 convention).
|
|
110
|
+
*/
|
|
111
|
+
sendRest(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<Response>;
|
|
93
112
|
private trackNonce;
|
|
94
113
|
private tryParseErrorBody;
|
|
95
114
|
}
|
|
@@ -45,6 +45,7 @@ async function handleRequest(req, res, opts) {
|
|
|
45
45
|
if (req.method === "GET" && url.pathname === "/poll") {
|
|
46
46
|
const type = url.searchParams.get("type") ?? "";
|
|
47
47
|
const timeoutMs = Number.parseInt(url.searchParams.get("timeout_ms") ?? "0", 10);
|
|
48
|
+
fireLiveliness(opts.liveliness, type);
|
|
48
49
|
if (type === "user-prompt-submit") {
|
|
49
50
|
return respondWithEvents(res, opts);
|
|
50
51
|
}
|
|
@@ -131,6 +132,17 @@ function readBody(req, maxBytes) {
|
|
|
131
132
|
req.on("error", reject);
|
|
132
133
|
});
|
|
133
134
|
}
|
|
135
|
+
function fireLiveliness(liveliness, type) {
|
|
136
|
+
if (!liveliness) return;
|
|
137
|
+
try {
|
|
138
|
+
if (type === "user-prompt-submit") {
|
|
139
|
+
liveliness.onTurnStart();
|
|
140
|
+
} else if (type === "stop") {
|
|
141
|
+
liveliness.onTurnEnd();
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
}
|
|
134
146
|
function respondWithEvents(res, opts) {
|
|
135
147
|
const entries = opts.queue.takeForHook();
|
|
136
148
|
const events = entries.map((entry) => opts.adapter.formatTandemEvent(entry.event));
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
listTandemIds,
|
|
3
3
|
startProxy
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IRD26KZG.js";
|
|
5
5
|
import "./chunk-XXFVWP6H.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-34IRTWP6.js";
|
|
7
7
|
import "./chunk-JXMVZEQ7.js";
|
|
8
8
|
import "./chunk-NR4Y54OL.js";
|
|
9
9
|
import "./chunk-CH32ELFX.js";
|
package/dist/lib.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { KeyLike, JWK } from 'jose';
|
|
2
|
-
import { L as LoadedKeyPair, K as KeystoreData, G as GatewayClient, M as McpToolDefinition, T as ToolCallResult, P as ProxyConfig } from './gateway-client-
|
|
2
|
+
import { L as LoadedKeyPair, K as KeystoreData, G as GatewayClient, M as McpToolDefinition, T as ToolCallResult, P as ProxyConfig } from './gateway-client-CbM2OC_w.js';
|
|
3
3
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/lib.js
CHANGED
|
@@ -43,6 +43,40 @@ function formatChannelNotification(event) {
|
|
|
43
43
|
return claudeCodeAdapter.formatTandemEvent(event);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// src/delivery/liveliness.ts
|
|
47
|
+
function createPresenceProvider({
|
|
48
|
+
post,
|
|
49
|
+
debounceMs = 2e3
|
|
50
|
+
}) {
|
|
51
|
+
let idleTimer = null;
|
|
52
|
+
const clearIdleTimer = () => {
|
|
53
|
+
if (idleTimer !== null) {
|
|
54
|
+
clearTimeout(idleTimer);
|
|
55
|
+
idleTimer = null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const emit = (state) => {
|
|
59
|
+
void post(state).catch(() => {
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
onTurnStart() {
|
|
64
|
+
clearIdleTimer();
|
|
65
|
+
emit("working");
|
|
66
|
+
},
|
|
67
|
+
onTurnEnd() {
|
|
68
|
+
clearIdleTimer();
|
|
69
|
+
idleTimer = setTimeout(() => {
|
|
70
|
+
idleTimer = null;
|
|
71
|
+
emit("idle");
|
|
72
|
+
}, debounceMs);
|
|
73
|
+
},
|
|
74
|
+
stop() {
|
|
75
|
+
clearIdleTimer();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
46
80
|
// src/delivery/claude-code.ts
|
|
47
81
|
function createClaudeCodeDelivery() {
|
|
48
82
|
let eventLog = null;
|
|
@@ -52,11 +86,12 @@ function createClaudeCodeDelivery() {
|
|
|
52
86
|
let hookServer = null;
|
|
53
87
|
let cleanupDiscoveryFile = null;
|
|
54
88
|
let streamHandle = null;
|
|
89
|
+
let liveliness = null;
|
|
55
90
|
return {
|
|
56
91
|
name: "claude-code",
|
|
57
92
|
async start(ctx) {
|
|
58
93
|
const { EventQueue } = await import("./event-queue-5YVJFR3E.js");
|
|
59
|
-
const { startHookServer } = await import("./hook-server-
|
|
94
|
+
const { startHookServer } = await import("./hook-server-XK2NHLJV.js");
|
|
60
95
|
const {
|
|
61
96
|
writeDiscoveryByKey,
|
|
62
97
|
cleanupDiscoveryByKey,
|
|
@@ -117,6 +152,9 @@ function createClaudeCodeDelivery() {
|
|
|
117
152
|
err.message
|
|
118
153
|
);
|
|
119
154
|
}
|
|
155
|
+
liveliness = createPresenceProvider({
|
|
156
|
+
post: (state) => ctx.gateway.sendRest("POST", "/api/v2/presence", { state })
|
|
157
|
+
});
|
|
120
158
|
queue = new EventQueue();
|
|
121
159
|
const localQueue = queue;
|
|
122
160
|
let liveStream = null;
|
|
@@ -124,6 +162,7 @@ function createClaudeCodeDelivery() {
|
|
|
124
162
|
port: 0,
|
|
125
163
|
queue: localQueue,
|
|
126
164
|
adapter: ctx.adapter,
|
|
165
|
+
liveliness,
|
|
127
166
|
...controlToken !== null ? { controlToken, send: { gateway: ctx.gateway, authToken: controlToken } } : {},
|
|
128
167
|
getStreamState: () => liveStream ? liveStream.getState() : {
|
|
129
168
|
connected: false,
|
|
@@ -152,11 +191,15 @@ function createClaudeCodeDelivery() {
|
|
|
152
191
|
cleanupDiscoveryFile = () => cleanupDiscoveryByKey(discoveryKey);
|
|
153
192
|
const localCleanupDiscovery = cleanupDiscoveryFile;
|
|
154
193
|
const localHookServer = hookServer;
|
|
194
|
+
const localLiveliness = liveliness;
|
|
155
195
|
const exitCleanup = () => {
|
|
156
196
|
localCleanupDiscovery();
|
|
157
197
|
eventLog?.cleanup();
|
|
158
198
|
};
|
|
159
199
|
const teardown = [
|
|
200
|
+
() => {
|
|
201
|
+
localLiveliness.stop();
|
|
202
|
+
},
|
|
160
203
|
() => {
|
|
161
204
|
void webhookSink?.stop();
|
|
162
205
|
},
|
|
@@ -207,6 +250,7 @@ function createClaudeCodeDelivery() {
|
|
|
207
250
|
});
|
|
208
251
|
},
|
|
209
252
|
async stop() {
|
|
253
|
+
liveliness?.stop();
|
|
210
254
|
await webhookSink?.stop();
|
|
211
255
|
cleanupDiscoveryFile?.();
|
|
212
256
|
eventLog?.cleanup();
|
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
removeCodexConfig,
|
|
10
10
|
writeCodexConfig
|
|
11
11
|
} from "./chunk-65KRRDHP.js";
|
|
12
|
+
import {
|
|
13
|
+
CHANNEL_ID,
|
|
14
|
+
removeOpenclawChannel,
|
|
15
|
+
writeOpenclawChannelConfig
|
|
16
|
+
} from "./chunk-CLKCNV2A.js";
|
|
12
17
|
import {
|
|
13
18
|
kojeeHomeDir
|
|
14
19
|
} from "./chunk-SQL56SEB.js";
|
|
@@ -30,8 +35,8 @@ import {
|
|
|
30
35
|
|
|
31
36
|
// src/wizard/wizard.ts
|
|
32
37
|
import crypto2 from "crypto";
|
|
33
|
-
import
|
|
34
|
-
import
|
|
38
|
+
import fs4 from "fs";
|
|
39
|
+
import path5 from "path";
|
|
35
40
|
import { fileURLToPath } from "url";
|
|
36
41
|
|
|
37
42
|
// src/wizard/registry.ts
|
|
@@ -318,81 +323,6 @@ function installHermes(inp) {
|
|
|
318
323
|
};
|
|
319
324
|
}
|
|
320
325
|
|
|
321
|
-
// src/wizard/capabilities/openclaw-channel-config.ts
|
|
322
|
-
import fs4 from "fs";
|
|
323
|
-
import path5 from "path";
|
|
324
|
-
var CHANNEL_ID = "kojee-tandem";
|
|
325
|
-
function mergeOpenclawChannelConfig(existing, block) {
|
|
326
|
-
const prevChannels = existing.channels && typeof existing.channels === "object" ? existing.channels : {};
|
|
327
|
-
return {
|
|
328
|
-
...existing,
|
|
329
|
-
channels: {
|
|
330
|
-
...prevChannels,
|
|
331
|
-
[CHANNEL_ID]: { ...block }
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
function readOpenclawConfigState(configPath) {
|
|
336
|
-
let raw;
|
|
337
|
-
try {
|
|
338
|
-
raw = fs4.readFileSync(configPath, "utf8");
|
|
339
|
-
} catch {
|
|
340
|
-
return { cfg: {}, unparseable: false };
|
|
341
|
-
}
|
|
342
|
-
try {
|
|
343
|
-
const parsed = JSON.parse(raw);
|
|
344
|
-
return {
|
|
345
|
-
cfg: parsed && typeof parsed === "object" ? parsed : {},
|
|
346
|
-
unparseable: false
|
|
347
|
-
};
|
|
348
|
-
} catch {
|
|
349
|
-
return { cfg: {}, unparseable: true };
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
function atomicWrite(filePath, content, secret) {
|
|
353
|
-
fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
354
|
-
const tmp = `${filePath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
355
|
-
fs4.writeFileSync(tmp, content, { ...secret ? { mode: 384 } : {} });
|
|
356
|
-
if (secret) secureFile(tmp);
|
|
357
|
-
fs4.renameSync(tmp, filePath);
|
|
358
|
-
if (secret) secureFile(filePath);
|
|
359
|
-
}
|
|
360
|
-
function writeOpenclawChannelConfig(configPath, block, opts = {}) {
|
|
361
|
-
const { cfg, unparseable } = readOpenclawConfigState(configPath);
|
|
362
|
-
let backedUp;
|
|
363
|
-
if (unparseable) {
|
|
364
|
-
const stamp = opts.timestamp ?? corruptStamp();
|
|
365
|
-
backedUp = `${configPath}.corrupt-${stamp}`;
|
|
366
|
-
fs4.copyFileSync(configPath, backedUp);
|
|
367
|
-
}
|
|
368
|
-
const merged = mergeOpenclawChannelConfig(cfg, block);
|
|
369
|
-
atomicWrite(configPath, JSON.stringify(merged, null, 2) + "\n", Boolean(block.credential));
|
|
370
|
-
return backedUp ? { backedUp } : {};
|
|
371
|
-
}
|
|
372
|
-
function corruptStamp() {
|
|
373
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
374
|
-
}
|
|
375
|
-
function removeOpenclawChannel(configPath) {
|
|
376
|
-
let raw;
|
|
377
|
-
try {
|
|
378
|
-
raw = fs4.readFileSync(configPath, "utf8");
|
|
379
|
-
} catch {
|
|
380
|
-
return false;
|
|
381
|
-
}
|
|
382
|
-
let cfg;
|
|
383
|
-
try {
|
|
384
|
-
cfg = JSON.parse(raw);
|
|
385
|
-
} catch {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
const channels = cfg.channels && typeof cfg.channels === "object" ? cfg.channels : void 0;
|
|
389
|
-
if (!channels || !(CHANNEL_ID in channels)) return false;
|
|
390
|
-
const { [CHANNEL_ID]: _removed, ...rest } = channels;
|
|
391
|
-
const next = { ...cfg, channels: rest };
|
|
392
|
-
atomicWrite(configPath, JSON.stringify(next, null, 2) + "\n", false);
|
|
393
|
-
return true;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
326
|
// src/wizard/installers/openclaw.ts
|
|
397
327
|
function installOpenclaw(inp) {
|
|
398
328
|
const gatewayUrl = inp.url ? inp.url.replace(/\/+$/, "") : void 0;
|
|
@@ -719,7 +649,7 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
|
|
|
719
649
|
return lines;
|
|
720
650
|
}
|
|
721
651
|
function distDir() {
|
|
722
|
-
return
|
|
652
|
+
return path5.dirname(fileURLToPath(import.meta.url));
|
|
723
653
|
}
|
|
724
654
|
function resolveBinPath() {
|
|
725
655
|
const entry = process.argv[1];
|
|
@@ -764,7 +694,7 @@ function configureHermes(opts) {
|
|
|
764
694
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
765
695
|
}
|
|
766
696
|
recordRuntime(runtime);
|
|
767
|
-
const envFile =
|
|
697
|
+
const envFile = path5.join(home, ".kojee", "hermes.env");
|
|
768
698
|
lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
|
|
769
699
|
lines.push("");
|
|
770
700
|
lines.push(install.output);
|
|
@@ -780,15 +710,15 @@ function configureHermes(opts) {
|
|
|
780
710
|
return { runtime, output: lines.join("\n"), exitCode: 0 };
|
|
781
711
|
}
|
|
782
712
|
function openclawConfigPath(opts) {
|
|
783
|
-
return opts.openclawConfigPath ??
|
|
713
|
+
return opts.openclawConfigPath ?? path5.join(kojeeHomeDir(), ".openclaw", "config.json");
|
|
784
714
|
}
|
|
785
715
|
function resolveOpenclawPluginSourceDir() {
|
|
786
716
|
const candidates = [
|
|
787
|
-
|
|
788
|
-
|
|
717
|
+
path5.resolve(distDir(), "..", "..", "integrations", "openclaw-plugin"),
|
|
718
|
+
path5.resolve(distDir(), "..", "..", "..", "integrations", "openclaw-plugin")
|
|
789
719
|
];
|
|
790
720
|
for (const dir of candidates) {
|
|
791
|
-
if (
|
|
721
|
+
if (fs4.existsSync(path5.join(dir, "openclaw.plugin.json"))) return dir;
|
|
792
722
|
}
|
|
793
723
|
return void 0;
|
|
794
724
|
}
|
|
@@ -841,9 +771,9 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
841
771
|
} else {
|
|
842
772
|
lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
|
|
843
773
|
lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
|
|
844
|
-
const envPath =
|
|
774
|
+
const envPath = path5.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
|
|
845
775
|
try {
|
|
846
|
-
|
|
776
|
+
fs4.unlinkSync(envPath);
|
|
847
777
|
lines.push(` removed ${envPath}`);
|
|
848
778
|
} catch {
|
|
849
779
|
}
|