kojee-mcp 0.5.16 → 0.5.18

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.
@@ -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
+ };
@@ -8,25 +8,18 @@ import {
8
8
  import {
9
9
  AuthModule
10
10
  } from "./chunk-JXMVZEQ7.js";
11
- import {
12
- secureDir,
13
- secureFile
14
- } from "./chunk-BLEGIR35.js";
15
11
  import {
16
12
  createMcpServer,
17
13
  startMcpServer
18
- } from "./chunk-CM3EKMDD.js";
14
+ } from "./chunk-XIDCYUTK.js";
19
15
  import {
20
16
  findClaudeAncestorPid
21
17
  } from "./chunk-VHKPWUX7.js";
22
- import {
23
- parseTandemsConfig
24
- } from "./chunk-YKW54DKF.js";
25
18
 
26
19
  // src/index.ts
27
- import fs2 from "fs";
28
- import os2 from "os";
29
- import path2 from "path";
20
+ import fs from "fs";
21
+ import os from "os";
22
+ import path from "path";
30
23
 
31
24
  // src/tool-registry.ts
32
25
  var ToolRegistry = class {
@@ -150,57 +143,8 @@ var unknownAdapter = {
150
143
  }
151
144
  };
152
145
 
153
- // src/tandem/room-memory.ts
154
- import fs from "fs";
155
- import os from "os";
156
- import path from "path";
157
- function defaultKojeeDir() {
158
- return path.join(os.homedir(), ".kojee");
159
- }
160
- function seatedRoomsPath(key, dir = defaultKojeeDir()) {
161
- return path.join(dir, `seated-rooms-cc-${key}.json`);
162
- }
163
- function readSeatedRooms(key, dir = defaultKojeeDir()) {
164
- let raw;
165
- try {
166
- raw = fs.readFileSync(seatedRoomsPath(key, dir), "utf8");
167
- } catch {
168
- return [];
169
- }
170
- try {
171
- const parsed = JSON.parse(raw);
172
- return Array.isArray(parsed.rooms) ? parsed.rooms.filter((r) => typeof r === "string") : [];
173
- } catch {
174
- return [];
175
- }
176
- }
177
- function hasSeatedRoomsFile(key, dir = defaultKojeeDir()) {
178
- return fs.existsSync(seatedRoomsPath(key, dir));
179
- }
180
- function seedSeatedRooms(key, rooms, dir = defaultKojeeDir()) {
181
- writeSeatedRooms(key, [...new Set(rooms)], dir);
182
- }
183
- function writeSeatedRooms(key, rooms, dir) {
184
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
185
- secureDir(dir);
186
- const filePath = seatedRoomsPath(key, dir);
187
- const body = { schema: 1, rooms, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
188
- fs.writeFileSync(filePath, JSON.stringify(body, null, 2), { mode: 384 });
189
- secureFile(filePath);
190
- }
191
- function addSeatedRoom(key, tandemId, dir = defaultKojeeDir()) {
192
- const rooms = readSeatedRooms(key, dir);
193
- if (rooms.includes(tandemId)) return;
194
- writeSeatedRooms(key, [...rooms, tandemId], dir);
195
- }
196
- function removeSeatedRoom(key, tandemId, dir = defaultKojeeDir()) {
197
- const rooms = readSeatedRooms(key, dir);
198
- if (!rooms.includes(tandemId)) return;
199
- writeSeatedRooms(key, rooms.filter((r) => r !== tandemId), dir);
200
- }
201
-
202
146
  // src/index.ts
203
- var DEFAULT_KEYSTORE_PATH = path2.join(os2.homedir(), ".kojee", "keypair.json");
147
+ var DEFAULT_KEYSTORE_PATH = path.join(os.homedir(), ".kojee", "keypair.json");
204
148
  function isDPoPEnrollmentError(err) {
205
149
  const msg = String(err?.message ?? err ?? "").toLowerCase();
206
150
  if (msg.includes("invalid or expired") && msg.includes("token")) return false;
@@ -242,15 +186,9 @@ async function startProxy(config) {
242
186
  console.error(
243
187
  `[kojee-mcp] Ready \u2014 ${registry.toolCount} tools available from ${config.url}`
244
188
  );
245
- const roomMemory = {
246
- hasMemory: () => hasSeatedRoomsFile(instanceKey),
247
- read: () => readSeatedRooms(instanceKey),
248
- seed: (rooms) => seedSeatedRooms(instanceKey, rooms)
249
- };
250
- const recordRooms = parseTandemsConfig(process.env["KOJEE_TANDEMS"]).mode === "auto-local";
251
189
  if (instanceKey.startsWith("wd-")) {
252
190
  console.error(
253
- "[kojee-mcp] degraded per-window fidelity: no per-window session id \u2014 two concurrent windows in the same project dir share ONE seat" + (recordRooms ? " and one room-memory file" : "") + ". Set KOJEE_INSTANCE=<unique-per-window> to keep them distinct."
191
+ "[kojee-mcp] degraded per-window fidelity: no per-window session id \u2014 two concurrent windows in the same project dir share ONE seat. Set KOJEE_INSTANCE=<unique-per-window> to keep them distinct."
254
192
  );
255
193
  }
256
194
  let activeStreamHandle = null;
@@ -267,12 +205,8 @@ async function startProxy(config) {
267
205
  return true;
268
206
  }
269
207
  });
270
- const onTandemJoin = (tandemId) => {
208
+ const onTandemJoin = (_tandemId) => {
271
209
  joinReconnect.requestReconnect();
272
- if (recordRooms && tandemId) addSeatedRoom(instanceKey, tandemId);
273
- };
274
- const onTandemLeave = (tandemId) => {
275
- if (recordRooms && tandemId) removeSeatedRoom(instanceKey, tandemId);
276
210
  };
277
211
  const teardownSteps = [];
278
212
  let shuttingDown = false;
@@ -295,14 +229,6 @@ async function startProxy(config) {
295
229
  console.error(`[kojee-mcp] shutting down (${reason}), exiting`);
296
230
  process.exit(0);
297
231
  }
298
- const { ensureJoinTandems } = await import("./ensure-join-5Y5IJ7HN.js");
299
- await ensureJoinTandems({
300
- gateway,
301
- env: process.env["KOJEE_TANDEMS"],
302
- listTandems: () => listTandemIds(gateway),
303
- roomMemory,
304
- onJoined: () => joinReconnect.requestReconnect()
305
- });
306
232
  let tandemMembershipCount = -1;
307
233
  try {
308
234
  const bootIds = await listTandemIds(gateway);
@@ -312,7 +238,7 @@ async function startProxy(config) {
312
238
  }
313
239
  console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
314
240
  let server;
315
- const { selectDelivery } = await import("./registry-HPCRIWRF.js");
241
+ const { selectDelivery } = await import("./registry-LBWRSM5Z.js");
316
242
  const delivery = selectDelivery(adapter.runtime, {
317
243
  supportsChannels: adapter.supportsChannels
318
244
  });
@@ -327,7 +253,7 @@ async function startProxy(config) {
327
253
  ccPid,
328
254
  tandemMembershipCount,
329
255
  listTandemIds: () => listTandemIds(gateway),
330
- toolCallHooks: { onTandemJoin, onTandemLeave },
256
+ toolCallHooks: { onTandemJoin },
331
257
  onStreamReady: (handle) => {
332
258
  activeStreamHandle = handle;
333
259
  joinReconnect.notifyReady();
@@ -387,7 +313,7 @@ async function enrollAndDiscover(config, keystorePath, isRetry = false) {
387
313
  "[kojee-mcp] Auth failed, attempting recovery with fresh enrollment..."
388
314
  );
389
315
  try {
390
- if (fs2.existsSync(keystorePath)) fs2.unlinkSync(keystorePath);
316
+ if (fs.existsSync(keystorePath)) fs.unlinkSync(keystorePath);
391
317
  } catch (unlinkErr) {
392
318
  console.error("[kojee-mcp] Could not remove stale keystore:", unlinkErr);
393
319
  }
@@ -57,19 +57,11 @@ function tandemIdArg(args) {
57
57
  async function executeToolCall(registry, name, args, hooks) {
58
58
  const rawResult = await registry.callTool(name, args);
59
59
  const result = translateToolCallResult(rawResult);
60
- if (!result.isError) {
61
- if (name === "tandem_join") {
62
- try {
63
- hooks?.onTandemJoin?.(tandemIdArg(args));
64
- } catch (err) {
65
- console.error("[mcp] onTandemJoin hook failed:", err?.message ?? String(err));
66
- }
67
- } else if (name === "tandem_leave") {
68
- try {
69
- hooks?.onTandemLeave?.(tandemIdArg(args));
70
- } catch (err) {
71
- console.error("[mcp] onTandemLeave hook failed:", err?.message ?? String(err));
72
- }
60
+ if (!result.isError && name === "tandem_join") {
61
+ try {
62
+ hooks?.onTandemJoin?.(tandemIdArg(args));
63
+ } catch (err) {
64
+ console.error("[mcp] onTandemJoin hook failed:", err?.message ?? String(err));
73
65
  }
74
66
  }
75
67
  return result;
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-OGHDTFAX.js";
5
5
  import {
6
6
  startProxy
7
- } from "./chunk-IRD26KZG.js";
7
+ } from "./chunk-TN45ULEB.js";
8
8
  import "./chunk-XXFVWP6H.js";
9
9
  import {
10
10
  pairedConfigPath
@@ -19,11 +19,10 @@ import {
19
19
  import "./chunk-BLEGIR35.js";
20
20
  import {
21
21
  VERSION
22
- } from "./chunk-CM3EKMDD.js";
22
+ } from "./chunk-XIDCYUTK.js";
23
23
  import "./chunk-X672ZN7V.js";
24
24
  import "./chunk-2OLXXOT3.js";
25
25
  import "./chunk-VHKPWUX7.js";
26
- import "./chunk-YKW54DKF.js";
27
26
 
28
27
  // src/cli.ts
29
28
  import { Command } from "commander";
@@ -45,11 +44,11 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
45
44
  });
46
45
  program.command("hook").description("Run a kojee MCP hook script (called by Claude Code via ~/.claude/settings.json)").requiredOption("--type <type>", "Hook type: stop, user-prompt-submit, or codex-stop").action(async (opts) => {
47
46
  if (opts.type === "stop") {
48
- const { runStopHook } = await import("./stop-hook-CUVDKXP7.js");
47
+ const { runStopHook } = await import("./stop-hook-YAVJ3EJD.js");
49
48
  await runStopHook();
50
49
  process.exit(0);
51
50
  } else if (opts.type === "user-prompt-submit") {
52
- const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-PMBUPKUV.js");
51
+ const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-FQ43NIHC.js");
53
52
  await runUserPromptSubmitHook();
54
53
  process.exit(0);
55
54
  } else if (opts.type === "codex-stop") {
@@ -99,7 +98,7 @@ program.command("tail <path>").description("Stream a file's contents and follow
99
98
  }
100
99
  });
101
100
  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-FVTALRQD.js");
101
+ const { runDoctor } = await import("./doctor-DVPT2ZRH.js");
103
102
  const code = await runDoctor();
104
103
  process.exit(code);
105
104
  });
@@ -140,7 +139,7 @@ program.command("init").description(
140
139
  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
140
  process.exit(1);
142
141
  }
143
- const { runWizard } = await import("./wizard-3FDEWEYO.js");
142
+ const { runWizard } = await import("./wizard-Y3ULSO2C.js");
144
143
  const result = await runWizard({
145
144
  ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
146
145
  ...opts.uninstall ? { uninstall: true } : {},
@@ -1,7 +1,4 @@
1
1
  import "./chunk-XLKGPGZT.js";
2
- import {
3
- loadControlToken
4
- } from "./chunk-GI2CKKBL.js";
5
2
  import {
6
3
  monitorHeartbeatPath,
7
4
  statusLogPath
@@ -11,6 +8,9 @@ import {
11
8
  readSessionDiscoveryByKey,
12
9
  sessionDiscoveryDir
13
10
  } from "./chunk-DO42NPNR.js";
11
+ import {
12
+ loadControlToken
13
+ } from "./chunk-GI2CKKBL.js";
14
14
  import {
15
15
  loadPairedConfig
16
16
  } from "./chunk-YH27B6SW.js";
@@ -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
+ };
package/dist/index.d.ts CHANGED
@@ -5,11 +5,11 @@ import 'jose';
5
5
  * List the tandem_ids where THIS AGENT holds an active seat, via a
6
6
  * `tandem_list` tool call. The backend's tandem_list is PRINCIPAL-scoped:
7
7
  * rows include rooms where only SIBLING agents of the principal sit
8
- * (`my_membership: {is_member:false, principal_is_member:true}`). This is
9
- * the auto ensure-join feed, so object rows are filtered to
10
- * `my_membership.is_member === true` — FAIL CLOSED: a row missing the flag
11
- * is excluded, because joining a room the agent was never in is the harm
12
- * (live-reproduced 2026-06-10: 5 rows listed, only 2 agent seats).
8
+ * (`my_membership: {is_member:false, principal_is_member:true}`). Consumers are
9
+ * the resubscribe-on-connect touch set and the boot membership-count probe (NOT
10
+ * a join feed — auto-join was removed), so rows are filtered to
11
+ * `my_membership.is_member === true` — FAIL CLOSED: a row missing the flag is
12
+ * excluded, so we only ever touch rooms this agent actually sits in.
13
13
  * Returns the id array, or null when the list could not be determined (tool
14
14
  * error or unparseable result) — null is the "unknown" signal callers map to a
15
15
  * -1 membership count. MINOR 6: called fresh on every reconnect so the touch
package/dist/index.js CHANGED
@@ -1,18 +1,17 @@
1
1
  import {
2
2
  listTandemIds,
3
3
  startProxy
4
- } from "./chunk-IRD26KZG.js";
4
+ } from "./chunk-TN45ULEB.js";
5
5
  import "./chunk-XXFVWP6H.js";
6
6
  import "./chunk-34IRTWP6.js";
7
7
  import "./chunk-JXMVZEQ7.js";
8
8
  import "./chunk-NR4Y54OL.js";
9
9
  import "./chunk-CH32ELFX.js";
10
10
  import "./chunk-BLEGIR35.js";
11
- import "./chunk-CM3EKMDD.js";
11
+ import "./chunk-XIDCYUTK.js";
12
12
  import "./chunk-X672ZN7V.js";
13
13
  import "./chunk-2OLXXOT3.js";
14
14
  import "./chunk-VHKPWUX7.js";
15
- import "./chunk-YKW54DKF.js";
16
15
  export {
17
16
  listTandemIds,
18
17
  startProxy
package/dist/lib.d.ts CHANGED
@@ -519,19 +519,13 @@ interface ToolCallHooks {
519
519
  * Fired after a SUCCESSFUL tandem_join performed through this proxy by its
520
520
  * agent, with the joined tandem id (null if it couldn't be read from args).
521
521
  * The proxy wires this to the debounced stream-reconnect scheduler so the
522
- * event-stream's membership snapshot picks up the just-acquired seat without
523
- * a daemon restart, AND (#28) records the room in this instance's local
524
- * room-memory for session-faithful re-seat. A throwing hook never breaks the
525
- * tool reply.
522
+ * event-stream's membership snapshot picks up the just-acquired EXPLICIT seat
523
+ * without a daemon restart. A throwing hook never breaks the tool reply.
524
+ * (There is no leave hook: auto-join removal deleted the room-memory it fed,
525
+ * and a leave needs no proxy action — the stream stops hearing the room on the
526
+ * next reconnect.)
526
527
  */
527
528
  onTandemJoin?: (tandemId: string | null) => void;
528
- /**
529
- * Fired after a SUCCESSFUL tandem_leave through this proxy, with the left
530
- * tandem id (#28). The proxy drops the room from this instance's local
531
- * room-memory so a restart doesn't rejoin a room the agent deliberately
532
- * left. A throwing hook never breaks the tool reply.
533
- */
534
- onTandemLeave?: (tandemId: string | null) => void;
535
529
  }
536
530
 
537
531
  /**
@@ -567,7 +561,7 @@ interface ToolCallHooks {
567
561
  /**
568
562
  * Everything a {@link WakeDelivery.start} needs from the proxy to build its
569
563
  * sinks/surfaces and wire the SSE stream. Assembled once in `startProxy` after
570
- * auth + ensure-join, before the stream connects.
564
+ * auth, before the stream connects.
571
565
  */
572
566
  interface WakeDeliveryContext {
573
567
  /**
@@ -594,7 +588,7 @@ interface WakeDeliveryContext {
594
588
  tandemMembershipCount: number;
595
589
  /** tandem_id lister for the resubscribe-on-connect touch. */
596
590
  listTandemIds: () => Promise<string[] | null>;
597
- /** join/leave side-channel hooks (stream reconnect + room-memory record). */
591
+ /** join side-channel hook (stream reconnect on an explicit join). */
598
592
  toolCallHooks: ToolCallHooks;
599
593
  /** Called once the stream handle is armed, to flush a boot-race reconnect. */
600
594
  onStreamReady?: (handle: StreamLike) => void;
@@ -4,7 +4,7 @@ import "./chunk-LSUB6QMP.js";
4
4
  import {
5
5
  claudeCodeAdapter
6
6
  } from "./chunk-XXFVWP6H.js";
7
- import "./chunk-CM3EKMDD.js";
7
+ import "./chunk-XIDCYUTK.js";
8
8
  import "./chunk-X672ZN7V.js";
9
9
  import "./chunk-2OLXXOT3.js";
10
10
 
@@ -102,7 +102,7 @@ function createClaudeCodeDelivery() {
102
102
  const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
103
103
  const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
104
104
  const { startEventStream } = await import("./event-stream-XX5EZ6HN.js");
105
- const { createMcpServer } = await import("./server-LBVEDIXP.js");
105
+ const { createMcpServer } = await import("./server-LTFG4GMV.js");
106
106
  const { deriveDiscoveryKey } = await import("./ancestry-ONFBQEP5.js");
107
107
  sweepStaleDiscovery();
108
108
  sweepStaleEventLogs();
@@ -279,7 +279,7 @@ function createWebhookDelivery(name) {
279
279
  const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
280
280
  const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
281
281
  const { startEventStream } = await import("./event-stream-XX5EZ6HN.js");
282
- const { createMcpServer } = await import("./server-LBVEDIXP.js");
282
+ const { createMcpServer } = await import("./server-LTFG4GMV.js");
283
283
  sweepStaleEventLogs();
284
284
  eventLog = startEventLog({
285
285
  key: ctx.instanceKey,
@@ -3,7 +3,7 @@ import {
3
3
  createMcpServer,
4
4
  executeToolCall,
5
5
  startMcpServer
6
- } from "./chunk-CM3EKMDD.js";
6
+ } from "./chunk-XIDCYUTK.js";
7
7
  import "./chunk-X672ZN7V.js";
8
8
  import "./chunk-2OLXXOT3.js";
9
9
  export {
@@ -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 fs5 from "fs";
34
- import path6 from "path";
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 path6.dirname(fileURLToPath(import.meta.url));
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 = path6.join(home, ".kojee", "hermes.env");
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 ?? path6.join(kojeeHomeDir(), ".openclaw", "config.json");
713
+ return opts.openclawConfigPath ?? path5.join(kojeeHomeDir(), ".openclaw", "config.json");
784
714
  }
785
715
  function resolveOpenclawPluginSourceDir() {
786
716
  const candidates = [
787
- path6.resolve(distDir(), "..", "..", "integrations", "openclaw-plugin"),
788
- path6.resolve(distDir(), "..", "..", "..", "integrations", "openclaw-plugin")
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 (fs5.existsSync(path6.join(dir, "openclaw.plugin.json"))) return dir;
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 = path6.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
774
+ const envPath = path5.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
845
775
  try {
846
- fs5.unlinkSync(envPath);
776
+ fs4.unlinkSync(envPath);
847
777
  lines.push(` removed ${envPath}`);
848
778
  } catch {
849
779
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -1,126 +0,0 @@
1
- // src/tandem/ensure-join.ts
2
- var OBJECT_ID_RE = /^[0-9a-f]{24}$/i;
3
- var DEFAULT_PER_CALL_TIMEOUT_MS = 1e4;
4
- function parseTandemsConfig(raw) {
5
- const trimmed = (raw ?? "").trim();
6
- if (trimmed.length === 0) return { mode: "auto-local", ids: [], invalid: [] };
7
- if (trimmed.toLowerCase() === "none") return { mode: "disabled", ids: [], invalid: [] };
8
- if (trimmed.toLowerCase() === "auto-agent") return { mode: "auto-agent", ids: [], invalid: [] };
9
- const ids = [];
10
- const invalid = [];
11
- for (const entry of trimmed.split(/[\s,]+/)) {
12
- if (entry.length === 0) continue;
13
- (OBJECT_ID_RE.test(entry) ? ids : invalid).push(entry);
14
- }
15
- return { mode: "explicit", ids, invalid };
16
- }
17
- async function ensureJoinTandems(opts) {
18
- const log = opts.log ?? ((line) => console.error(line));
19
- const perCallTimeoutMs = opts.perCallTimeoutMs ?? DEFAULT_PER_CALL_TIMEOUT_MS;
20
- const config = parseTandemsConfig(opts.env);
21
- const result = { mode: config.mode, joined: [], already: [], failed: [] };
22
- if (config.mode === "disabled") {
23
- log("[ensure-join] disabled (KOJEE_TANDEMS=none)");
24
- return result;
25
- }
26
- for (const bad of config.invalid) {
27
- log(`[ensure-join] skipping invalid tandem id "${bad}" (not a 24-hex ObjectId)`);
28
- }
29
- const listAgentTandems = async () => {
30
- try {
31
- return opts.listTandems ? await opts.listTandems() : null;
32
- } catch (err) {
33
- log(`[ensure-join] tandem_list threw: ${err.message}`);
34
- return null;
35
- }
36
- };
37
- const warnListFailed = () => {
38
- log(
39
- "[ensure-join] tandem_list failed \u2014 cannot re-seat this session (set KOJEE_TANDEMS=<ids> to pin, or KOJEE_TANDEMS=none to silence)"
40
- );
41
- };
42
- let ids;
43
- if (config.mode === "explicit") {
44
- ids = config.ids;
45
- log(`[ensure-join] mode=explicit n=${ids.length} (KOJEE_TANDEMS)`);
46
- } else if (config.mode === "auto-agent") {
47
- const listed = await listAgentTandems();
48
- if (listed === null) {
49
- warnListFailed();
50
- return result;
51
- }
52
- ids = listed;
53
- log(`[ensure-join] mode=auto-agent n=${ids.length} (legacy \u2014 re-seating every room this agent holds a seat)`);
54
- } else {
55
- const mem = opts.roomMemory;
56
- if (mem && mem.hasMemory()) {
57
- ids = mem.read().filter((id) => OBJECT_ID_RE.test(id));
58
- log(`[ensure-join] mode=auto-local n=${ids.length} (rejoining this session's own rooms from local memory)`);
59
- } else {
60
- const listed = await listAgentTandems();
61
- if (listed === null) {
62
- warnListFailed();
63
- return result;
64
- }
65
- ids = listed;
66
- if (mem) {
67
- try {
68
- mem.seed(ids);
69
- log(`[ensure-join] mode=auto-local n=${ids.length} (first run \u2014 seeded local memory from membership)`);
70
- } catch (err) {
71
- log(`[ensure-join] mode=auto-local n=${ids.length} (first run \u2014 seed write FAILED: ${err.message}; joining without persisting)`);
72
- }
73
- } else {
74
- log(`[ensure-join] mode=auto-local n=${ids.length} (no room-memory port \u2014 falling back to agent-scoped list)`);
75
- }
76
- }
77
- }
78
- for (const id of ids) {
79
- const outcome = await joinOne(opts.gateway, id, perCallTimeoutMs);
80
- if (outcome.kind === "failed") {
81
- result.failed.push(id);
82
- log(`[ensure-join] ${id}: FAILED \u2014 ${outcome.detail} (continuing)`);
83
- continue;
84
- }
85
- if (outcome.kind === "already") {
86
- result.already.push(id);
87
- log(`[ensure-join] ${id}: already seated`);
88
- } else {
89
- result.joined.push(id);
90
- log(`[ensure-join] ${id}: joined fresh`);
91
- }
92
- try {
93
- opts.onJoined?.(id);
94
- } catch {
95
- }
96
- }
97
- return result;
98
- }
99
- async function joinOne(gateway, tandemId, perCallTimeoutMs) {
100
- const ac = new AbortController();
101
- const timer = setTimeout(() => ac.abort(), perCallTimeoutMs);
102
- try {
103
- const result = await gateway.sendRpc(
104
- "tools/call",
105
- { name: "tandem_join", arguments: { tandem_id: tandemId } },
106
- ac.signal
107
- );
108
- const text = (result.content ?? []).map((c) => typeof c?.text === "string" ? c.text : "").filter(Boolean).join("\n");
109
- if (result.isError) {
110
- return { kind: "failed", detail: text || "tandem_join returned an error with no text" };
111
- }
112
- if (/already[\s_-]?(a[\s_-]?)?(member|seated|joined)/i.test(text)) {
113
- return { kind: "already" };
114
- }
115
- return { kind: "joined" };
116
- } catch (err) {
117
- return { kind: "failed", detail: err?.message ?? String(err) };
118
- } finally {
119
- clearTimeout(timer);
120
- }
121
- }
122
-
123
- export {
124
- parseTandemsConfig,
125
- ensureJoinTandems
126
- };
@@ -1,8 +0,0 @@
1
- import {
2
- ensureJoinTandems,
3
- parseTandemsConfig
4
- } from "./chunk-YKW54DKF.js";
5
- export {
6
- ensureJoinTandems,
7
- parseTandemsConfig
8
- };
@@ -1,7 +1,4 @@
1
1
  import "./chunk-XLKGPGZT.js";
2
- import {
3
- controlTokenAuthHeaders
4
- } from "./chunk-GI2CKKBL.js";
5
2
  import {
6
3
  formatChannelEvents
7
4
  } from "./chunk-PHXO5P25.js";
@@ -15,6 +12,9 @@ import {
15
12
  import {
16
13
  readSessionDiscoveryByKey
17
14
  } from "./chunk-DO42NPNR.js";
15
+ import {
16
+ controlTokenAuthHeaders
17
+ } from "./chunk-GI2CKKBL.js";
18
18
  import "./chunk-BLEGIR35.js";
19
19
  import {
20
20
  buildMonitorNudge
@@ -1,6 +1,3 @@
1
- import {
2
- controlTokenAuthHeaders
3
- } from "./chunk-GI2CKKBL.js";
4
1
  import {
5
2
  formatChannelEvents
6
3
  } from "./chunk-PHXO5P25.js";
@@ -10,6 +7,9 @@ import {
10
7
  import {
11
8
  readSessionDiscoveryByKey
12
9
  } from "./chunk-DO42NPNR.js";
10
+ import {
11
+ controlTokenAuthHeaders
12
+ } from "./chunk-GI2CKKBL.js";
13
13
  import "./chunk-BLEGIR35.js";
14
14
  import {
15
15
  deriveDiscoveryKey,