kojee-mcp 0.5.12 → 0.5.13

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.
@@ -62,6 +62,7 @@ function translateHttpError(status, errorCode, trigger) {
62
62
  return null;
63
63
  }
64
64
  var TANDEM_ERROR_MESSAGES = {
65
+ [-32001]: () => "You aren't an active member of that Tandem (the seat may have been reaped). Run tandem_join to reactivate it, then retry.",
65
66
  [-32003]: () => "This Tandem is hardened to owner-only membership; you can't join.",
66
67
  [-32004]: () => "You aren't a member of that Tandem. Use tandem_join(join_link) first.",
67
68
  [-32006]: (data) => {
@@ -2,8 +2,9 @@ import {
2
2
  claudeCodeAdapter
3
3
  } from "./chunk-XXFVWP6H.js";
4
4
  import {
5
- GatewayClient
6
- } from "./chunk-HSR3GXCL.js";
5
+ GatewayClient,
6
+ applyStableSessionId
7
+ } from "./chunk-IMOEZ4NJ.js";
7
8
  import {
8
9
  AuthModule
9
10
  } from "./chunk-JXMVZEQ7.js";
@@ -14,7 +15,7 @@ import {
14
15
  import {
15
16
  createMcpServer,
16
17
  startMcpServer
17
- } from "./chunk-WLMPCX7T.js";
18
+ } from "./chunk-CM3EKMDD.js";
18
19
  import {
19
20
  findClaudeAncestorPid
20
21
  } from "./chunk-VHKPWUX7.js";
@@ -23,7 +24,7 @@ import {
23
24
  } from "./chunk-YKW54DKF.js";
24
25
 
25
26
  // src/index.ts
26
- import fs3 from "fs";
27
+ import fs2 from "fs";
27
28
  import os2 from "os";
28
29
  import path2 from "path";
29
30
 
@@ -149,67 +150,8 @@ var unknownAdapter = {
149
150
  }
150
151
  };
151
152
 
152
- // src/runtime/cc-session-id.ts
153
- import fs from "fs";
154
- import { execFileSync } from "child_process";
155
- import { createHash } from "crypto";
156
- var CC_SESSION_ENV = "CLAUDE_CODE_SESSION_ID";
157
- function extractCcSessionId(raw) {
158
- if (raw.includes("\0")) {
159
- for (const entry of raw.split("\0")) {
160
- if (entry.startsWith(`${CC_SESSION_ENV}=`)) {
161
- const v = entry.slice(CC_SESSION_ENV.length + 1);
162
- return v.length > 0 ? v : null;
163
- }
164
- }
165
- return null;
166
- }
167
- const re = new RegExp(`(?:^|\\s)${CC_SESSION_ENV}=([^\\s]+)`, "g");
168
- let last = null;
169
- let m;
170
- while ((m = re.exec(raw)) !== null) last = m[1];
171
- return last;
172
- }
173
- function defaultReadProcessEnvRaw(pid, platform) {
174
- try {
175
- if (platform === "linux") {
176
- return fs.readFileSync(`/proc/${pid}/environ`, "utf8");
177
- }
178
- if (platform === "darwin") {
179
- return execFileSync("ps", ["eww", "-p", String(pid), "-o", "command="], {
180
- encoding: "utf8",
181
- timeout: 2e3
182
- });
183
- }
184
- } catch {
185
- return null;
186
- }
187
- return null;
188
- }
189
- function sanitizeKey(value) {
190
- return value.replace(/[^A-Za-z0-9_-]/g, "");
191
- }
192
- function resolveInstanceKey(deps = {}) {
193
- const env = deps.env ?? process.env;
194
- const platform = deps.platform ?? process.platform;
195
- const ccPid = deps.ccPid ?? null;
196
- if (ccPid !== null) {
197
- const reader = deps.readProcessEnvRaw ?? defaultReadProcessEnvRaw;
198
- const raw = reader(ccPid, platform);
199
- if (raw) {
200
- const sid = sanitizeKey(extractCcSessionId(raw) ?? "");
201
- if (sid) return sid;
202
- }
203
- }
204
- const explicit = sanitizeKey((env.KOJEE_INSTANCE ?? "").trim());
205
- if (explicit) return `inst-${explicit}`;
206
- const base = deps.projectDir ?? env.CLAUDE_PROJECT_DIR ?? deps.cwd ?? process.cwd() ?? "";
207
- const hash = createHash("sha256").update(base).digest("hex").slice(0, 12);
208
- return `wd-${hash}`;
209
- }
210
-
211
153
  // src/tandem/room-memory.ts
