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.
- package/dist/chunk-CLKCNV2A.js +100 -0
- package/dist/{chunk-IRD26KZG.js → chunk-TN45ULEB.js} +10 -84
- package/dist/{chunk-CM3EKMDD.js → chunk-XIDCYUTK.js} +5 -13
- package/dist/cli.js +6 -7
- package/dist/{doctor-FVTALRQD.js → doctor-DVPT2ZRH.js} +9 -3
- package/dist/doctor-openclaw-SS2TMQOX.js +95 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -3
- package/dist/lib.d.ts +7 -13
- package/dist/{registry-HPCRIWRF.js → registry-LBWRSM5Z.js} +3 -3
- package/dist/{server-LBVEDIXP.js → server-LTFG4GMV.js} +1 -1
- package/dist/{wizard-3FDEWEYO.js → wizard-Y3ULSO2C.js} +15 -85
- package/package.json +1 -1
- package/dist/chunk-YKW54DKF.js +0 -126
- package/dist/ensure-join-5Y5IJ7HN.js +0 -8
- package/dist/{stop-hook-CUVDKXP7.js → stop-hook-YAVJ3EJD.js} +3 -3
- package/dist/{user-prompt-submit-hook-PMBUPKUV.js → user-prompt-submit-hook-FQ43NIHC.js} +3 -3
|
@@ -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-
|
|
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
|
|
28
|
-
import
|
|
29
|
-
import
|
|
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 =
|
|
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
|
|
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 = (
|
|
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-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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}`).
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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-
|
|
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-
|
|
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
|
|
523
|
-
* a daemon restart
|
|
524
|
-
*
|
|
525
|
-
*
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
282
|
+
const { createMcpServer } = await import("./server-LTFG4GMV.js");
|
|
283
283
|
sweepStaleEventLogs();
|
|
284
284
|
eventLog = startEventLog({
|
|
285
285
|
key: ctx.instanceKey,
|
|
@@ -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
|
}
|
package/package.json
CHANGED
package/dist/chunk-YKW54DKF.js
DELETED
|
@@ -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,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,
|