kojee-mcp 0.5.18 → 0.6.0

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.
Files changed (27) hide show
  1. package/dist/{chunk-XIDCYUTK.js → chunk-ALZX3SGI.js} +2 -2
  2. package/dist/{chunk-TN45ULEB.js → chunk-BYV7CWNY.js} +6 -6
  3. package/dist/{chunk-CO73VGWM.js → chunk-DURRQDYI.js} +1 -1
  4. package/dist/{chunk-35XBRG3V.js → chunk-KFDZWNQO.js} +1 -1
  5. package/dist/{chunk-34IRTWP6.js → chunk-KH5YEJIM.js} +13 -55
  6. package/dist/{chunk-XXFVWP6H.js → chunk-N35X22G7.js} +1 -1
  7. package/dist/{chunk-DXJ6QLSJ.js → chunk-PKOBR7ZN.js} +1 -1
  8. package/dist/{chunk-2OLXXOT3.js → chunk-PPTKGWFF.js} +69 -3
  9. package/dist/{chunk-X672ZN7V.js → chunk-Q7T3WAST.js} +1 -1
  10. package/dist/{chunk-74XFVX6Z.js → chunk-XN3IC4B5.js} +17 -5
  11. package/dist/cli.js +71 -76
  12. package/dist/{codex-stop-hook-VY7DOMAG.js → codex-stop-hook-RWQSZTST.js} +2 -2
  13. package/dist/{doctor-DVPT2ZRH.js → doctor-WVX3WCYX.js} +3 -3
  14. package/dist/{doctor-codex-PA3WO6LR.js → doctor-codex-Z5M4SFBG.js} +1 -1
  15. package/dist/{event-log-VZD7NKYX.js → event-log-HOBO3ECB.js} +1 -1
  16. package/dist/{event-stream-XX5EZ6HN.js → event-stream-SYBUJ5EH.js} +1 -1
  17. package/dist/{hook-server-XK2NHLJV.js → hook-server-CWBH6PFL.js} +2 -2
  18. package/dist/index.js +6 -6
  19. package/dist/lib.d.ts +15 -11
  20. package/dist/lib.js +3 -4
  21. package/dist/{registry-LBWRSM5Z.js → registry-YLOFZDI2.js} +12 -12
  22. package/dist/{send-cli-T6RPZZQ4.js → send-cli-IYJF32PE.js} +3 -4
  23. package/dist/{server-LTFG4GMV.js → server-R24AVNTI.js} +3 -3
  24. package/dist/{wizard-Y3ULSO2C.js → setup-handler-7HOFAD7C.js} +115 -7
  25. package/dist/{stop-hook-YAVJ3EJD.js → stop-hook-TCPJFGFF.js} +2 -2
  26. package/dist/{tail-stream-5DAVRQYK.js → tail-stream-4IMXI44S.js} +24 -3
  27. package/package.json +2 -1
@@ -2,10 +2,10 @@ import {
2
2
  buildCatchUpNote,
3
3
  buildMonitorSpawn,
4
4
  buildReplyRecipe
5
- } from "./chunk-X672ZN7V.js";
5
+ } from "./chunk-Q7T3WAST.js";
6
6
  import {
7
7
  translateToolCallResult
8
- } from "./chunk-2OLXXOT3.js";
8
+ } from "./chunk-PPTKGWFF.js";
9
9
 
10
10
  // src/server.ts
11
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  claudeCodeAdapter
3
- } from "./chunk-XXFVWP6H.js";
3
+ } from "./chunk-N35X22G7.js";
4
4
  import {
5
5
  GatewayClient,
6
6
  applyStableSessionId
7
- } from "./chunk-34IRTWP6.js";
7
+ } from "./chunk-KH5YEJIM.js";
8
8
  import {
9
9
  AuthModule
10
10
  } from "./chunk-JXMVZEQ7.js";
11
11
  import {
12
12
  createMcpServer,
13
13
  startMcpServer
14
- } from "./chunk-XIDCYUTK.js";
14
+ } from "./chunk-ALZX3SGI.js";
15
15
  import {
16
16
  findClaudeAncestorPid
17
17
  } from "./chunk-VHKPWUX7.js";
@@ -186,9 +186,9 @@ async function startProxy(config) {
186
186
  console.error(
187
187
  `[kojee-mcp] Ready \u2014 ${registry.toolCount} tools available from ${config.url}`
188
188
  );
189
- if (instanceKey.startsWith("wd-")) {
189
+ if (instanceKey.startsWith("mint-")) {
190
190
  console.error(
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."
191
+ "[kojee-mcp] no stable session identity \u2014 minted a fresh per-launch seat (re-seats on restart). Set KOJEE_INSTANCE=<stable-per-session-value> for a restart-stable seat (required for daemons like Hermes)."
192
192
  );
193
193
  }
194
194
  let activeStreamHandle = null;
@@ -238,7 +238,7 @@ async function startProxy(config) {
238
238
  }
239
239
  console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
240
240
  let server;
241
- const { selectDelivery } = await import("./registry-LBWRSM5Z.js");
241
+ const { selectDelivery } = await import("./registry-YLOFZDI2.js");
242
242
  const delivery = selectDelivery(adapter.runtime, {
243
243
  supportsChannels: adapter.supportsChannels
244
244
  });
@@ -35,7 +35,7 @@ function startEventLog(opts) {
35
35
  const statusMaxBytes = opts.statusMaxBytes ?? DEFAULT_STATUS_MAX_BYTES;
36
36
  fs.mkdirSync(dir, { recursive: true });
37
37
  let filePath = path.join(dir, `kojee-events-${opts.key}.log`);
38
- if (opts.key.startsWith("wd-") && livePathOwnedBySibling(filePath)) {
38
+ if (livePathOwnedBySibling(filePath)) {
39
39
  filePath = path.join(dir, `kojee-events-${opts.key}-${process.pid}.log`);
40
40
  }
41
41
  const statusPath = statusLogPath(filePath);
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-LSUB6QMP.js";
4
4
  import {
5
5
  buildCodexWakeReason
6
- } from "./chunk-X672ZN7V.js";
6
+ } from "./chunk-Q7T3WAST.js";
7
7
 
8
8
  // src/hooks/codex-stop-hook.ts
9
9
  import fs from "fs";
@@ -8,10 +8,7 @@ import {
8
8
  translateHttpError,
9
9
  translateJsonRpcError,
10
10
  translateNetworkError
11
- } from "./chunk-2OLXXOT3.js";
12
- import {
13
- findClaudeAncestorPid
14
- } from "./chunk-VHKPWUX7.js";
11
+ } from "./chunk-PPTKGWFF.js";
15
12
 
16
13
  // src/gateway-client.ts
17
14
  import crypto from "crypto";
@@ -107,8 +104,11 @@ var GatewayClient = class {
107
104
  const body = await this.tryParseErrorBody(response);
108
105
  const translated = translateHttpError(response.status, body?.error);
109
106
  if (translated) return translated;
107
+ const detail = body?.error_description ?? body?.detail ?? body?.error ?? "<no detail>";
110
108
  return {
111
- content: [{ type: "text", text: `Gateway error: ${response.status}` }],
109
+ content: [
110
+ { type: "text", text: `Gateway error ${response.status}: ${detail}` }
111
+ ],
112
112
  isError: true
113
113
  };
114
114
  }
@@ -190,70 +190,28 @@ var GatewayClient = class {
190
190
  };
191
191
 
192
192
  // src/runtime/cc-session-id.ts
193
- import fs from "fs";
194
- import { execFileSync } from "child_process";
195
- import { createHash } from "crypto";
196
- var CC_SESSION_ENV = "CLAUDE_CODE_SESSION_ID";
197
- function extractCcSessionId(raw) {
198
- if (raw.includes("\0")) {
199
- for (const entry of raw.split("\0")) {
200
- if (entry.startsWith(`${CC_SESSION_ENV}=`)) {
201
- const v = entry.slice(CC_SESSION_ENV.length + 1);
202
- return v.length > 0 ? v : null;
203
- }
204
- }
205
- return null;
206
- }
207
- const re = new RegExp(`(?:^|\\s)${CC_SESSION_ENV}=([^\\s]+)`, "g");
208
- let last = null;
209
- let m;
210
- while ((m = re.exec(raw)) !== null) last = m[1];
211
- return last;
212
- }
213
- function defaultReadProcessEnvRaw(pid, platform) {
214
- try {
215
- if (platform === "linux") {
216
- return fs.readFileSync(`/proc/${pid}/environ`, "utf8");
217
- }
218
- if (platform === "darwin") {
219
- return execFileSync("ps", ["eww", "-p", String(pid), "-o", "command="], {
220
- encoding: "utf8",
221
- timeout: 2e3
222
- });
223
- }
224
- } catch {
225
- return null;
226
- }
227
- return null;
228
- }
193
+ import { randomUUID } from "crypto";
194
+ var SESSION_ID_ENV_VARS = ["CLAUDE_CODE_SESSION_ID"];
229
195
  function sanitizeKey(value) {
230
196
  return value.replace(/[^A-Za-z0-9_-]/g, "");
231
197
  }
232
198
  function resolveInstanceKey(deps = {}) {
233
199
  const env = deps.env ?? process.env;
234
- const platform = deps.platform ?? process.platform;
235
- const ccPid = deps.ccPid ?? null;
236
- if (ccPid !== null) {
237
- const reader = deps.readProcessEnvRaw ?? defaultReadProcessEnvRaw;
238
- const raw = reader(ccPid, platform);
239
- if (raw) {
240
- const sid = sanitizeKey(extractCcSessionId(raw) ?? "");
241
- if (sid) return sid;
242
- }
200
+ for (const name of SESSION_ID_ENV_VARS) {
201
+ const sid = sanitizeKey((env[name] ?? "").trim());
202
+ if (sid) return sid;
243
203
  }
244
204
  const explicit = sanitizeKey((env.KOJEE_INSTANCE ?? "").trim());
245
205
  if (explicit) return `inst-${explicit}`;
246
- const base = deps.projectDir ?? env.CLAUDE_PROJECT_DIR ?? deps.cwd ?? process.cwd() ?? "";
247
- const hash = createHash("sha256").update(base).digest("hex").slice(0, 12);
248
- return `wd-${hash}`;
206
+ const mint = deps.randomUUID ?? randomUUID;
207
+ return `mint-${mint()}`;
249
208
  }
250
209
 
251
210
  // src/runtime/stable-session.ts
252
211
  async function applyStableSessionId(token, deps = {}) {
253
212
  let instanceKey = deps.instanceKey;
254
213
  if (instanceKey === void 0) {
255
- const ccPid = deps.ccPid !== void 0 ? deps.ccPid : await findClaudeAncestorPid();
256
- instanceKey = resolveInstanceKey({ ccPid });
214
+ instanceKey = resolveInstanceKey({ env: deps.env });
257
215
  }
258
216
  const sessionId = deriveStableSessionId(token, instanceKey);
259
217
  setStableSessionId(sessionId);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildReplyRecipe
3
- } from "./chunk-X672ZN7V.js";
3
+ } from "./chunk-Q7T3WAST.js";
4
4
 
5
5
  // src/adapters/claude-code.ts
6
6
  function computeSeverity(event) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  translateToolCallResult
3
- } from "./chunk-2OLXXOT3.js";
3
+ } from "./chunk-PPTKGWFF.js";
4
4
 
5
5
  // src/tandem/send.ts
6
6
  var SEND_KINDS = ["message", "status"];
@@ -33,7 +33,7 @@ function translateHttpError(status, errorCode, trigger) {
33
33
  }
34
34
  if (errorCode === "invalid_dpop_proof") {
35
35
  return makeError(
36
- "Authentication failed. The proxy will attempt to re-enroll. If this persists, regenerate your gateway token."
36
+ "Authentication failed (DPoP key rejected). Restart the proxy so it re-enrolls its key. If this persists, regenerate your gateway token."
37
37
  );
38
38
  }
39
39
  if (errorCode === "key_enrollment_required") {
@@ -61,8 +61,71 @@ function translateHttpError(status, errorCode, trigger) {
61
61
  }
62
62
  return null;
63
63
  }
64
+ function translateTandemNotActive(reason) {
65
+ switch (reason) {
66
+ // --- genuine inactive / reaped seat: re-joining is the right fix ---
67
+ case "not_a_member":
68
+ case "not_active_member":
69
+ return "You aren't an active member of that Tandem (the seat may have been reaped). Run tandem_join to reactivate it, then retry.";
70
+ // --- authentication: pairing problem, tandem_join won't help ---
71
+ case "unauthenticated":
72
+ return "Your proxy session isn't authenticated/paired with Kojee, so this call was rejected. Re-pair the proxy \u2014 tandem_join won't fix this.";
73
+ // --- existence / visibility ---
74
+ case "tandem_not_found":
75
+ return "That Tandem doesn't exist or isn't visible to you.";
76
+ // --- power / scope: you lack the required role ---
77
+ case "owner_only":
78
+ return "This action requires owner power in this Tandem, which you don't have.";
79
+ case "admin_only":
80
+ return "This action requires admin power in this Tandem, which you don't have.";
81
+ // --- capacity ---
82
+ case "member_cap_reached":
83
+ return "This Tandem is full (member cap reached); rejoining won't help \u2014 ask an admin to make room.";
84
+ // --- message-scoped ---
85
+ case "message_not_found":
86
+ return "That message doesn't exist in this Tandem (wrong id, or it was already redacted).";
87
+ case "redact_forbidden":
88
+ return "You can't redact that message \u2014 you're neither its author nor an admin of this Tandem.";
89
+ // --- targeting ---
90
+ case "target_principal_not_found":
91
+ return "The target principal isn't a member of this Tandem.";
92
+ // --- invitations ---
93
+ case "invitation_not_found":
94
+ return "That invitation doesn't exist, or it was already revoked or used.";
95
+ case "invite_required":
96
+ return "This Tandem requires an invite to join; ask an owner or admin to invite you.";
97
+ case "invite_invalid":
98
+ return "That invite token is invalid.";
99
+ case "invite_expired":
100
+ return "That invite has expired; request a fresh one.";
101
+ case "invite_exhausted":
102
+ return "That invite has no remaining uses; request a fresh one.";
103
+ case "invite_wrong_principal":
104
+ return "That invite was issued to a different principal and can't be used by you.";
105
+ // --- join requests / knocks ---
106
+ case "join_request_not_found":
107
+ return "That join request doesn't exist or was already decided.";
108
+ case "join_request_not_pending":
109
+ return "That join request was already decided (it's no longer pending).";
110
+ case "already_member":
111
+ return "You're already a member of that Tandem.";
112
+ case "already_requested":
113
+ return "You already have a pending join request for that Tandem.";
114
+ case "knock_required":
115
+ return "This Tandem requires a knock; request to join and wait for an admin to approve.";
116
+ case "knock_cooldown":
117
+ return "You're knocking too frequently; wait a bit before requesting to join again.";
118
+ // --- direct (1:1) Tandems ---
119
+ case "direct_tandem_immutable_membership":
120
+ case "direct_immutable":
121
+ return "This is a direct (1:1) Tandem; its membership can't be changed.";
122
+ // --- unknown token: surface the real reason verbatim, never the reaped string ---
123
+ default:
124
+ return reason ? `Tandem request refused: ${reason}.` : "Tandem request refused.";
125
+ }
126
+ }
64
127
  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.",
128
+ [-32001]: (_data, message) => translateTandemNotActive((message ?? "").trim()),
66
129
  [-32003]: () => "This Tandem is hardened to owner-only membership; you can't join.",
67
130
  [-32004]: () => "You aren't a member of that Tandem. Use tandem_join(join_link) first.",
68
131
  [-32006]: (data) => {
@@ -91,7 +154,10 @@ function translateJsonRpcError(error) {
91
154
  content: [
92
155
  {
93
156
  type: "text",
94
- text: tandemMessage(error.data)
157
+ text: tandemMessage(
158
+ error.data,
159
+ msg
160
+ )
95
161
  }
96
162
  ],
97
163
  isError: true
@@ -13,7 +13,7 @@ function buildReplyRecipe(event) {
13
13
  return `reply directly with tandem_send(tandem_id, ${SEND_BODY_PARAM}, reply_to=<msg>); fetch context only if needed via tandem_messages(tandem_id, since=cursor-1)`;
14
14
  }
15
15
  function buildCatchUpNote() {
16
- return "If the event line `cursor=<n>` ever jumps (a gap) or the log resets (the proxy caps and truncates the messages log in place on overflow \u2014 a `status=rotated` line is recorded in the status sibling), you may have missed messages: catch up with tandem_messages(tandem_id, since=<last cursor you saw>).";
16
+ return "Cursors are allocated per-Tandem, and the event-log interleaves every subscribed room into one stream \u2014 so track the last `cursor=<n>` you saw keyed by the line's `tandem=<id>` field, and only treat a jump WITHIN the same tandem_id as a gap (a jump between lines from different rooms is normal interleaving, not a missed message). If the cursor jumps within one tandem, or the log resets (the proxy caps and truncates the messages log in place on overflow \u2014 a `status=rotated` line is recorded in the status sibling), you may have missed messages: catch up with tandem_messages(tandem_id, since=<last cursor you saw for THAT tandem>).";
17
17
  }
18
18
  function buildMonitorNudge(logPath) {
19
19
  return `[kojee] Tandem events are being logged but no Monitor is reading them \u2014 you may be missing wake notifications. Spawn the watcher once: ${buildMonitorSpawn(logPath)}. Then ${buildReplyRecipe()}.`;
@@ -4,6 +4,7 @@ import {
4
4
  } from "./chunk-NR4Y54OL.js";
5
5
 
6
6
  // src/tandem/event-stream.ts
7
+ import { Agent } from "undici";
7
8
  var STALE_FLOOR_MS = 9e4;
8
9
  var STALE_INTERVAL_MULTIPLIER = 3;
9
10
  var STALE_CHECK_INTERVAL_MS = 5e3;
@@ -71,6 +72,10 @@ function createBackoffController(options = {}) {
71
72
  var LOG_HEARTBEAT_INTERVAL_MS = 6e4;
72
73
  async function startEventStream(opts) {
73
74
  let stopped = false;
75
+ const streamDispatcher = new Agent({
76
+ bodyTimeout: UNDICI_DEFAULT_BODY_TIMEOUT_MS,
77
+ headersTimeout: UNDICI_DEFAULT_BODY_TIMEOUT_MS
78
+ });
74
79
  const cursors = /* @__PURE__ */ new Map();
75
80
  let currentController = new AbortController();
76
81
  let connectGeneration = 0;
@@ -95,6 +100,7 @@ async function startEventStream(opts) {
95
100
  currentController,
96
101
  isReconnect,
97
102
  state,
103
+ streamDispatcher,
98
104
  (tandemId, cursor) => {
99
105
  const prev = cursors.get(tandemId);
100
106
  if (prev === void 0 || cursor > prev) cursors.set(tandemId, cursor);
@@ -125,6 +131,8 @@ async function startEventStream(opts) {
125
131
  stopped = true;
126
132
  state.connected = false;
127
133
  currentController.abort();
134
+ void streamDispatcher.close().catch(() => {
135
+ });
128
136
  });
129
137
  handle.reconnect = () => {
130
138
  if (stopped) return;
@@ -144,12 +152,12 @@ async function startEventStream(opts) {
144
152
  });
145
153
  return handle;
146
154
  }
147
- async function connectAndConsume(opts, cursors, controller, isReconnect, state, onCursor, onOpen) {
155
+ async function connectAndConsume(opts, cursors, controller, isReconnect, state, dispatcher, onCursor, onOpen) {
148
156
  const mapSince = cursors.size > 0 ? serializeCursorMap(cursors) : null;
149
- let res = await openStream(opts, controller, mapSince);
157
+ let res = await openStream(opts, controller, mapSince, dispatcher);
150
158
  if (res.status === 400 && mapSince !== null) {
151
159
  console.error("[event-stream] per-room ?since rejected (400) \u2014 falling back to bare cursor");
152
- res = await openStream(opts, controller, String(Math.min(...cursors.values())));
160
+ res = await openStream(opts, controller, String(Math.min(...cursors.values())), dispatcher);
153
161
  }
154
162
  if (!res.ok) throw new Error(`SSE connect failed: ${res.status}`);
155
163
  if (!res.body) throw new Error("SSE response has no body");
@@ -163,7 +171,7 @@ async function connectAndConsume(opts, cursors, controller, isReconnect, state,
163
171
  await consumeSse(res.body, opts, controller, state, onCursor);
164
172
  return true;
165
173
  }
166
- async function openStream(opts, controller, sinceValue) {
174
+ async function openStream(opts, controller, sinceValue, dispatcher) {
167
175
  const params = new URLSearchParams();
168
176
  if (sinceValue !== null) params.set("since", sinceValue);
169
177
  const url = `${opts.brokerUrl}/api/v2/tandems/stream${params.toString() ? "?" + params.toString() : ""}`;
@@ -183,7 +191,11 @@ async function openStream(opts, controller, sinceValue) {
183
191
  "Mcp-Session-Id": getSessionId(),
184
192
  Accept: "text/event-stream"
185
193
  },
186
- signal: controller.signal
194
+ signal: controller.signal,
195
+ // Isolate this long-lived stream onto its own connection pool so it can
196
+ // never block tool-call POSTs on the shared global pool. The dispatcher is
197
+ // owned by startEventStream (per-stream lifecycle) and closed on teardown.
198
+ dispatcher
187
199
  });
188
200
  }
189
201
  async function consumeSse(body, opts, controller, state, onCursor) {
package/dist/cli.js CHANGED
@@ -4,12 +4,12 @@ import {
4
4
  } from "./chunk-OGHDTFAX.js";
5
5
  import {
6
6
  startProxy
7
- } from "./chunk-TN45ULEB.js";
8
- import "./chunk-XXFVWP6H.js";
7
+ } from "./chunk-BYV7CWNY.js";
8
+ import "./chunk-N35X22G7.js";
9
9
  import {
10
10
  pairedConfigPath
11
11
  } from "./chunk-YH27B6SW.js";
12
- import "./chunk-34IRTWP6.js";
12
+ import "./chunk-KH5YEJIM.js";
13
13
  import "./chunk-JXMVZEQ7.js";
14
14
  import "./chunk-NR4Y54OL.js";
15
15
  import {
@@ -19,9 +19,9 @@ import {
19
19
  import "./chunk-BLEGIR35.js";
20
20
  import {
21
21
  VERSION
22
- } from "./chunk-XIDCYUTK.js";
23
- import "./chunk-X672ZN7V.js";
24
- import "./chunk-2OLXXOT3.js";
22
+ } from "./chunk-ALZX3SGI.js";
23
+ import "./chunk-Q7T3WAST.js";
24
+ import "./chunk-PPTKGWFF.js";
25
25
  import "./chunk-VHKPWUX7.js";
26
26
 
27
27
  // src/cli.ts
@@ -44,7 +44,7 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
44
44
  });
45
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) => {
46
46
  if (opts.type === "stop") {
47
- const { runStopHook } = await import("./stop-hook-YAVJ3EJD.js");
47
+ const { runStopHook } = await import("./stop-hook-TCPJFGFF.js");
48
48
  await runStopHook();
49
49
  process.exit(0);
50
50
  } else if (opts.type === "user-prompt-submit") {
@@ -52,7 +52,7 @@ program.command("hook").description("Run a kojee MCP hook script (called by Clau
52
52
  await runUserPromptSubmitHook();
53
53
  process.exit(0);
54
54
  } else if (opts.type === "codex-stop") {
55
- const { runCodexStopHook } = await import("./codex-stop-hook-VY7DOMAG.js");
55
+ const { runCodexStopHook } = await import("./codex-stop-hook-RWQSZTST.js");
56
56
  await runCodexStopHook();
57
57
  process.exit(0);
58
58
  } else {
@@ -78,7 +78,7 @@ Restart Claude Code for hooks to take effect.`
78
78
  program.command("send <tandem_id>").description(
79
79
  "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)."
80
80
  ).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) => {
81
- const { runSendCli } = await import("./send-cli-T6RPZZQ4.js");
81
+ const { runSendCli } = await import("./send-cli-IYJF32PE.js");
82
82
  const { exitCode, envelope } = await runSendCli({
83
83
  tandemId,
84
84
  body: opts.body,
@@ -89,7 +89,7 @@ program.command("send <tandem_id>").description(
89
89
  process.exit(exitCode);
90
90
  });
91
91
  program.command("tail <path>").description("Stream a file's contents and follow appends (portable replacement for `tail -F`)").action(async (filePath) => {
92
- const { runTail } = await import("./tail-stream-5DAVRQYK.js");
92
+ const { runTail } = await import("./tail-stream-4IMXI44S.js");
93
93
  try {
94
94
  await runTail(filePath);
95
95
  } catch (err) {
@@ -98,75 +98,70 @@ program.command("tail <path>").description("Stream a file's contents and follow
98
98
  }
99
99
  });
100
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 () => {
101
- const { runDoctor } = await import("./doctor-DVPT2ZRH.js");
101
+ const { runDoctor } = await import("./doctor-WVX3WCYX.js");
102
102
  const code = await runDoctor();
103
103
  process.exit(code);
104
104
  });
105
- program.command("init").description(
106
- "Set up kojee for a runtime (claude-code | hermes | openclaw | codex). Interactive when stdin is a TTY; `--runtime <id>` for non-interactive/CI. Run after `kojee-mcp pair`."
107
- ).option("--runtime <id>", "Target runtime: claude-code | hermes | openclaw | codex").option("--config-path <path>", "Override the runtime's MCP-config path").option("--hooks-path <path>", "Override the runtime's hooks-file path").option("--token <token>", "Gateway token \u2014 token mode (writes --token/--url into the MCP config)").option("--url <url>", "Broker base URL (token mode / pair URL; default https://rosie-staging.kojee.net)").option("--pair-code <code>", "Pair code \u2014 runs the pair flow (writes ~/.kojee/config.json) before configuring").option("--webhook-url <url>", "Webhook receiver URL (codex/hermes/openclaw)").option("--webhook-secret <secret>", "Webhook HMAC secret (generated if omitted)").option(
108
- "--webhook-signature-format <preset>",
109
- 'Signature preset: "github" = header X-Hub-Signature-256, value sha256=<hex> (default: bare hex in X-Kojee-Signature)'
110
- ).option(
111
- "--webhook-signature-header <name>",
112
- "Signature header name (default X-Kojee-Signature; overrides the preset's header)"
113
- ).option(
114
- "--webhook-signature-prefix <prefix>",
115
- "Literal string prepended to the hex digest (default empty; overrides the preset's prefix)"
116
- ).option("--uninstall", "Remove the kojee config for the chosen (or recorded) runtime").action(async (opts) => {
117
- const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
118
- if (opts.pairCode && !opts.uninstall) {
119
- const { runPair: runPair2 } = await import("./pair-P4ILCMT7.js");
120
- const { pairedConfigPath: pairedConfigPath2 } = await import("./paired-config-JTFLHMZ2.js");
121
- const { defaultPairedKeystorePath: defaultPairedKeystorePath2 } = await import("./keystore-XLEV3FL5.js");
122
- const url = (opts.url ?? "https://rosie-staging.kojee.net").replace(/\/+$/, "");
123
- try {
124
- const r = await runPair2({
125
- code: opts.pairCode,
126
- url,
127
- keystorePath: defaultPairedKeystorePath2(),
128
- configPath: pairedConfigPath2()
129
- });
130
- console.error(r.message);
131
- } catch (err) {
132
- console.error("[kojee-mcp init] Pairing failed:", err.message);
133
- process.exit(1);
134
- }
135
- }
136
- const { loadPairedConfig } = await import("./paired-config-JTFLHMZ2.js");
137
- const hasCredential = loadPairedConfig() !== null || opts.token !== void 0;
138
- if (!hasCredential && !interactive && !opts.uninstall) {
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.");
140
- process.exit(1);
141
- }
142
- const { runWizard } = await import("./wizard-Y3ULSO2C.js");
143
- const result = await runWizard({
144
- ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
145
- ...opts.uninstall ? { uninstall: true } : {},
146
- ...opts.configPath ? { configPath: opts.configPath } : {},
147
- ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
148
- // Token-mode threading (token + url ⇒ --token/--url in the written config).
149
- // Normalize the URL exactly like the proxy action does (strip trailing /).
150
- ...opts.token ? { token: opts.token } : {},
151
- ...opts.url ? { url: opts.url.replace(/\/+$/, "") } : {},
152
- ...opts.webhookUrl ? { webhookUrl: opts.webhookUrl } : {},
153
- ...opts.webhookSecret ? { webhookSecret: opts.webhookSecret } : {},
154
- ...opts.webhookSignatureFormat ? { webhookSignatureFormat: opts.webhookSignatureFormat } : {},
155
- ...opts.webhookSignatureHeader ? { webhookSignatureHeader: opts.webhookSignatureHeader } : {},
156
- ...opts.webhookSignaturePrefix !== void 0 ? { webhookSignaturePrefix: opts.webhookSignaturePrefix } : {},
157
- interactive,
158
- ...interactive ? {
159
- promptRuntime: promptRuntimeFromTty,
160
- promptUrl: promptUrlFromTty,
161
- promptAuth: promptAuthFromTty,
162
- promptToken: promptTokenFromTty,
163
- promptPairCode: promptPairCodeFromTty,
164
- promptWebhookUrl: promptWebhookUrlFromTty
165
- } : {}
166
- });
167
- console.error(result.output);
168
- process.exit(result.exitCode);
169
- });
105
+ function addInstallOptions(cmd, runtimeHelp) {
106
+ return cmd.option("--runtime <id>", runtimeHelp).option("--config-path <path>", "Override the runtime's MCP-config path").option("--hooks-path <path>", "Override the runtime's hooks-file path").option("--token <token>", "Gateway token \u2014 token mode (writes --token/--url into the MCP config; requires --url)").option("--url <url>", "Broker base URL (token mode / pair URL; default https://rosie-staging.kojee.net)").option("--pair <code>", "Pair code \u2014 runs the pair flow before configuring (alias of --pair-code)").option("--pair-code <code>", "Pair code \u2014 runs the pair flow (writes ~/.kojee/config.json) before configuring").option("--webhook-url <url>", "Webhook receiver URL (codex/hermes/openclaw)").option("--webhook-secret <secret>", "Webhook HMAC secret (generated if omitted)").option(
107
+ "--webhook-signature-format <preset>",
108
+ 'Signature preset: "github" = header X-Hub-Signature-256, value sha256=<hex> (default: bare hex in X-Kojee-Signature)'
109
+ ).option(
110
+ "--webhook-signature-header <name>",
111
+ "Signature header name (default X-Kojee-Signature; overrides the preset's header)"
112
+ ).option(
113
+ "--webhook-signature-prefix <prefix>",
114
+ "Literal string prepended to the hex digest (default empty; overrides the preset's prefix)"
115
+ ).option("--uninstall", "Remove the kojee config for the chosen (or recorded) runtime");
116
+ }
117
+ function makeInstallAction(verb) {
118
+ return async (opts) => {
119
+ const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
120
+ const { runSetup, resolvePairCode } = await import("./setup-handler-7HOFAD7C.js");
121
+ const pairCode = resolvePairCode(opts);
122
+ const result = await runSetup({
123
+ verb,
124
+ ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
125
+ ...opts.configPath ? { configPath: opts.configPath } : {},
126
+ ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
127
+ ...opts.token ? { token: opts.token } : {},
128
+ ...opts.url ? { url: opts.url } : {},
129
+ ...pairCode ? { pairCode } : {},
130
+ ...opts.webhookUrl ? { webhookUrl: opts.webhookUrl } : {},
131
+ ...opts.webhookSecret ? { webhookSecret: opts.webhookSecret } : {},
132
+ ...opts.webhookSignatureFormat ? { webhookSignatureFormat: opts.webhookSignatureFormat } : {},
133
+ ...opts.webhookSignatureHeader ? { webhookSignatureHeader: opts.webhookSignatureHeader } : {},
134
+ ...opts.webhookSignaturePrefix !== void 0 ? { webhookSignaturePrefix: opts.webhookSignaturePrefix } : {},
135
+ ...opts.uninstall ? { uninstall: true } : {},
136
+ interactive,
137
+ ...interactive ? {
138
+ prompts: {
139
+ promptRuntime: promptRuntimeFromTty,
140
+ promptUrl: promptUrlFromTty,
141
+ promptAuth: promptAuthFromTty,
142
+ promptToken: promptTokenFromTty,
143
+ promptPairCode: promptPairCodeFromTty,
144
+ promptWebhookUrl: promptWebhookUrlFromTty
145
+ }
146
+ } : {}
147
+ });
148
+ for (const m of result.messages) console.error(m);
149
+ console.error(result.output);
150
+ process.exit(result.exitCode);
151
+ };
152
+ }
153
+ addInstallOptions(
154
+ program.command("init").description(
155
+ "Set up kojee for a runtime (claude-code | hermes | openclaw | codex). Interactive when stdin is a TTY; `--runtime <id>` for non-interactive/CI. Run after `kojee-mcp pair`."
156
+ ),
157
+ "Target runtime: claude-code | hermes | openclaw | codex"
158
+ ).action(makeInstallAction("init"));
159
+ addInstallOptions(
160
+ program.command("setup").description(
161
+ "One-line install: pair + write the runtime's MCP config + hooks in one shot (claude-code | codex). `--pair <code> --url <broker> --runtime <id>`, or the token form `--token <T> --url <U> --runtime <id>` for CI."
162
+ ),
163
+ "Target runtime: claude-code | codex"
164
+ ).action(makeInstallAction("setup"));
170
165
  async function withReadline(fn) {
171
166
  const readline = await import("readline/promises");
172
167
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
@@ -4,9 +4,9 @@ import {
4
4
  decideCodexStopHook,
5
5
  defaultPeekPending,
6
6
  runCodexStopHook
7
- } from "./chunk-35XBRG3V.js";
7
+ } from "./chunk-KFDZWNQO.js";
8
8
  import "./chunk-LSUB6QMP.js";
9
- import "./chunk-X672ZN7V.js";
9
+ import "./chunk-Q7T3WAST.js";
10
10
  export {
11
11
  CODEX_PEEK_BUDGET_MS,
12
12
  codexPendingMarkerPath,
@@ -2,7 +2,7 @@ import "./chunk-XLKGPGZT.js";
2
2
  import {
3
3
  monitorHeartbeatPath,
4
4
  statusLogPath
5
- } from "./chunk-CO73VGWM.js";
5
+ } from "./chunk-DURRQDYI.js";
6
6
  import {
7
7
  discoveryPathForKey,
8
8
  readSessionDiscoveryByKey,
@@ -18,7 +18,7 @@ import "./chunk-BLEGIR35.js";
18
18
  import {
19
19
  buildMonitorSpawn,
20
20
  buildReplyRecipe
21
- } from "./chunk-X672ZN7V.js";
21
+ } from "./chunk-Q7T3WAST.js";
22
22
  import {
23
23
  deriveDiscoveryKey,
24
24
  findClaudeAncestorPid
@@ -328,7 +328,7 @@ function formatDoctorReport(report) {
328
328
  async function runDoctor() {
329
329
  const { readRecordedRuntime } = await import("./runtime-record-WO4IECM6.js");
330
330
  if (readRecordedRuntime() === "codex") {
331
- const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-PA3WO6LR.js");
331
+ const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-Z5M4SFBG.js");
332
332
  const report2 = collectCodexDoctorReport();
333
333
  console.error(formatCodexDoctorReport(report2));
334
334
  return report2.verdict === "broken" ? 1 : 0;
@@ -9,7 +9,7 @@ import {
9
9
  import "./chunk-BLEGIR35.js";
10
10
  import {
11
11
  CODEX_LISTEN_CAP_MS
12
- } from "./chunk-X672ZN7V.js";
12
+ } from "./chunk-Q7T3WAST.js";
13
13
 
14
14
  // src/doctor-codex.ts
15
15
  import fs from "fs";
@@ -5,7 +5,7 @@ import {
5
5
  startEventLog,
6
6
  statusLogPath,
7
7
  sweepStaleEventLogs
8
- } from "./chunk-CO73VGWM.js";
8
+ } from "./chunk-DURRQDYI.js";
9
9
  import "./chunk-DO42NPNR.js";
10
10
  import "./chunk-BLEGIR35.js";
11
11
  export {
@@ -6,7 +6,7 @@ import {
6
6
  sanitizeDisplayname,
7
7
  serializeCursorMap,
8
8
  startEventStream
9
- } from "./chunk-74XFVX6Z.js";
9
+ } from "./chunk-XN3IC4B5.js";
10
10
  import "./chunk-NR4Y54OL.js";
11
11
  export {
12
12
  UNDICI_DEFAULT_BODY_TIMEOUT_MS,
@@ -3,8 +3,8 @@ import {
3
3
  httpStatusForEnvelope,
4
4
  parseSendRequest,
5
5
  sendFailure
6
- } from "./chunk-DXJ6QLSJ.js";
7
- import "./chunk-2OLXXOT3.js";
6
+ } from "./chunk-PKOBR7ZN.js";
7
+ import "./chunk-PPTKGWFF.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-TN45ULEB.js";
5
- import "./chunk-XXFVWP6H.js";
6
- import "./chunk-34IRTWP6.js";
4
+ } from "./chunk-BYV7CWNY.js";
5
+ import "./chunk-N35X22G7.js";
6
+ import "./chunk-KH5YEJIM.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-XIDCYUTK.js";
12
- import "./chunk-X672ZN7V.js";
13
- import "./chunk-2OLXXOT3.js";
11
+ import "./chunk-ALZX3SGI.js";
12
+ import "./chunk-Q7T3WAST.js";
13
+ import "./chunk-PPTKGWFF.js";
14
14
  import "./chunk-VHKPWUX7.js";
15
15
  export {
16
16
  listTandemIds,
package/dist/lib.d.ts CHANGED
@@ -440,13 +440,16 @@ declare function resubscribeMemberships(opts: ResubscribeOptions): Promise<numbe
440
440
 
441
441
  interface StableSessionDeps {
442
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.
443
+ * DEPRECATED / IGNORED. The resolver no longer reads process ancestry — it
444
+ * reads the runtime's session id from the proxy's OWN env (see
445
+ * {@link resolveInstanceKey}). Accepted only so callers that still pass
446
+ * `{ ccPid }` keep compiling; it has no effect on resolution.
446
447
  */
447
448
  ccPid?: number | null;
449
+ /** Environment for {@link resolveInstanceKey} (defaults to `process.env`). Tests pin it. */
450
+ env?: NodeJS.ProcessEnv;
448
451
  /**
449
- * An explicit instanceKey — bypasses ancestry AND resolveInstanceKey entirely.
452
+ * An explicit instanceKey — bypasses {@link resolveInstanceKey} entirely.
450
453
  * Used where the window identity is known out-of-band (an embedder's account
451
454
  * key, tests).
452
455
  */
@@ -459,10 +462,10 @@ interface StableSessionDeps {
459
462
  * same id → the backend converges on ONE seat per (credential, window) across
460
463
  * restarts (no phantom seats; reaper retired).
461
464
  *
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.
465
+ * instanceKey precedence: an explicit `deps.instanceKey` wins; otherwise
466
+ * {@link resolveInstanceKey} resolves it from the proxy's OWN env → the runtime
467
+ * session id (e.g. CLAUDE_CODE_SESSION_ID), else KOJEE_INSTANCE, else a minted
468
+ * per-launch id. (No process-ancestry read, no directory hash — both removed.)
466
469
  *
467
470
  * Returns the resolved instanceKey + the installed sessionId so callers that
468
471
  * also need the key (room-memory, discovery) don't resolve it twice.
@@ -565,9 +568,10 @@ interface ToolCallHooks {
565
568
  */
566
569
  interface WakeDeliveryContext {
567
570
  /**
568
- * This instance's stable key (#28): the CC session_id when readable, else
569
- * KOJEE_INSTANCE / a working-dir hash. Used to derive the per-runtime
570
- * event-log filename and other per-session artifacts.
571
+ * This instance's stable key (#28): the runtime's own-env session id, else
572
+ * `inst-<KOJEE_INSTANCE>`, else a minted `mint-<uuid>` (see `resolveInstanceKey`).
573
+ * Used to derive the per-runtime event-log filename and other per-session
574
+ * artifacts.
571
575
  */
572
576
  instanceKey: string;
573
577
  /** The detected runtime label (drives capability + which delivery is built). */
package/dist/lib.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  normalizeBackendEvent,
6
6
  sanitizeDisplayname,
7
7
  startEventStream
8
- } from "./chunk-74XFVX6Z.js";
8
+ } from "./chunk-XN3IC4B5.js";
9
9
  import {
10
10
  loadPairedConfig,
11
11
  pairedConfigPath
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  GatewayClient,
15
15
  applyStableSessionId
16
- } from "./chunk-34IRTWP6.js";
16
+ } from "./chunk-KH5YEJIM.js";
17
17
  import {
18
18
  AuthModule
19
19
  } from "./chunk-JXMVZEQ7.js";
@@ -29,8 +29,7 @@ import {
29
29
  saveKeystore
30
30
  } from "./chunk-CH32ELFX.js";
31
31
  import "./chunk-BLEGIR35.js";
32
- import "./chunk-2OLXXOT3.js";
33
- import "./chunk-VHKPWUX7.js";
32
+ import "./chunk-PPTKGWFF.js";
34
33
  export {
35
34
  AuthModule,
36
35
  GatewayClient,
@@ -1,12 +1,12 @@
1
- import "./chunk-35XBRG3V.js";
1
+ import "./chunk-KFDZWNQO.js";
2
2
  import "./chunk-PHXO5P25.js";
3
3
  import "./chunk-LSUB6QMP.js";
4
4
  import {
5
5
  claudeCodeAdapter
6
- } from "./chunk-XXFVWP6H.js";
7
- import "./chunk-XIDCYUTK.js";
8
- import "./chunk-X672ZN7V.js";
9
- import "./chunk-2OLXXOT3.js";
6
+ } from "./chunk-N35X22G7.js";
7
+ import "./chunk-ALZX3SGI.js";
8
+ import "./chunk-Q7T3WAST.js";
9
+ import "./chunk-PPTKGWFF.js";
10
10
 
11
11
  // src/delivery/lib/fanout.ts
12
12
  async function deliverEvent(event, sinks) {
@@ -91,18 +91,18 @@ function createClaudeCodeDelivery() {
91
91
  name: "claude-code",
92
92
  async start(ctx) {
93
93
  const { EventQueue } = await import("./event-queue-5YVJFR3E.js");
94
- const { startHookServer } = await import("./hook-server-XK2NHLJV.js");
94
+ const { startHookServer } = await import("./hook-server-CWBH6PFL.js");
95
95
  const {
96
96
  writeDiscoveryByKey,
97
97
  cleanupDiscoveryByKey,
98
98
  sweepStaleDiscovery
99
99
  } = await import("./session-discovery-FNMJGFPM.js");
100
- const { startEventLog, sweepStaleEventLogs } = await import("./event-log-VZD7NKYX.js");
100
+ const { startEventLog, sweepStaleEventLogs } = await import("./event-log-HOBO3ECB.js");
101
101
  const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
102
102
  const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
103
103
  const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
104
- const { startEventStream } = await import("./event-stream-XX5EZ6HN.js");
105
- const { createMcpServer } = await import("./server-LTFG4GMV.js");
104
+ const { startEventStream } = await import("./event-stream-SYBUJ5EH.js");
105
+ const { createMcpServer } = await import("./server-R24AVNTI.js");
106
106
  const { deriveDiscoveryKey } = await import("./ancestry-ONFBQEP5.js");
107
107
  sweepStaleDiscovery();
108
108
  sweepStaleEventLogs();
@@ -274,12 +274,12 @@ function createWebhookDelivery(name) {
274
274
  return {
275
275
  name,
276
276
  async start(ctx) {
277
- const { startEventLog, sweepStaleEventLogs } = await import("./event-log-VZD7NKYX.js");
277
+ const { startEventLog, sweepStaleEventLogs } = await import("./event-log-HOBO3ECB.js");
278
278
  const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
279
279
  const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
280
280
  const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
281
- const { startEventStream } = await import("./event-stream-XX5EZ6HN.js");
282
- const { createMcpServer } = await import("./server-LTFG4GMV.js");
281
+ const { startEventStream } = await import("./event-stream-SYBUJ5EH.js");
282
+ const { createMcpServer } = await import("./server-R24AVNTI.js");
283
283
  sweepStaleEventLogs();
284
284
  eventLog = startEventLog({
285
285
  key: ctx.instanceKey,
@@ -3,21 +3,20 @@ import {
3
3
  executeSend,
4
4
  parseSendRequest,
5
5
  sendFailure
6
- } from "./chunk-DXJ6QLSJ.js";
6
+ } from "./chunk-PKOBR7ZN.js";
7
7
  import {
8
8
  loadPairedConfig
9
9
  } from "./chunk-YH27B6SW.js";
10
10
  import {
11
11
  GatewayClient,
12
12
  applyStableSessionId
13
- } from "./chunk-34IRTWP6.js";
13
+ } from "./chunk-KH5YEJIM.js";
14
14
  import "./chunk-NR4Y54OL.js";
15
15
  import {
16
16
  loadKeystore
17
17
  } from "./chunk-CH32ELFX.js";
18
18
  import "./chunk-BLEGIR35.js";
19
- import "./chunk-2OLXXOT3.js";
20
- import "./chunk-VHKPWUX7.js";
19
+ import "./chunk-PPTKGWFF.js";
21
20
 
22
21
  // src/tandem/send-cli.ts
23
22
  import os from "os";
@@ -3,9 +3,9 @@ import {
3
3
  createMcpServer,
4
4
  executeToolCall,
5
5
  startMcpServer
6
- } from "./chunk-XIDCYUTK.js";
7
- import "./chunk-X672ZN7V.js";
8
- import "./chunk-2OLXXOT3.js";
6
+ } from "./chunk-ALZX3SGI.js";
7
+ import "./chunk-Q7T3WAST.js";
8
+ import "./chunk-PPTKGWFF.js";
9
9
  export {
10
10
  buildChannelInstructions,
11
11
  createMcpServer,
@@ -25,13 +25,16 @@ import {
25
25
  resolveSignatureEmission,
26
26
  resolveWebhookConfig
27
27
  } from "./chunk-V5VZPYMZ.js";
28
+ import {
29
+ loadPairedConfig
30
+ } from "./chunk-YH27B6SW.js";
28
31
  import {
29
32
  secureFile
30
33
  } from "./chunk-BLEGIR35.js";
31
34
  import {
32
35
  CODEX_LISTEN_CAP_MS,
33
36
  buildWebhookReceiverNote
34
- } from "./chunk-X672ZN7V.js";
37
+ } from "./chunk-Q7T3WAST.js";
35
38
 
36
39
  // src/wizard/wizard.ts
37
40
  import crypto2 from "crypto";
@@ -824,11 +827,116 @@ registerBuiltinInstaller(
824
827
  ["pair-credential", "in-process-plugin"],
825
828
  (o) => configureOpenclaw(o)
826
829
  );
830
+
831
+ // src/wizard/setup-handler.ts
832
+ var SETUP_SUPPORTED_RUNTIMES = ["claude-code", "codex"];
833
+ function isSetupRuntime(value) {
834
+ return SETUP_SUPPORTED_RUNTIMES.includes(value);
835
+ }
836
+ function resolvePairCode(opts) {
837
+ return opts.pair ?? opts.pairCode;
838
+ }
839
+ async function defaultRunPair(args) {
840
+ const { runPair } = await import("./pair-P4ILCMT7.js");
841
+ const { pairedConfigPath } = await import("./paired-config-JTFLHMZ2.js");
842
+ const { defaultPairedKeystorePath } = await import("./keystore-XLEV3FL5.js");
843
+ return runPair({
844
+ code: args.code,
845
+ url: args.url,
846
+ keystorePath: defaultPairedKeystorePath(),
847
+ configPath: pairedConfigPath()
848
+ });
849
+ }
850
+ async function runSetup(opts) {
851
+ const messages = [];
852
+ const requested = opts.runtime ?? "claude-code";
853
+ if (opts.verb === "setup" && opts.runtime !== void 0 && !isSetupRuntime(opts.runtime)) {
854
+ const supported = SETUP_SUPPORTED_RUNTIMES.join(", ");
855
+ const output = isWizardRuntime(opts.runtime) ? `\`setup\` does not support --runtime ${opts.runtime} yet (coming soon). Supported by \`setup\`: ${supported}. For ${opts.runtime}, use \`kojee-mcp init --runtime ${opts.runtime}\` for now.` : `Unknown --runtime "${opts.runtime}". \`setup\` supports: ${supported}. (For hermes/openclaw use \`kojee-mcp init\`.)`;
856
+ return { runtime: requested, messages, output, exitCode: 2 };
857
+ }
858
+ if (opts.token !== void 0 && (opts.url === void 0 || opts.url.trim() === "") && !opts.uninstall) {
859
+ return {
860
+ runtime: requested,
861
+ messages,
862
+ output: "--token requires --url (the broker URL the Kojee UI gave you). Re-run with --url <broker-url>. Nothing was written.",
863
+ exitCode: 2
864
+ };
865
+ }
866
+ const url = opts.url ? opts.url.replace(/\/+$/, "") : void 0;
867
+ if (opts.pairCode && !opts.uninstall) {
868
+ const pair = opts.runPair ?? defaultRunPair;
869
+ const pairUrl = url ?? DEFAULT_BROKER_URL;
870
+ try {
871
+ const r = await pair({ code: opts.pairCode, url: pairUrl });
872
+ messages.push(r.message);
873
+ } catch (err) {
874
+ return {
875
+ runtime: requested,
876
+ messages,
877
+ output: `Pairing failed: ${err.message}`,
878
+ exitCode: 1
879
+ };
880
+ }
881
+ }
882
+ const loadCfg = opts.loadPairedConfig ?? loadPairedConfig;
883
+ const hasCredential = loadCfg() !== null || opts.token !== void 0;
884
+ if (!hasCredential && !opts.interactive && !opts.uninstall) {
885
+ return {
886
+ runtime: requested,
887
+ messages,
888
+ output: "Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run \u2014 or pass --pair <code> (or --token <T> --url <U>), or run in a terminal for the guided wizard.",
889
+ exitCode: 1
890
+ };
891
+ }
892
+ const result = await runWizard({
893
+ ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
894
+ ...opts.uninstall ? { uninstall: true } : {},
895
+ ...opts.configPath ? { configPath: opts.configPath } : {},
896
+ ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
897
+ // Token mode (token + url) threads --token/--url into the written config.
898
+ ...opts.token ? { token: opts.token } : {},
899
+ ...url ? { url } : {},
900
+ ...opts.webhookUrl ? { webhookUrl: opts.webhookUrl } : {},
901
+ ...opts.webhookSecret ? { webhookSecret: opts.webhookSecret } : {},
902
+ ...opts.webhookSignatureFormat ? { webhookSignatureFormat: opts.webhookSignatureFormat } : {},
903
+ ...opts.webhookSignatureHeader ? { webhookSignatureHeader: opts.webhookSignatureHeader } : {},
904
+ ...opts.webhookSignaturePrefix !== void 0 ? { webhookSignaturePrefix: opts.webhookSignaturePrefix } : {},
905
+ interactive: opts.interactive,
906
+ ...opts.interactive && opts.prompts ? opts.prompts : {}
907
+ });
908
+ if (opts.verb === "setup" && result.exitCode === 0 && !opts.uninstall) {
909
+ return {
910
+ runtime: result.runtime,
911
+ messages,
912
+ output: crispSetupSummary(result.runtime, opts, url),
913
+ exitCode: 0
914
+ };
915
+ }
916
+ return { runtime: result.runtime, messages, output: result.output, exitCode: result.exitCode };
917
+ }
918
+ function crispSetupSummary(runtime, opts, url) {
919
+ const authLine = opts.token ? ` Auth: token mode (--token/--url${url ? `, ${url}` : ""})` : " Auth: paired credentials (~/.kojee/config.json)";
920
+ const lines = [];
921
+ if (runtime === "codex") {
922
+ lines.push("\u2713 kojee is set up for Codex.");
923
+ lines.push(` MCP config: ${opts.configPath ?? "~/.codex/config.toml"} ([mcp_servers.kojee])`);
924
+ lines.push(` Stop hook: ${opts.hooksPath ?? "~/.codex/hooks.json"} (codex-stop)`);
925
+ lines.push(authLine);
926
+ lines.push("");
927
+ lines.push("Next: restart Codex to connect.");
928
+ } else {
929
+ lines.push("\u2713 kojee is set up for Claude Code.");
930
+ lines.push(` MCP config: ${opts.configPath ?? "~/.claude.json"} (mcpServers.kojee)`);
931
+ lines.push(` Hooks: ${opts.hooksPath ?? "~/.claude/settings.json"} (Stop + UserPromptSubmit)`);
932
+ lines.push(authLine);
933
+ lines.push("");
934
+ lines.push("Next: restart Claude Code to connect.");
935
+ }
936
+ return lines.join("\n");
937
+ }
827
938
  export {
828
- CODEX_UNVERIFIED_NOTE,
829
- DEFAULT_BROKER_URL,
830
- generateWebhookSecret,
831
- resolveRuntime,
832
- resolveWizardWebhook,
833
- runWizard
939
+ SETUP_SUPPORTED_RUNTIMES,
940
+ resolvePairCode,
941
+ runSetup
834
942
  };
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  monitorHeartbeatPath,
10
10
  nudgeSentinelPath
11
- } from "./chunk-CO73VGWM.js";
11
+ } from "./chunk-DURRQDYI.js";
12
12
  import {
13
13
  readSessionDiscoveryByKey
14
14
  } from "./chunk-DO42NPNR.js";
@@ -18,7 +18,7 @@ import {
18
18
  import "./chunk-BLEGIR35.js";
19
19
  import {
20
20
  buildMonitorNudge
21
- } from "./chunk-X672ZN7V.js";
21
+ } from "./chunk-Q7T3WAST.js";
22
22
  import {
23
23
  deriveDiscoveryKey,
24
24
  findClaudeAncestorPid
@@ -3,11 +3,11 @@ import {
3
3
  STATUS_LINE_PREFIX,
4
4
  monitorHeartbeatPath,
5
5
  statusLogPath
6
- } from "./chunk-CO73VGWM.js";
6
+ } from "./chunk-DURRQDYI.js";
7
7
  import "./chunk-DO42NPNR.js";
8
8
  import {
9
9
  createAdaptiveWatchdog
10
- } from "./chunk-74XFVX6Z.js";
10
+ } from "./chunk-XN3IC4B5.js";
11
11
  import "./chunk-NR4Y54OL.js";
12
12
  import "./chunk-BLEGIR35.js";
13
13
 
@@ -15,6 +15,9 @@ import "./chunk-BLEGIR35.js";
15
15
  import fs from "fs";
16
16
  var STATUS_LINE_RE = new RegExp(`^\\[[^\\]]*\\] ${STATUS_LINE_PREFIX} (.*)$`);
17
17
  var HEARTBEAT_FIELDS_RE = /(^|\s)status=heartbeat(\s|$)/;
18
+ var ROTATED_FIELDS_RE = /(^|\s)status=rotated(\s|$)/;
19
+ var ROTATED_DROPPED_RE = /(^|\s)dropped=(\d+)(\s|$)/;
20
+ var MESSAGE_CURSOR_RE = /(^|\s)cursor=(\S+)(\s|$)/;
18
21
  var HEARTBEAT_STALE_FLOOR_MS = envInt("KOJEE_TAIL_STALE_MS", 12e4);
19
22
  var STALE_CHECK_INTERVAL_MS = envInt("KOJEE_TAIL_CHECK_MS", 5e3);
20
23
  function envInt(name, fallback) {
@@ -38,14 +41,18 @@ async function runTail(messagesPath) {
38
41
  }
39
42
  }
40
43
  };
44
+ let lastCursor = null;
41
45
  function onMessageLine(line) {
42
46
  lastLifeAt = Date.now();
43
47
  if (staleAnnounced) {
44
48
  staleAnnounced = false;
45
49
  process.stdout.write("[kojee] stream recovered \u2014 events flowing again.\n");
46
50
  }
51
+ const cm = MESSAGE_CURSOR_RE.exec(line);
52
+ if (cm && cm[2]) lastCursor = cm[2];
47
53
  process.stdout.write(line + "\n");
48
54
  }
55
+ let statusSeeded = false;
49
56
  function onStatusLine(line) {
50
57
  const now = Date.now();
51
58
  const m = STATUS_LINE_RE.exec(line);
@@ -58,12 +65,26 @@ async function runTail(messagesPath) {
58
65
  staleAnnounced = false;
59
66
  process.stdout.write("[kojee] stream recovered \u2014 heartbeats resumed.\n");
60
67
  }
68
+ if (statusSeeded && ROTATED_FIELDS_RE.test(fields)) {
69
+ const dm = ROTATED_DROPPED_RE.exec(fields);
70
+ const dropped = dm ? dm[2] : "?";
71
+ const since = lastCursor ? `since=${lastCursor}` : "since=<your last cursor>";
72
+ process.stdout.write(
73
+ `[kojee] WARNING: event log rotated (dropped=${dropped}) \u2014 older events were truncated and may have been missed; run tandem_messages(${since}) to backfill.
74
+ `
75
+ );
76
+ }
61
77
  }
62
78
  const followers = [
63
79
  makeFollower(messagesPath, onMessageLine, touchHeartbeat),
64
80
  makeFollower(statusPath, onStatusLine, touchHeartbeat)
65
81
  ];
66
- void Promise.all(followers.map((f) => f.start()));
82
+ void Promise.all([
83
+ followers[0].start(),
84
+ followers[1].start().then(() => {
85
+ statusSeeded = true;
86
+ })
87
+ ]);
67
88
  const staleTimer = setInterval(() => {
68
89
  if (staleAnnounced) return;
69
90
  const now = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.5.18",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -33,6 +33,7 @@
33
33
  "jose": "^5.0.0",
34
34
  "ps-list": "^8.1.1",
35
35
  "ulidx": "^2.3.0",
36
+ "undici": "^8.5.0",
36
37
  "zod": "^3.22.0"
37
38
  },
38
39
  "devDependencies": {