212
- import fs2 from "fs";
154
+ import fs from "fs";
213
155
  import os from "os";
214
156
  import path from "path";
215
157
  function defaultKojeeDir() {
@@ -221,7 +163,7 @@ function seatedRoomsPath(key, dir = defaultKojeeDir()) {
221
163
  function readSeatedRooms(key, dir = defaultKojeeDir()) {
222
164
  let raw;
223
165
  try {
224
- raw = fs2.readFileSync(seatedRoomsPath(key, dir), "utf8");
166
+ raw = fs.readFileSync(seatedRoomsPath(key, dir), "utf8");
225
167
  } catch {
226
168
  return [];
227
169
  }
@@ -233,17 +175,17 @@ function readSeatedRooms(key, dir = defaultKojeeDir()) {
233
175
  }
234
176
  }
235
177
  function hasSeatedRoomsFile(key, dir = defaultKojeeDir()) {
236
- return fs2.existsSync(seatedRoomsPath(key, dir));
178
+ return fs.existsSync(seatedRoomsPath(key, dir));
237
179
  }
238
180
  function seedSeatedRooms(key, rooms, dir = defaultKojeeDir()) {
239
181
  writeSeatedRooms(key, [...new Set(rooms)], dir);
240
182
  }
241
183
  function writeSeatedRooms(key, rooms, dir) {
242
- fs2.mkdirSync(dir, { recursive: true, mode: 448 });
184
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
243
185
  secureDir(dir);
244
186
  const filePath = seatedRoomsPath(key, dir);
245
187
  const body = { schema: 1, rooms, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
246
- fs2.writeFileSync(filePath, JSON.stringify(body, null, 2), { mode: 384 });
188
+ fs.writeFileSync(filePath, JSON.stringify(body, null, 2), { mode: 384 });
247
189
  secureFile(filePath);
248
190
  }
249
191
  function addSeatedRoom(key, tandemId, dir = defaultKojeeDir()) {
@@ -294,21 +236,21 @@ async function startProxy(config) {
294
236
  const keystorePath = config.keystorePath || DEFAULT_KEYSTORE_PATH;
295
237
  const adapter = await selectAdapter();
296
238
  console.error(`[kojee-mcp] Starting proxy for ${config.url} (runtime=${adapter.runtime})`);
239
+ const ccPid = await findClaudeAncestorPid();
240
+ const { instanceKey } = await applyStableSessionId(config.token, { ccPid });
297
241
  const { registry, gateway } = await enrollAndDiscover(config, keystorePath);
298
242
  console.error(
299
243
  `[kojee-mcp] Ready \u2014 ${registry.toolCount} tools available from ${config.url}`
300
244
  );
301
- const ccPid = await findClaudeAncestorPid();
302
- const instanceKey = resolveInstanceKey({ ccPid });
303
245
  const roomMemory = {
304
246
  hasMemory: () => hasSeatedRoomsFile(instanceKey),
305
247
  read: () => readSeatedRooms(instanceKey),
306
248
  seed: (rooms) => seedSeatedRooms(instanceKey, rooms)
307
249
  };
308
250
  const recordRooms = parseTandemsConfig(process.env["KOJEE_TANDEMS"]).mode === "auto-local";
309
- if (recordRooms && instanceKey.startsWith("wd-")) {
251
+ if (instanceKey.startsWith("wd-")) {
310
252
  console.error(
311
- "[kojee-mcp] #28: no Claude Code session id available \u2014 room-memory keyed on project-dir hash; concurrent windows in the same dir will share it. Set KOJEE_INSTANCE=<unique-per-window> to keep them session-faithful."
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."
312
254
  );
313
255
  }
314
256
  let activeStreamHandle = null;
@@ -370,7 +312,7 @@ async function startProxy(config) {
370
312
  }
371
313
  console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
372
314
  let server;
373
- const { selectDelivery } = await import("./registry-A3VT6VJD.js");
315
+ const { selectDelivery } = await import("./registry-TGALQP6M.js");
374
316
  const delivery = selectDelivery(adapter.runtime, {
375
317
  supportsChannels: adapter.supportsChannels
376
318
  });
@@ -445,7 +387,7 @@ async function enrollAndDiscover(config, keystorePath, isRetry = false) {
445
387
  "[kojee-mcp] Auth failed, attempting recovery with fresh enrollment..."
446
388
  );
447
389
  try {
448
- if (fs3.existsSync(keystorePath)) fs3.unlinkSync(keystorePath);
390
+ if (fs2.existsSync(keystorePath)) fs2.unlinkSync(keystorePath);
449
391
  } catch (unlinkErr) {
450
392
  console.error("[kojee-mcp] Could not remove stale keystore:", unlinkErr);
451
393
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
- MCP_SESSION_ID,
3
- createDPoPProof
4
- } from "./chunk-2MIISF2W.js";
2
+ createDPoPProof,
3
+ getSessionId
4
+ } from "./chunk-NR4Y54OL.js";
5
5
 
6
6
  // src/tandem/event-stream.ts
7
7
  var STALE_FLOOR_MS = 9e4;
@@ -180,7 +180,7 @@ async function openStream(opts, controller, sinceValue) {
180
180
  headers: {
181
181
  Authorization: `DPoP ${opts.token}`,
182
182
  DPoP: proof,
183
- "Mcp-Session-Id": MCP_SESSION_ID,
183
+ "Mcp-Session-Id": getSessionId(),
184
184
  Accept: "text/event-stream"
185
185
  },
186
186
  signal: controller.signal
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-X672ZN7V.js";
6
6
  import {
7
7
  translateToolCallResult
8
- } from "./chunk-LDZXU3DW.js";
8
+ } from "./chunk-2OLXXOT3.js";
9
9
 
10
10
  // src/server.ts
11
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  translateToolCallResult
3
- } from "./chunk-LDZXU3DW.js";
3
+ } from "./chunk-2OLXXOT3.js";
4
4
 
5
5
  // src/tandem/send.ts
6
6
  var SEND_KINDS = ["message", "status"];
@@ -52,7 +52,7 @@ function classifySendFailure(text) {
52
52
  if (/token is invalid or expired/i.test(text) || /authentication failed/i.test(text) || /re-?authoriz/i.test(text)) {
53
53
  return sendFailure("gateway_auth", text);
54
54
  }
55
- if (/aren'?t a member|not a member/i.test(text)) {
55
+ if (/aren'?t a member|not[ _]a[ _]member|aren'?t an active member/i.test(text)) {
56
56
  return sendFailure("not_member", text);
57
57
  }
58
58
  if (/rate limit/i.test(text)) {
@@ -110,6 +110,25 @@ async function executeSend(gateway, request) {
110
110
  text
111
111
  };
112
112
  }
113
+ async function executeJoin(gateway, tandemId) {
114
+ let result;
115
+ try {
116
+ result = await gateway.sendRpc("tools/call", {
117
+ name: "tandem_join",
118
+ arguments: { tandem_id: tandemId }
119
+ });
120
+ } catch (err) {
121
+ return classifySendFailure(
122
+ `tandem_join failed: ${err?.message ?? String(err)}`
123
+ );
124
+ }
125
+ const translated = translateToolCallResult(result);
126
+ const text = (translated.content ?? []).map((c) => typeof c?.text === "string" ? c.text : "").filter(Boolean).join("\n");
127
+ if (translated.isError) {
128
+ return classifySendFailure(text || "tandem_join returned an error with no text");
129
+ }
130
+ return { ok: true, tandem_id: tandemId, message_id: null, cursor: null, text };
131
+ }
113
132
  var HTTP_STATUS_BY_CODE = {
114
133
  bad_request: 400,
115
134
  unauthorized: 401,
@@ -137,5 +156,6 @@ export {
137
156
  sendFailure,
138
157
  parseSendRequest,
139
158
  executeSend,
159
+ executeJoin,
140
160
  httpStatusForEnvelope
141
161
  };
@@ -1,12 +1,17 @@
1
1
  import {
2
- MCP_SESSION_ID,
3
- createDPoPProof
4
- } from "./chunk-2MIISF2W.js";
2
+ createDPoPProof,
3
+ deriveStableSessionId,
4
+ getSessionId,
5
+ setStableSessionId
6
+ } from "./chunk-NR4Y54OL.js";
5
7
  import {
6
8
  translateHttpError,
7
9
  translateJsonRpcError,
8
10
  translateNetworkError
9
- } from "./chunk-LDZXU3DW.js";
11
+ } from "./chunk-2OLXXOT3.js";
12
+ import {
13
+ findClaudeAncestorPid
14
+ } from "./chunk-VHKPWUX7.js";
10
15
 
11
16
  // src/gateway-client.ts
12
17
  import crypto from "crypto";
@@ -129,7 +134,7 @@ var GatewayClient = class {
129
134
  "Content-Type": "application/json",
130
135
  Authorization: `DPoP ${this.token}`,
131
136
  DPoP: proof,
132
- "Mcp-Session-Id": MCP_SESSION_ID
137
+ "Mcp-Session-Id": getSessionId()
133
138
  },
134
139
  body: JSON.stringify(rpcRequest),
135
140
  // ROUND-3 MAJOR A: the caller's AbortSignal rides HERE (a real fetch
@@ -153,6 +158,78 @@ var GatewayClient = class {
153
158
  }
154
159
  };
155
160
 
161
+ // src/runtime/cc-session-id.ts
162
+ import fs from "fs";
163
+ import { execFileSync } from "child_process";
164
+ import { createHash } from "crypto";
165
+ var CC_SESSION_ENV = "CLAUDE_CODE_SESSION_ID";
166
+ function extractCcSessionId(raw) {
167
+ if (raw.includes("\0")) {
168
+ for (const entry of raw.split("\0")) {
169
+ if (entry.startsWith(`${CC_SESSION_ENV}=`)) {
170
+ const v = entry.slice(CC_SESSION_ENV.length + 1);
171
+ return v.length > 0 ? v : null;
172
+ }
173
+ }
174
+ return null;
175
+ }
176
+ const re = new RegExp(`(?:^|\\s)${CC_SESSION_ENV}=([^\\s]+)`, "g");
177
+ let last = null;
178
+ let m;
179
+ while ((m = re.exec(raw)) !== null) last = m[1];
180
+ return last;
181
+ }
182
+ function defaultReadProcessEnvRaw(pid, platform) {
183
+ try {
184
+ if (platform === "linux") {
185
+ return fs.readFileSync(`/proc/${pid}/environ`, "utf8");
186
+ }
187
+ if (platform === "darwin") {
188
+ return execFileSync("ps", ["eww", "-p", String(pid), "-o", "command="], {
189
+ encoding: "utf8",
190
+ timeout: 2e3
191
+ });
192
+ }
193
+ } catch {
194
+ return null;
195
+ }
196
+ return null;
197
+ }
198
+ function sanitizeKey(value) {
199
+ return value.replace(/[^A-Za-z0-9_-]/g, "");
200
+ }
201
+ function resolveInstanceKey(deps = {}) {
202
+ const env = deps.env ?? process.env;
203
+ const platform = deps.platform ?? process.platform;
204
+ const ccPid = deps.ccPid ?? null;
205
+ if (ccPid !== null) {
206
+ const reader = deps.readProcessEnvRaw ?? defaultReadProcessEnvRaw;
207
+ const raw = reader(ccPid, platform);
208
+ if (raw) {
209
+ const sid = sanitizeKey(extractCcSessionId(raw) ?? "");
210
+ if (sid) return sid;
211
+ }
212
+ }
213
+ const explicit = sanitizeKey((env.KOJEE_INSTANCE ?? "").trim());
214
+ if (explicit) return `inst-${explicit}`;
215
+ const base = deps.projectDir ?? env.CLAUDE_PROJECT_DIR ?? deps.cwd ?? process.cwd() ?? "";
216
+ const hash = createHash("sha256").update(base).digest("hex").slice(0, 12);
217
+ return `wd-${hash}`;
218
+ }
219
+
220
+ // src/runtime/stable-session.ts
221
+ async function applyStableSessionId(token, deps = {}) {
222
+ let instanceKey = deps.instanceKey;
223
+ if (instanceKey === void 0) {
224
+ const ccPid = deps.ccPid !== void 0 ? deps.ccPid : await findClaudeAncestorPid();
225
+ instanceKey = resolveInstanceKey({ ccPid });
226
+ }
227
+ const sessionId = deriveStableSessionId(token, instanceKey);
228
+ setStableSessionId(sessionId);
229
+ return { instanceKey, sessionId };
230
+ }
231
+
156
232
  export {
157
- GatewayClient
233
+ GatewayClient,
234
+ applyStableSessionId
158
235
  };
@@ -26,10 +26,23 @@ function computeAth(accessToken) {
26
26
  }
27
27
 
28
28
  // src/tandem/session-id.ts
29
+ import crypto2 from "crypto";
29
30
  import { ulid } from "ulidx";
30
31
  var MCP_SESSION_ID = ulid();
32
+ function deriveStableSessionId(token, instanceKey) {
33
+ return crypto2.createHash("sha256").update(`${token}:${instanceKey}`).digest("hex").slice(0, 32);
34
+ }
35
+ var _stableSessionId = null;
36
+ function setStableSessionId(id) {
37
+ _stableSessionId = id;
38
+ }
39
+ function getSessionId() {
40
+ return _stableSessionId ?? MCP_SESSION_ID;
41
+ }
31
42
 
32
43
  export {
33
44
  createDPoPProof,
34
- MCP_SESSION_ID
45
+ deriveStableSessionId,
46
+ setStableSessionId,
47
+ getSessionId
35
48
  };
package/dist/cli.js CHANGED
@@ -4,14 +4,14 @@ import {
4
4
  } from "./chunk-OGHDTFAX.js";
5
5
  import {
6
6
  startProxy
7
- } from "./chunk-FQZCENSG.js";
7
+ } from "./chunk-3H3TL34J.js";
8
8
  import "./chunk-XXFVWP6H.js";
9
9
  import {
10
10
  pairedConfigPath
11
11
  } from "./chunk-YH27B6SW.js";
12
- import "./chunk-HSR3GXCL.js";
12
+ import "./chunk-IMOEZ4NJ.js";
13
13
  import "./chunk-JXMVZEQ7.js";
14
- import "./chunk-2MIISF2W.js";
14
+ import "./chunk-NR4Y54OL.js";
15
15
  import {
16
16
  defaultPairedKeystorePath,
17
17
  deriveKeystorePath
@@ -19,9 +19,9 @@ import {
19
19
  import "./chunk-BLEGIR35.js";
20
20
  import {
21
21
  VERSION
22
- } from "./chunk-WLMPCX7T.js";
22
+ } from "./chunk-CM3EKMDD.js";
23
23
  import "./chunk-X672ZN7V.js";
24
- import "./chunk-LDZXU3DW.js";
24
+ import "./chunk-2OLXXOT3.js";
25
25
  import "./chunk-VHKPWUX7.js";
26
26
  import "./chunk-YKW54DKF.js";
27
27
 
@@ -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-NZP5XE7T.js");
82
+ const { runSendCli } = await import("./send-cli-RH7D4JDP.js");
83
83
  const { exitCode, envelope } = await runSendCli({
84
84
  tandemId,
85
85
  body: opts.body,
@@ -90,7 +90,7 @@ program.command("send <tandem_id>").description(
90
90
  process.exit(exitCode);
91
91
  });
92
92
  program.command("tail <path>").description("Stream a file's contents and follow appends (portable replacement for `tail -F`)").action(async (filePath) => {
93
- const { runTail } = await import("./tail-stream-VZ462ZON.js");
93
+ const { runTail } = await import("./tail-stream-5DAVRQYK.js");
94
94
  try {
95
95
  await runTail(filePath);
96
96
  } catch (err) {
@@ -6,8 +6,8 @@ import {
6
6
  sanitizeDisplayname,
7
7
  serializeCursorMap,
8
8
  startEventStream
9
- } from "./chunk-5XP2UOFK.js";
10
- import "./chunk-2MIISF2W.js";
9
+ } from "./chunk-74XFVX6Z.js";
10
+ import "./chunk-NR4Y54OL.js";
11
11
  export {
12
12
  UNDICI_DEFAULT_BODY_TIMEOUT_MS,
13
13
  createAdaptiveWatchdog,
@@ -3,8 +3,8 @@ import {
3
3
  httpStatusForEnvelope,
4
4
  parseSendRequest,
5
5
  sendFailure
6
- } from "./chunk-HIZ4NDWN.js";
7
- import "./chunk-LDZXU3DW.js";
6
+ } from "./chunk-DXJ6QLSJ.js";
7
+ import "./chunk-2OLXXOT3.js";
8
8
 
9
9
  // src/tandem/hook-server.ts
10
10
  import crypto from "crypto";
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  listTandemIds,
3
3
  startProxy
4
- } from "./chunk-FQZCENSG.js";
4
+ } from "./chunk-3H3TL34J.js";
5
5
  import "./chunk-XXFVWP6H.js";
6
- import "./chunk-HSR3GXCL.js";
6
+ import "./chunk-IMOEZ4NJ.js";
7
7
  import "./chunk-JXMVZEQ7.js";
8
- import "./chunk-2MIISF2W.js";
8
+ import "./chunk-NR4Y54OL.js";
9
9
  import "./chunk-CH32ELFX.js";
10
10
  import "./chunk-BLEGIR35.js";
11
- import "./chunk-WLMPCX7T.js";
11
+ import "./chunk-CM3EKMDD.js";
12
12
  import "./chunk-X672ZN7V.js";
13
- import "./chunk-LDZXU3DW.js";
13
+ import "./chunk-2OLXXOT3.js";
14
14
  import "./chunk-VHKPWUX7.js";
15
15
  import "./chunk-YKW54DKF.js";
16
16
  export {
package/dist/lib.d.ts CHANGED
@@ -438,6 +438,49 @@ interface ResubscribeOptions {
438
438
  */
439
439
  declare function resubscribeMemberships(opts: ResubscribeOptions): Promise<number>;
440
440
 
441
+ interface StableSessionDeps {
442
+ /**
443
+ * The Claude Code ancestor PID, when the caller already has it (the daemon).
444
+ * Pass `null` to SKIP the ancestry read (non-CC contexts, tests) — resolution
445
+ * then falls to KOJEE_INSTANCE → working-dir hash. Omit to resolve ancestry.
446
+ */
447
+ ccPid?: number | null;
448
+ /**
449
+ * An explicit instanceKey — bypasses ancestry AND resolveInstanceKey entirely.
450
+ * Used where the window identity is known out-of-band (an embedder's account
451
+ * key, tests).
452
+ */
453
+ instanceKey?: string;
454
+ }
455
+ /**
456
+ * §7 — install the STABLE Mcp-Session-Id (D1) for THIS process, the one DRY way
457
+ * every outbound construction site does it: the daemon (index.ts), the `send`
458
+ * sub-command, and embedding plugins (OpenClaw). Same (token, instanceKey) →
459
+ * same id → the backend converges on ONE seat per (credential, window) across
460
+ * restarts (no phantom seats; reaper retired).
461
+ *
462
+ * instanceKey precedence: an explicit `deps.instanceKey` wins; otherwise it's
463
+ * resolved by {@link resolveInstanceKey} from `deps.ccPid` (or a fresh ancestry
464
+ * read when `ccPid` is omitted) → CC session id, else KOJEE_INSTANCE, else a
465
+ * working-dir hash.
466
+ *
467
+ * Returns the resolved instanceKey + the installed sessionId so callers that
468
+ * also need the key (room-memory, discovery) don't resolve it twice.
469
+ */
470
+ declare function applyStableSessionId(token: string, deps?: StableSessionDeps): Promise<{
471
+ instanceKey: string;
472
+ sessionId: string;
473
+ }>;
474
+
475
+ /**
476
+ * D1 — the STABLE Mcp-Session-Id: deterministic per (token, instanceKey),
477
+ * one-way + opaque. Same inputs → same id across proxy restarts, so the backend
478
+ * resolves the SAME Session + seat (no phantom seats, reaper retired). Computed
479
+ * at proxy startup where the gateway token + instanceKey (resolveInstanceKey)
480
+ * are known, in place of the random ulid() above.
481
+ */
482
+ declare function deriveStableSessionId(token: string, instanceKey: string): string;
483
+
441
484
  /**
442
485
  * Flat tool registry — fetches all Kojee tools with full schemas on startup
443
486
  * and registers them directly with the MCP server. No discovery indirection,
@@ -604,4 +647,4 @@ interface WakeDelivery {
604
647
  health?(): DeliveryHealth;
605
648
  }
606
649
 
607
- export { AuthModule, type DeliveryHealth, type EventLog, type EventStreamOptions, GatewayClient, type HarnessAdapter, type PairedConfig, ProxyConfig, type Runtime, type StartedDelivery, type StreamHandle, type StreamLike, type StreamState, type TandemEvent, ToolCallResult, type WakeDelivery, type WakeDeliveryContext, type WebhookSink, createDPoPProof, defaultPairedKeystorePath, deriveKeystorePath, generateES256KeyPair, loadKeystore, loadPairedConfig, normalizeBackendEvent, pairedConfigPath, resubscribeMemberships, sanitizeDisplayname, saveKeystore, startEventStream };
650
+ export { AuthModule, type DeliveryHealth, type EventLog, type EventStreamOptions, GatewayClient, type HarnessAdapter, type PairedConfig, ProxyConfig, type Runtime, type StableSessionDeps, type StartedDelivery, type StreamHandle, type StreamLike, type StreamState, type TandemEvent, ToolCallResult, type WakeDelivery, type WakeDeliveryContext, type WebhookSink, applyStableSessionId, createDPoPProof, defaultPairedKeystorePath, deriveKeystorePath, deriveStableSessionId, generateES256KeyPair, loadKeystore, loadPairedConfig, normalizeBackendEvent, pairedConfigPath, resubscribeMemberships, sanitizeDisplayname, saveKeystore, startEventStream };
package/dist/lib.js CHANGED
@@ -5,20 +5,22 @@ import {
5
5
  normalizeBackendEvent,
6
6
  sanitizeDisplayname,
7
7
  startEventStream
8
- } from "./chunk-5XP2UOFK.js";
8
+ } from "./chunk-74XFVX6Z.js";
9
9
  import {
10
10
  loadPairedConfig,
11
11
  pairedConfigPath
12
12
  } from "./chunk-YH27B6SW.js";
13
13
  import {
14
- GatewayClient
15
- } from "./chunk-HSR3GXCL.js";
14
+ GatewayClient,
15
+ applyStableSessionId
16
+ } from "./chunk-IMOEZ4NJ.js";
16
17
  import {
17
18
  AuthModule
18
19
  } from "./chunk-JXMVZEQ7.js";
19
20
  import {
20
- createDPoPProof
21
- } from "./chunk-2MIISF2W.js";
21
+ createDPoPProof,
22
+ deriveStableSessionId
23
+ } from "./chunk-NR4Y54OL.js";
22
24
  import {
23
25
  defaultPairedKeystorePath,
24
26
  deriveKeystorePath,
@@ -27,13 +29,16 @@ import {
27
29
  saveKeystore
28
30
  } from "./chunk-CH32ELFX.js";
29
31
  import "./chunk-BLEGIR35.js";
30
- import "./chunk-LDZXU3DW.js";
32
+ import "./chunk-2OLXXOT3.js";
33
+ import "./chunk-VHKPWUX7.js";
31
34
  export {
32
35
  AuthModule,
33
36
  GatewayClient,
37
+ applyStableSessionId,
34
38
  createDPoPProof,
35
39
  defaultPairedKeystorePath,
36
40
  deriveKeystorePath,
41
+ deriveStableSessionId,
37
42
  generateES256KeyPair,
38
43
  loadKeystore,
39
44
  loadPairedConfig,
@@ -4,9 +4,9 @@ import "./chunk-LSUB6QMP.js";
4
4
  import {
5
5
  claudeCodeAdapter
6
6
  } from "./chunk-XXFVWP6H.js";
7
- import "./chunk-WLMPCX7T.js";
7
+ import "./chunk-CM3EKMDD.js";
8
8
  import "./chunk-X672ZN7V.js";
9
- import "./chunk-LDZXU3DW.js";
9
+ import "./chunk-2OLXXOT3.js";
10
10
 
11
11
  // src/delivery/lib/fanout.ts
12
12
  async function deliverEvent(event, sinks) {
@@ -56,7 +56,7 @@ function createClaudeCodeDelivery() {
56
56
  name: "claude-code",
57
57
  async start(ctx) {
58
58
  const { EventQueue } = await import("./event-queue-5YVJFR3E.js");
59
- const { startHookServer } = await import("./hook-server-37E2LUKJ.js");
59
+ const { startHookServer } = await import("./hook-server-T2Z444OV.js");
60
60
  const {
61
61
  writeDiscoveryByKey,
62
62
  cleanupDiscoveryByKey,
@@ -66,8 +66,8 @@ function createClaudeCodeDelivery() {
66
66
  const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
67
67
  const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
68
68
  const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
69
- const { startEventStream } = await import("./event-stream-FOT7MJZH.js");
70
- const { createMcpServer } = await import("./server-77QRWKJM.js");
69
+ const { startEventStream } = await import("./event-stream-XX5EZ6HN.js");
70
+ const { createMcpServer } = await import("./server-LBVEDIXP.js");
71
71
  const { deriveDiscoveryKey } = await import("./ancestry-ONFBQEP5.js");
72
72
  sweepStaleDiscovery();
73
73
  sweepStaleEventLogs();
@@ -234,8 +234,8 @@ function createWebhookDelivery(name) {
234
234
  const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
235
235
  const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
236
236
  const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
237
- const { startEventStream } = await import("./event-stream-FOT7MJZH.js");
238
- const { createMcpServer } = await import("./server-77QRWKJM.js");
237
+ const { startEventStream } = await import("./event-stream-XX5EZ6HN.js");
238
+ const { createMcpServer } = await import("./server-LBVEDIXP.js");
239
239
  sweepStaleEventLogs();
240
240
  eventLog = startEventLog({
241
241
  key: ctx.instanceKey,
@@ -1,25 +1,28 @@
1
1
  import {
2
+ executeJoin,
2
3
  executeSend,
3
4
  parseSendRequest,
4
5
  sendFailure
5
- } from "./chunk-HIZ4NDWN.js";
6
+ } from "./chunk-DXJ6QLSJ.js";
6
7
  import {
7
8
  loadPairedConfig
8
9
  } from "./chunk-YH27B6SW.js";
9
10
  import {
10
- GatewayClient
11
- } from "./chunk-HSR3GXCL.js";
12
- import "./chunk-2MIISF2W.js";
11
+ GatewayClient,
12
+ applyStableSessionId
13
+ } from "./chunk-IMOEZ4NJ.js";
14
+ import "./chunk-NR4Y54OL.js";
13
15
  import {
14
16
  loadKeystore
15
17
  } from "./chunk-CH32ELFX.js";
16
18
  import "./chunk-BLEGIR35.js";
17
- import "./chunk-LDZXU3DW.js";
19
+ import "./chunk-2OLXXOT3.js";
20
+ import "./chunk-VHKPWUX7.js";
18
21
 
19
22
  // src/tandem/send-cli.ts
20
23
  import os from "os";
21
24
  import path from "path";
22
- async function createPairedGateway(kojeeDir) {
25
+ async function createPairedGateway(kojeeDir, stableDeps = {}) {
23
26
  const configPath = path.join(kojeeDir, "config.json");
24
27
  const paired = loadPairedConfig(configPath);
25
28
  if (!paired) {
@@ -28,6 +31,7 @@ async function createPairedGateway(kojeeDir) {
28
31
  `no paired config at ${configPath} \u2014 run \`kojee-mcp pair <code> --url <broker>\` first`
29
32
  );
30
33
  }
34
+ await applyStableSessionId(paired.token, stableDeps);
31
35
  const brokerUrl = paired.broker_url.replace(/\/+$/, "");
32
36
  const keystorePath = path.join(kojeeDir, "keypair.json");
33
37
  const keystore = await loadKeystore(keystorePath, brokerUrl).catch(() => null);
@@ -65,6 +69,10 @@ async function runSendCli(args, deps = {}) {
65
69
  if (!resolution.ok) {
66
70
  return { exitCode: 1, envelope: resolution };
67
71
  }
72
+ const joined = await executeJoin(resolution.gateway, parsed.request.tandem_id);
73
+ if (!joined.ok && (joined.error === "not_member" || joined.error === "gateway_auth")) {
74
+ return { exitCode: 1, envelope: joined };
75
+ }
68
76
  const envelope = await executeSend(resolution.gateway, parsed.request);
69
77
  return { exitCode: envelope.ok ? 0 : 1, envelope };
70
78
  }
@@ -3,9 +3,9 @@ import {
3
3
  createMcpServer,
4
4
  executeToolCall,
5
5
  startMcpServer
6
- } from "./chunk-WLMPCX7T.js";
6
+ } from "./chunk-CM3EKMDD.js";
7
7
  import "./chunk-X672ZN7V.js";
8
- import "./chunk-LDZXU3DW.js";
8
+ import "./chunk-2OLXXOT3.js";
9
9
  export {
10
10
  buildChannelInstructions,
11
11
  createMcpServer,
@@ -7,8 +7,8 @@ import {
7
7
  import "./chunk-DO42NPNR.js";
8
8
  import {
9
9
  createAdaptiveWatchdog
10
- } from "./chunk-5XP2UOFK.js";
11
- import "./chunk-2MIISF2W.js";
10
+ } from "./chunk-74XFVX6Z.js";
11
+ import "./chunk-NR4Y54OL.js";
12
12
  import "./chunk-BLEGIR35.js";
13
13
 
14
14
  // src/tail-stream.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.5.12",
3
+ "version": "0.5.13",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {