kojee-mcp 0.7.5-staging.2 → 0.7.5-staging.21
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/README.md +27 -6
- package/dist/{cc-session-id-RURNIHHC.js → cc-session-id-5HHQJ7VC.js} +3 -1
- package/dist/{chunk-ZSHCHOPL.js → chunk-676WQSWZ.js} +163 -7
- package/dist/{chunk-FJUAMJHU.js → chunk-6NPMVZD7.js} +38 -3
- package/dist/{chunk-247WFMCJ.js → chunk-AGZ7ZGLY.js} +30 -3
- package/dist/chunk-AXBJQJKA.js +65 -0
- package/dist/{chunk-TCWIXG5C.js → chunk-CCVRXJJ2.js} +1 -1
- package/dist/{chunk-WBXU27BF.js → chunk-FNMKP34Y.js} +1 -1
- package/dist/chunk-GEGUWQYT.js +90 -0
- package/dist/{chunk-NE2F5CKS.js → chunk-LKGKDZSO.js} +9 -5
- package/dist/{chunk-XPIW4N55.js → chunk-N2YAZRAD.js} +178 -43
- package/dist/{chunk-LSUB6QMP.js → chunk-NIIBVKLP.js} +3 -0
- package/dist/chunk-P64UR3SE.js +86 -0
- package/dist/{chunk-A4IOKD4Z.js → chunk-QYZP6QT2.js} +2 -4
- package/dist/{chunk-KNEJTD6G.js → chunk-SZLNNMIZ.js} +6 -2
- package/dist/{chunk-CUOJHP2S.js → chunk-UGEBGCYU.js} +48 -12
- package/dist/{chunk-6XWTUDWW.js → chunk-WDWVBPXA.js} +7 -5
- package/dist/{chunk-TNZITJAB.js → chunk-WVQU3QQ7.js} +14 -4
- package/dist/cli.js +42 -23
- package/dist/{codex-prompt-submit-hook-J5PZEJSK.js → codex-prompt-submit-hook-UQJI6A54.js} +2 -2
- package/dist/{codex-stop-hook-32W4BIOM.js → codex-stop-hook-GTGRQNVV.js} +2 -2
- package/dist/{connect-handler-NZINEMG3.js → connect-handler-GTFXNZUC.js} +68 -9
- package/dist/{doctor-WUU5BVPT.js → doctor-54S7JGOE.js} +64 -5
- package/dist/{doctor-codex-PJFWIABF.js → doctor-codex-6H4OOS6O.js} +84 -10
- package/dist/doctor-hermes-KMHM27Y3.js +597 -0
- package/dist/{doctor-openclaw-LL6NSPLC.js → doctor-openclaw-B5KADVHD.js} +5 -1
- package/dist/{event-log-2NBJEIEP.js → event-log-M22X3FAI.js} +1 -1
- package/dist/{event-stream-ATKYXDBV.js → event-stream-5ZEL7FXN.js} +5 -1
- package/dist/flap-log-D4KG7RK4.js +23 -0
- package/dist/{gateway-client-CbM2OC_w.d.ts → gateway-client-CLmQghON.d.ts} +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -7
- package/dist/{install-JQNDGAAQ.js → install-GJVZEWLI.js} +8 -7
- package/dist/lib.d.ts +30 -1
- package/dist/lib.js +3 -3
- package/dist/plugins/hermes/adapter.py +7 -2
- package/dist/{registry-XJ67EO22.js → registry-MCJAVXJW.js} +36 -24
- package/dist/{send-cli-45RLGYIC.js → send-cli-WTY7H3BR.js} +2 -2
- package/dist/{server-DU3LFS32.js → server-VM3ZIGSZ.js} +2 -3
- package/dist/session-start-hook-VL3KYDTT.js +25 -0
- package/dist/{setup-handler-44ASXYMS.js → setup-handler-GNAIWAYU.js} +7 -7
- package/dist/{stop-hook-N6TX4YQT.js → stop-hook-BLPYRE7T.js} +5 -5
- package/dist/stream-watchdog-K2AWNCQW.js +64 -0
- package/dist/{tail-stream-G4WGTXDS.js → tail-stream-PQXEY3TI.js} +120 -9
- package/dist/{user-prompt-submit-hook-DTXXFDSD.js → user-prompt-submit-hook-YQXGE3JU.js} +3 -3
- package/package.json +3 -2
- package/dist/chunk-5DHIUN73.js +0 -25
- package/dist/chunk-TMCNB4JH.js +0 -33
|
@@ -11,6 +11,7 @@ var STALE_FLOOR_MS = 9e4;
|
|
|
11
11
|
var STALE_INTERVAL_MULTIPLIER = 3;
|
|
12
12
|
var STALE_CHECK_INTERVAL_MS = 5e3;
|
|
13
13
|
var UNARMED_FALLBACK_MS = 12 * 6e4;
|
|
14
|
+
var CONNECT_TIMEOUT_MS = 3e4;
|
|
14
15
|
var UNDICI_DEFAULT_BODY_TIMEOUT_MS = 3e5;
|
|
15
16
|
function createAdaptiveWatchdog(options = {}) {
|
|
16
17
|
const floorMs = options.floorMs ?? STALE_FLOOR_MS;
|
|
@@ -79,7 +80,18 @@ async function startEventStream(opts) {
|
|
|
79
80
|
let stopped = false;
|
|
80
81
|
const streamDispatcher = new Agent({
|
|
81
82
|
bodyTimeout: UNDICI_DEFAULT_BODY_TIMEOUT_MS,
|
|
82
|
-
headersTimeout: UNDICI_DEFAULT_BODY_TIMEOUT_MS
|
|
83
|
+
headersTimeout: UNDICI_DEFAULT_BODY_TIMEOUT_MS,
|
|
84
|
+
// FORCE HTTP/1.1 FOR THE SSE STREAM. undici 8.x defaults `allowH2: true`, so on a
|
|
85
|
+
// TLS connection ALPN negotiates HTTP/2 to the ALB. The ALB then RSTs the long-lived
|
|
86
|
+
// SSE stream with NGHTTP_INTERNAL_ERROR (H2 idle / per-stream lifetime limits), which
|
|
87
|
+
// undici surfaces as ERR_HTTP2_STREAM_ERROR — a mid-connection stream reset the
|
|
88
|
+
// reconnect loop can't recover from: it wedges (connected=false for 90s) → the
|
|
89
|
+
// liveness watchdog self-exits(1) → systemd restarts → a fresh session that may
|
|
90
|
+
// subscribe to a partial room-set. Observed fleet-wide (inst-clauron/henry/
|
|
91
|
+
// shakshuka-dev), 25 recorded flaps all with this signature. HTTP/1.1 keeps the SSE
|
|
92
|
+
// connection open indefinitely (nginx/ALB 3600s idle timeout + our heartbeats).
|
|
93
|
+
// Root-caused by Backend-Dev, 2026-08-04.
|
|
94
|
+
allowH2: false
|
|
83
95
|
});
|
|
84
96
|
const cursors = /* @__PURE__ */ new Map();
|
|
85
97
|
let currentController = new AbortController();
|
|
@@ -165,10 +177,16 @@ async function startEventStream(opts) {
|
|
|
165
177
|
}
|
|
166
178
|
async function connectAndConsume(opts, cursors, controller, isReconnect, state, dispatcher, onCursor, onOpen) {
|
|
167
179
|
const mapSince = cursors.size > 0 ? serializeCursorMap(cursors) : null;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
res = await openStream(opts, controller,
|
|
180
|
+
const connectTimer = setTimeout(() => controller.abort(), CONNECT_TIMEOUT_MS);
|
|
181
|
+
let res;
|
|
182
|
+
try {
|
|
183
|
+
res = await openStream(opts, controller, mapSince, dispatcher);
|
|
184
|
+
if (res.status === 400 && mapSince !== null) {
|
|
185
|
+
console.error("[event-stream] per-room ?since rejected (400) \u2014 falling back to bare cursor");
|
|
186
|
+
res = await openStream(opts, controller, String(Math.min(...cursors.values())), dispatcher);
|
|
187
|
+
}
|
|
188
|
+
} finally {
|
|
189
|
+
clearTimeout(connectTimer);
|
|
172
190
|
}
|
|
173
191
|
if (!res.ok) throw new Error(`SSE connect failed: ${res.status}`);
|
|
174
192
|
if (!res.body) throw new Error("SSE response has no body");
|
|
@@ -182,10 +200,15 @@ async function connectAndConsume(opts, cursors, controller, isReconnect, state,
|
|
|
182
200
|
await consumeSse(res.body, opts, controller, state, onCursor);
|
|
183
201
|
return true;
|
|
184
202
|
}
|
|
185
|
-
|
|
203
|
+
function buildStreamUrl(brokerUrl, sinceValue, seatName) {
|
|
186
204
|
const params = new URLSearchParams();
|
|
187
205
|
if (sinceValue !== null) params.set("since", sinceValue);
|
|
188
|
-
|
|
206
|
+
if (seatName) params.set("seat_name", seatName);
|
|
207
|
+
const qs = params.toString();
|
|
208
|
+
return `${brokerUrl}/api/v2/tandems/stream/${qs ? "?" + qs : ""}`;
|
|
209
|
+
}
|
|
210
|
+
async function openStream(opts, controller, sinceValue, dispatcher) {
|
|
211
|
+
const url = buildStreamUrl(opts.brokerUrl, sinceValue, opts.seatName);
|
|
189
212
|
const proof = await createDPoPProof(
|
|
190
213
|
opts.gateway.getPrivateKey(),
|
|
191
214
|
opts.gateway.getKid(),
|
|
@@ -250,12 +273,19 @@ async function consumeSse(body, opts, controller, state, onCursor) {
|
|
|
250
273
|
}
|
|
251
274
|
continue;
|
|
252
275
|
}
|
|
253
|
-
|
|
276
|
+
{
|
|
254
277
|
try {
|
|
255
278
|
const raw = JSON.parse(evt.data);
|
|
256
279
|
const parsed = normalizeBackendEvent(raw, evt.event);
|
|
257
|
-
|
|
258
|
-
|
|
280
|
+
if (typeof parsed.cursor === "number") {
|
|
281
|
+
onCursor(parsed.tandem_id, parsed.cursor);
|
|
282
|
+
}
|
|
283
|
+
if (parsed.wake !== true) continue;
|
|
284
|
+
if (evt.event === "message_update" && parsed.edited_at === void 0) {
|
|
285
|
+
console.error(
|
|
286
|
+
"[event-stream] message_update missing edited_at \u2014 the edit will dedupe against the original and NOT wake (backend contract regression?)"
|
|
287
|
+
);
|
|
288
|
+
}
|
|
259
289
|
if (opts.delivery) {
|
|
260
290
|
try {
|
|
261
291
|
await opts.delivery.deliver(parsed);
|
|
@@ -393,6 +423,8 @@ function normalizeBackendEvent(raw, sseEventType) {
|
|
|
393
423
|
const wake = typeof rawWake === "boolean" ? rawWake : void 0;
|
|
394
424
|
const rawWakeReason = obj["wake_reason"];
|
|
395
425
|
const wakeReason = typeof rawWakeReason === "string" && rawWakeReason.trim() ? sanitizeDisplayname(rawWakeReason) : void 0;
|
|
426
|
+
const rawEditedAt = obj["edited_at"];
|
|
427
|
+
const editedAt = typeof rawEditedAt === "string" && rawEditedAt.trim() ? rawEditedAt : void 0;
|
|
396
428
|
const senderPresent = typeof obj === "object" && obj !== null && "sender" in obj;
|
|
397
429
|
const maybeFrom = obj["from"];
|
|
398
430
|
if (!senderPresent && maybeFrom && typeof maybeFrom["principal"] === "string") {
|
|
@@ -406,7 +438,8 @@ function normalizeBackendEvent(raw, sseEventType) {
|
|
|
406
438
|
displayname: resolveDisplayname(maybeFrom["displayname"], canonicalPrincipal)
|
|
407
439
|
},
|
|
408
440
|
...wake !== void 0 ? { wake } : {},
|
|
409
|
-
...wakeReason !== void 0 ? { wake_reason: wakeReason } : {}
|
|
441
|
+
...wakeReason !== void 0 ? { wake_reason: wakeReason } : {},
|
|
442
|
+
...editedAt !== void 0 ? { edited_at: editedAt } : {}
|
|
410
443
|
};
|
|
411
444
|
}
|
|
412
445
|
const sender = obj["sender"] ?? {};
|
|
@@ -441,18 +474,21 @@ function normalizeBackendEvent(raw, sseEventType) {
|
|
|
441
474
|
...obj["reply_to"] !== void 0 ? { reply_to: obj["reply_to"] } : {},
|
|
442
475
|
...severity ? { severity } : {},
|
|
443
476
|
...wake !== void 0 ? { wake } : {},
|
|
444
|
-
...wakeReason !== void 0 ? { wake_reason: wakeReason } : {}
|
|
477
|
+
...wakeReason !== void 0 ? { wake_reason: wakeReason } : {},
|
|
478
|
+
...editedAt !== void 0 ? { edited_at: editedAt } : {}
|
|
445
479
|
};
|
|
446
480
|
}
|
|
447
481
|
|
|
448
482
|
export {
|
|
449
483
|
undiciFetch,
|
|
450
484
|
UNARMED_FALLBACK_MS,
|
|
485
|
+
CONNECT_TIMEOUT_MS,
|
|
451
486
|
UNDICI_DEFAULT_BODY_TIMEOUT_MS,
|
|
452
487
|
createAdaptiveWatchdog,
|
|
453
488
|
createBackoffController,
|
|
454
489
|
LOG_HEARTBEAT_INTERVAL_MS,
|
|
455
490
|
startEventStream,
|
|
491
|
+
buildStreamUrl,
|
|
456
492
|
statusReason,
|
|
457
493
|
serializeCursorMap,
|
|
458
494
|
sanitizeDisplayname,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
wrapNpxLauncher
|
|
3
|
+
} from "./chunk-D6JKFJ6A.js";
|
|
1
4
|
import {
|
|
2
5
|
buildHookCommand,
|
|
3
6
|
isKojeeHookCommand,
|
|
4
7
|
npxHookCommand
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
wrapNpxLauncher
|
|
8
|
-
} from "./chunk-D6JKFJ6A.js";
|
|
8
|
+
} from "./chunk-AXBJQJKA.js";
|
|
9
9
|
import {
|
|
10
10
|
kojeeHomeDir
|
|
11
11
|
} from "./chunk-SQL56SEB.js";
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-U5HHHRXA.js";
|
|
15
15
|
import {
|
|
16
16
|
VERSION
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-6NPMVZD7.js";
|
|
18
18
|
|
|
19
19
|
// src/wizard/codex-config.ts
|
|
20
20
|
import fs from "fs";
|
|
@@ -32,6 +32,7 @@ function defaultCodexConfigPath() {
|
|
|
32
32
|
function defaultCodexHooksPath() {
|
|
33
33
|
return win32DriveQualified(path.join(kojeeHomeDir(), ".codex", "hooks.json"), ".codex", "hooks.json");
|
|
34
34
|
}
|
|
35
|
+
var CODEX_TRUST_FIX_STEPS = 'to trust it, run `codex`, then type `/hooks` and press `t` on the entry \u2014 Codex only prints a "hooks need review" warning at startup, it does not auto-prompt';
|
|
35
36
|
var CODEX_STOP_HOOK_COMMAND = npxHookCommand("codex-stop");
|
|
36
37
|
function codexStopHookCommand() {
|
|
37
38
|
return buildHookCommand("codex-stop");
|
|
@@ -345,6 +346,7 @@ function writeFile600(p, content) {
|
|
|
345
346
|
export {
|
|
346
347
|
defaultCodexConfigPath,
|
|
347
348
|
defaultCodexHooksPath,
|
|
349
|
+
CODEX_TRUST_FIX_STEPS,
|
|
348
350
|
buildCodexMcpServerTable,
|
|
349
351
|
buildCodexStopHookBlock,
|
|
350
352
|
buildCodexPromptSubmitHookBlock,
|
|
@@ -15,6 +15,7 @@ var MAX_BODY_CHARS = 200;
|
|
|
15
15
|
var DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
16
16
|
var DEFAULT_STATUS_MAX_BYTES = 1 * 1024 * 1024;
|
|
17
17
|
var DEFAULT_MIN_AGE_MS = 6e4;
|
|
18
|
+
var DEDUP_MAX_KEYS = 2e4;
|
|
18
19
|
var STATUS_LINE_PREFIX = "kojee-status";
|
|
19
20
|
function statusLogPath(eventLogPath) {
|
|
20
21
|
const dir = path.dirname(eventLogPath);
|
|
@@ -49,6 +50,7 @@ function startEventLog(opts) {
|
|
|
49
50
|
const dir = opts.dir ?? DEFAULT_DIR;
|
|
50
51
|
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
51
52
|
const statusMaxBytes = opts.statusMaxBytes ?? DEFAULT_STATUS_MAX_BYTES;
|
|
53
|
+
const dedupMaxKeys = opts.dedupMaxKeys ?? DEDUP_MAX_KEYS;
|
|
52
54
|
fs.mkdirSync(dir, { recursive: true });
|
|
53
55
|
const filePath = resolveEventLogPath(opts);
|
|
54
56
|
const statusPath = statusLogPath(filePath);
|
|
@@ -57,6 +59,14 @@ function startEventLog(opts) {
|
|
|
57
59
|
fs.writeFileSync(statusPath, "", { mode: 384 });
|
|
58
60
|
secureFile(statusPath);
|
|
59
61
|
let writtenIds = /* @__PURE__ */ new Set();
|
|
62
|
+
function rememberDedupKey(key) {
|
|
63
|
+
writtenIds.add(key);
|
|
64
|
+
while (writtenIds.size > dedupMaxKeys) {
|
|
65
|
+
const oldest = writtenIds.values().next().value;
|
|
66
|
+
if (oldest === void 0) break;
|
|
67
|
+
writtenIds.delete(oldest);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
60
70
|
let bytesWritten = 0;
|
|
61
71
|
let linesWritten = 0;
|
|
62
72
|
let statusBytesWritten = 0;
|
|
@@ -90,7 +100,6 @@ function startEventLog(opts) {
|
|
|
90
100
|
await fs.promises.truncate(filePath, 0);
|
|
91
101
|
bytesWritten = 0;
|
|
92
102
|
linesWritten = 0;
|
|
93
|
-
writtenIds = /* @__PURE__ */ new Set();
|
|
94
103
|
void enqueueStatus(() => writeStatusRaw(`status=rotated dropped=${dropped}`)).catch(() => {
|
|
95
104
|
});
|
|
96
105
|
} catch (err) {
|
|
@@ -105,15 +114,16 @@ function startEventLog(opts) {
|
|
|
105
114
|
path: filePath,
|
|
106
115
|
statusPath,
|
|
107
116
|
async append(event) {
|
|
108
|
-
|
|
109
|
-
if (
|
|
117
|
+
const dedupKey = event.id ? event.edited_at ? `${event.id}\0${event.edited_at}` : event.id : void 0;
|
|
118
|
+
if (dedupKey && writtenIds.has(dedupKey)) return;
|
|
119
|
+
if (dedupKey) rememberDedupKey(dedupKey);
|
|
110
120
|
const line = formatLine(event);
|
|
111
121
|
await enqueueMsg(async () => {
|
|
112
122
|
try {
|
|
113
123
|
await writeMsgRaw(line);
|
|
114
124
|
} catch (err) {
|
|
115
125
|
console.error("[event-log] append failed:", err.message);
|
|
116
|
-
if (
|
|
126
|
+
if (dedupKey) writtenIds.delete(dedupKey);
|
|
117
127
|
}
|
|
118
128
|
});
|
|
119
129
|
},
|
package/dist/cli.js
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
} from "./chunk-EIAUW6KO.js";
|
|
5
5
|
import {
|
|
6
6
|
startProxy
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-676WQSWZ.js";
|
|
8
|
+
import "./chunk-CCVRXJJ2.js";
|
|
9
9
|
import {
|
|
10
10
|
pairedConfigPath
|
|
11
11
|
} from "./chunk-5SZHXYPK.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-AGZ7ZGLY.js";
|
|
13
13
|
import "./chunk-Z5LPNJQ6.js";
|
|
14
14
|
import "./chunk-I67C2HYA.js";
|
|
15
15
|
import "./chunk-MIEI4PLB.js";
|
|
@@ -18,14 +18,13 @@ import {
|
|
|
18
18
|
deriveKeystorePath
|
|
19
19
|
} from "./chunk-6G6YYST6.js";
|
|
20
20
|
import "./chunk-U5HHHRXA.js";
|
|
21
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-QYZP6QT2.js";
|
|
22
22
|
import {
|
|
23
23
|
VERSION
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-FJUAMJHU.js";
|
|
24
|
+
} from "./chunk-6NPMVZD7.js";
|
|
26
25
|
import "./chunk-PPTKGWFF.js";
|
|
27
26
|
import "./chunk-XJEBJIQE.js";
|
|
28
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-SZLNNMIZ.js";
|
|
29
28
|
|
|
30
29
|
// src/cli.ts
|
|
31
30
|
import { Command } from "commander";
|
|
@@ -48,7 +47,7 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
|
|
|
48
47
|
program.command("connect <code>").description(
|
|
49
48
|
"Connect this runtime to Kojee with a per-agent pair code from the dashboard (claude-code | codex | openclaw | hermes). claude-code/codex/openclaw write a per-runtime paired slot (~/.kojee/agents/<runtime>/config.json) and point the runtime launcher at it via --paired-config; hermes writes the global ~/.kojee/config.json (its daemon + `kojee-mcp send` read that). Idempotent."
|
|
50
49
|
).requiredOption("--runtime <id>", "Target runtime: claude-code | codex | openclaw | hermes").option("--url <url>", "Broker base URL (default: the canonical staging broker)").action(async (code, opts) => {
|
|
51
|
-
const { runConnect } = await import("./connect-handler-
|
|
50
|
+
const { runConnect } = await import("./connect-handler-GTFXNZUC.js");
|
|
52
51
|
const result = await runConnect({
|
|
53
52
|
code,
|
|
54
53
|
runtime: opts.runtime,
|
|
@@ -58,45 +57,59 @@ program.command("connect <code>").description(
|
|
|
58
57
|
console.error(result.output);
|
|
59
58
|
process.exit(result.exitCode);
|
|
60
59
|
});
|
|
60
|
+
program.command("reconnect").description(
|
|
61
|
+
"Re-run configure for an already-paired runtime, KEEPING the token + enrolled key (no re-pair). Re-writes configs/hooks to this install and refreshes the agent instructions."
|
|
62
|
+
).option("--runtime <id>", "Target runtime (defaults to the recorded runtime from the last connect/init)").action(async (opts) => {
|
|
63
|
+
const { runReconnect } = await import("./connect-handler-GTFXNZUC.js");
|
|
64
|
+
const result = await runReconnect({ ...opts.runtime ? { runtime: opts.runtime } : {} });
|
|
65
|
+
for (const m of result.messages) console.error(m);
|
|
66
|
+
console.error(result.output);
|
|
67
|
+
process.exit(result.exitCode);
|
|
68
|
+
});
|
|
61
69
|
program.command("hook").description("Run a kojee MCP hook script (called by Claude Code via ~/.claude/settings.json)").requiredOption(
|
|
62
70
|
"--type <type>",
|
|
63
|
-
"Hook type: stop, user-prompt-submit, codex-stop, or codex-prompt-submit"
|
|
71
|
+
"Hook type: stop, user-prompt-submit, session-start, codex-stop, or codex-prompt-submit"
|
|
64
72
|
).action(async (opts) => {
|
|
65
73
|
if (opts.type === "stop") {
|
|
66
|
-
const { runStopHook } = await import("./stop-hook-
|
|
74
|
+
const { runStopHook } = await import("./stop-hook-BLPYRE7T.js");
|
|
67
75
|
await runStopHook();
|
|
68
76
|
process.exit(0);
|
|
69
77
|
} else if (opts.type === "user-prompt-submit") {
|
|
70
|
-
const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-
|
|
78
|
+
const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-YQXGE3JU.js");
|
|
71
79
|
await runUserPromptSubmitHook();
|
|
72
80
|
process.exit(0);
|
|
81
|
+
} else if (opts.type === "session-start") {
|
|
82
|
+
const { runSessionStartHook } = await import("./session-start-hook-VL3KYDTT.js");
|
|
83
|
+
await runSessionStartHook();
|
|
84
|
+
process.exit(0);
|
|
73
85
|
} else if (opts.type === "codex-stop") {
|
|
74
|
-
const { runCodexStopHook } = await import("./codex-stop-hook-
|
|
86
|
+
const { runCodexStopHook } = await import("./codex-stop-hook-GTGRQNVV.js");
|
|
75
87
|
await runCodexStopHook();
|
|
76
88
|
process.exit(0);
|
|
77
89
|
} else if (opts.type === "codex-prompt-submit") {
|
|
78
|
-
const { runCodexPromptSubmitHook } = await import("./codex-prompt-submit-hook-
|
|
90
|
+
const { runCodexPromptSubmitHook } = await import("./codex-prompt-submit-hook-UQJI6A54.js");
|
|
79
91
|
await runCodexPromptSubmitHook();
|
|
80
92
|
process.exit(0);
|
|
81
93
|
} else {
|
|
82
94
|
console.error(
|
|
83
|
-
`Unknown hook type: ${opts.type}. Expected 'stop', 'user-prompt-submit', 'codex-stop', or 'codex-prompt-submit'.`
|
|
95
|
+
`Unknown hook type: ${opts.type}. Expected 'stop', 'user-prompt-submit', 'session-start', 'codex-stop', or 'codex-prompt-submit'.`
|
|
84
96
|
);
|
|
85
97
|
process.exit(1);
|
|
86
98
|
}
|
|
87
99
|
});
|
|
88
|
-
program.command("install-hooks").description("Install kojee Stop + UserPromptSubmit hooks in ~/.claude/settings.json (idempotent)").option("--hooks-path <path>", "Override default ~/.claude/settings.json").option("--uninstall", "Remove kojee hook entries instead of installing them").action(async (opts) => {
|
|
89
|
-
const { installHooks, uninstallHooks } = await import("./install-
|
|
100
|
+
program.command("install-hooks").description("Install kojee Stop + UserPromptSubmit + SessionStart hooks in ~/.claude/settings.json (idempotent)").option("--hooks-path <path>", "Override default ~/.claude/settings.json").option("--uninstall", "Remove kojee hook entries instead of installing them").action(async (opts) => {
|
|
101
|
+
const { installHooks, uninstallHooks } = await import("./install-GJVZEWLI.js");
|
|
90
102
|
if (opts.uninstall) {
|
|
91
103
|
const removed = uninstallHooks({ hooksPath: opts.hooksPath });
|
|
92
104
|
console.error(removed ? "Removed kojee hook entries." : "No kojee hook entries found.");
|
|
93
105
|
} else {
|
|
94
|
-
const { stop, ups } = installHooks({ hooksPath: opts.hooksPath });
|
|
106
|
+
const { stop, ups, sessionStart } = installHooks({ hooksPath: opts.hooksPath });
|
|
95
107
|
const label = (s) => s === "added" ? "\u2713 added" : s === "updated" ? "\u21BB updated" : "\u21BB already installed";
|
|
96
108
|
console.error(
|
|
97
109
|
"Installed kojee hooks in " + (opts.hooksPath ?? "~/.claude/settings.json") + `:
|
|
98
110
|
- Stop: ${label(stop)}
|
|
99
111
|
- UserPromptSubmit: ${label(ups)}
|
|
112
|
+
- SessionStart: ${label(sessionStart)}
|
|
100
113
|
Restart Claude Code for hooks to take effect.`
|
|
101
114
|
);
|
|
102
115
|
}
|
|
@@ -104,7 +117,7 @@ Restart Claude Code for hooks to take effect.`
|
|
|
104
117
|
program.command("send <tandem_id>").description(
|
|
105
118
|
"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)."
|
|
106
119
|
).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) => {
|
|
107
|
-
const { runSendCli } = await import("./send-cli-
|
|
120
|
+
const { runSendCli } = await import("./send-cli-WTY7H3BR.js");
|
|
108
121
|
const { exitCode, envelope } = await runSendCli({
|
|
109
122
|
tandemId,
|
|
110
123
|
body: opts.body,
|
|
@@ -114,17 +127,23 @@ program.command("send <tandem_id>").description(
|
|
|
114
127
|
process.stdout.write(JSON.stringify(envelope) + "\n");
|
|
115
128
|
process.exit(exitCode);
|
|
116
129
|
});
|
|
117
|
-
program.command("tail <path>").description("Stream a file's
|
|
118
|
-
|
|
130
|
+
program.command("tail <path>").description("Stream a file's appends and follow (portable `tail -F`; follows from EOF \u2014 new events only)").option(
|
|
131
|
+
"--wake-only",
|
|
132
|
+
"Suppress routine heartbeat warn/recover telemetry; surface only a sustained stream outage"
|
|
133
|
+
).option(
|
|
134
|
+
"--replay",
|
|
135
|
+
"Replay the whole file from the start before following (default: follow from EOF, no replay)"
|
|
136
|
+
).action(async (filePath, opts) => {
|
|
137
|
+
const { runTail } = await import("./tail-stream-PQXEY3TI.js");
|
|
119
138
|
try {
|
|
120
|
-
await runTail(filePath);
|
|
139
|
+
await runTail(filePath, { wakeOnly: opts.wakeOnly === true, replay: opts.replay === true });
|
|
121
140
|
} catch (err) {
|
|
122
141
|
console.error("[kojee-mcp tail] Error:", err.message);
|
|
123
142
|
process.exit(1);
|
|
124
143
|
}
|
|
125
144
|
});
|
|
126
145
|
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 () => {
|
|
127
|
-
const { runDoctor } = await import("./doctor-
|
|
146
|
+
const { runDoctor } = await import("./doctor-54S7JGOE.js");
|
|
128
147
|
const code = await runDoctor();
|
|
129
148
|
process.exit(code);
|
|
130
149
|
});
|
|
@@ -143,7 +162,7 @@ function addInstallOptions(cmd, runtimeHelp) {
|
|
|
143
162
|
function makeInstallAction(verb) {
|
|
144
163
|
return async (opts) => {
|
|
145
164
|
const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
|
|
146
|
-
const { runSetup, resolvePairCode } = await import("./setup-handler-
|
|
165
|
+
const { runSetup, resolvePairCode } = await import("./setup-handler-GNAIWAYU.js");
|
|
147
166
|
const pairCode = resolvePairCode(opts);
|
|
148
167
|
const result = await runSetup({
|
|
149
168
|
verb,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readHookStdin
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NIIBVKLP.js";
|
|
4
4
|
import {
|
|
5
5
|
listPending
|
|
6
6
|
} from "./chunk-IZN7IZPW.js";
|
|
7
7
|
import {
|
|
8
8
|
CODEX_WAKE_BELL
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6NPMVZD7.js";
|
|
10
10
|
|
|
11
11
|
// src/hooks/codex-prompt-submit-hook.ts
|
|
12
12
|
function buildCodexPromptSubmitOutput() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readHookStdin
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NIIBVKLP.js";
|
|
4
4
|
import {
|
|
5
5
|
drainedRoomsPath,
|
|
6
6
|
listPending,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-IZN7IZPW.js";
|
|
9
9
|
import {
|
|
10
10
|
CODEX_WAKE_BELL
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-6NPMVZD7.js";
|
|
12
12
|
|
|
13
13
|
// src/hooks/codex-stop-hook.ts
|
|
14
14
|
import fs from "fs";
|
|
@@ -2,16 +2,19 @@ import {
|
|
|
2
2
|
DEFAULT_BROKER_URL,
|
|
3
3
|
reconcileConnectPairSlot,
|
|
4
4
|
runWizard
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-N2YAZRAD.js";
|
|
6
|
+
import "./chunk-WDWVBPXA.js";
|
|
7
7
|
import "./chunk-E6WMFMM2.js";
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-TMCNB4JH.js";
|
|
10
|
-
import "./chunk-D6JKFJ6A.js";
|
|
8
|
+
import "./chunk-GEGUWQYT.js";
|
|
11
9
|
import {
|
|
12
10
|
CONNECT_RUNTIMES
|
|
13
11
|
} from "./chunk-EIH2LNF4.js";
|
|
14
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
readRecordedRuntime
|
|
14
|
+
} from "./chunk-RDLF4NQC.js";
|
|
15
|
+
import "./chunk-77HWBSRH.js";
|
|
16
|
+
import "./chunk-D6JKFJ6A.js";
|
|
17
|
+
import "./chunk-AXBJQJKA.js";
|
|
15
18
|
import "./chunk-SQL56SEB.js";
|
|
16
19
|
import "./chunk-V5VZPYMZ.js";
|
|
17
20
|
import {
|
|
@@ -29,10 +32,10 @@ import {
|
|
|
29
32
|
deriveKeystorePath
|
|
30
33
|
} from "./chunk-6G6YYST6.js";
|
|
31
34
|
import "./chunk-U5HHHRXA.js";
|
|
32
|
-
import "./chunk-
|
|
33
|
-
import "./chunk-FJUAMJHU.js";
|
|
35
|
+
import "./chunk-6NPMVZD7.js";
|
|
34
36
|
|
|
35
37
|
// src/wizard/connect-handler.ts
|
|
38
|
+
import fs from "fs";
|
|
36
39
|
import os from "os";
|
|
37
40
|
import path from "path";
|
|
38
41
|
function absConfigPath(runtime) {
|
|
@@ -139,7 +142,63 @@ Start (or restart) the hermes daemon to pick up the connection.`,
|
|
|
139
142
|
exitCode: 0
|
|
140
143
|
};
|
|
141
144
|
}
|
|
145
|
+
async function runReconnect(opts = {}) {
|
|
146
|
+
const messages = [];
|
|
147
|
+
const runtime = opts.runtime ?? readRecordedRuntime() ?? null;
|
|
148
|
+
if (!runtime) {
|
|
149
|
+
return {
|
|
150
|
+
messages,
|
|
151
|
+
output: `reconnect: no runtime recorded on this box \u2014 pass --runtime (${CONNECT_RUNTIMES.join(" | ")} | hermes), or run \`kojee-mcp connect <code> --runtime <id>\` first.`,
|
|
152
|
+
exitCode: 2
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const isHermes = runtime === "hermes";
|
|
156
|
+
if (!isHermes && !CONNECT_RUNTIMES.includes(runtime)) {
|
|
157
|
+
return {
|
|
158
|
+
messages,
|
|
159
|
+
output: `reconnect supports: ${CONNECT_RUNTIMES.join(", ")}, hermes. Unknown --runtime "${runtime}".`,
|
|
160
|
+
exitCode: 2
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const configPath = isHermes ? pairedConfigPath() : absConfigPath(runtime);
|
|
164
|
+
const paired = loadPairedConfig(configPath);
|
|
165
|
+
if (!paired?.token) {
|
|
166
|
+
return {
|
|
167
|
+
messages,
|
|
168
|
+
output: `reconnect: no paired config at ${configPath} \u2014 nothing to keep. Run \`kojee-mcp connect <code> --runtime ${runtime}\` to pair first.`,
|
|
169
|
+
exitCode: 2
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const keystorePath = isHermes ? defaultPairedKeystorePath() : deriveKeystorePath(paired.token);
|
|
173
|
+
if (!fs.existsSync(keystorePath)) {
|
|
174
|
+
messages.push(
|
|
175
|
+
`\u26A0 enrolled keystore MISSING at ${keystorePath} \u2014 configs will be refreshed, but the proxy cannot authenticate with a new key on this token (backend rejects double-enroll). If sends fail, re-pair with a fresh code.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const wizard = opts.runWizard ?? runWizard;
|
|
179
|
+
const wiz = await wizard({
|
|
180
|
+
runtime,
|
|
181
|
+
...isHermes ? {} : { pairedConfigPath: configPath },
|
|
182
|
+
interactive: false
|
|
183
|
+
});
|
|
184
|
+
if (wiz.exitCode !== 0) {
|
|
185
|
+
return { messages, output: wiz.output, exitCode: wiz.exitCode };
|
|
186
|
+
}
|
|
187
|
+
const label = identityLabel(paired);
|
|
188
|
+
return {
|
|
189
|
+
messages,
|
|
190
|
+
output: [
|
|
191
|
+
`\u21BB reconfigured ${runtime} as ${label} \u2014 token KEPT (no re-pair, no key re-enrollment).`,
|
|
192
|
+
"Configs + hooks re-written to this install; agent instructions refreshed below.",
|
|
193
|
+
`Restart ${runtime} to pick up the changes.`,
|
|
194
|
+
"",
|
|
195
|
+
wiz.output
|
|
196
|
+
].join("\n"),
|
|
197
|
+
exitCode: 0
|
|
198
|
+
};
|
|
199
|
+
}
|
|
142
200
|
export {
|
|
143
201
|
runConnect,
|
|
144
|
-
runConnectHermes
|
|
202
|
+
runConnectHermes,
|
|
203
|
+
runReconnect
|
|
145
204
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyHookCommand,
|
|
3
|
+
isKojeeHookCommand
|
|
4
|
+
} from "./chunk-AXBJQJKA.js";
|
|
1
5
|
import "./chunk-XLKGPGZT.js";
|
|
2
6
|
import {
|
|
3
7
|
loadControlToken
|
|
@@ -5,7 +9,7 @@ import {
|
|
|
5
9
|
import {
|
|
6
10
|
monitorHeartbeatPath,
|
|
7
11
|
statusLogPath
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WVQU3QQ7.js";
|
|
9
13
|
import {
|
|
10
14
|
discoveryPathForKey,
|
|
11
15
|
readSessionDiscoveryByKey,
|
|
@@ -18,14 +22,14 @@ import "./chunk-U5HHHRXA.js";
|
|
|
18
22
|
import {
|
|
19
23
|
buildMonitorSpawn,
|
|
20
24
|
buildReplyRecipe
|
|
21
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-6NPMVZD7.js";
|
|
22
26
|
import {
|
|
23
27
|
deriveDiscoveryKey,
|
|
24
28
|
findClaudeAncestorPid
|
|
25
29
|
} from "./chunk-XJEBJIQE.js";
|
|
26
30
|
import {
|
|
27
31
|
resolveSharedSessionId
|
|
28
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-SZLNNMIZ.js";
|
|
29
33
|
|
|
30
34
|
// src/doctor.ts
|
|
31
35
|
import fs from "fs";
|
|
@@ -149,6 +153,53 @@ async function resolveDefaultPairedScope() {
|
|
|
149
153
|
}
|
|
150
154
|
return { scope: resolution.scope, present, ...runtime ? { runtime } : {} };
|
|
151
155
|
}
|
|
156
|
+
function defaultReadHookCommands() {
|
|
157
|
+
try {
|
|
158
|
+
const p = path.join(os.homedir(), ".claude", "settings.json");
|
|
159
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
160
|
+
const cfg = JSON.parse(raw);
|
|
161
|
+
const out = [];
|
|
162
|
+
for (const event of ["Stop", "UserPromptSubmit"]) {
|
|
163
|
+
for (const matcher of cfg.hooks?.[event] ?? []) {
|
|
164
|
+
for (const h of matcher.hooks ?? []) {
|
|
165
|
+
if (typeof h.command === "string" && isKojeeHookCommand(h.command)) out.push(h.command);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
var HOOK_REPIN_FIX = "fix: `npm i -g kojee-mcp` then re-run `kojee-mcp setup` (re-pins in place, no re-pair)";
|
|
175
|
+
function hookPinCheck(commands) {
|
|
176
|
+
const name = "hook pin (settings.json Stop/UserPromptSubmit)";
|
|
177
|
+
if (commands === null) {
|
|
178
|
+
return { name, ok: "warn", detail: `no ~/.claude/settings.json readable \u2014 run \`kojee-mcp setup\` to install the wake hooks` };
|
|
179
|
+
}
|
|
180
|
+
if (commands.length === 0) {
|
|
181
|
+
return { name, ok: "warn", detail: `no kojee hooks installed \u2014 run \`kojee-mcp setup\` (the stop-hook is the Monitor self-heal)` };
|
|
182
|
+
}
|
|
183
|
+
const states = commands.map((c) => classifyHookCommand(c));
|
|
184
|
+
if (states.includes("pinned-broken")) {
|
|
185
|
+
return { name, ok: false, detail: `pinned cli.js is GONE \u2014 hooks are failing with exit 127 RIGHT NOW; ${HOOK_REPIN_FIX}` };
|
|
186
|
+
}
|
|
187
|
+
if (states.includes("pinned-ephemeral")) {
|
|
188
|
+
return {
|
|
189
|
+
name,
|
|
190
|
+
ok: false,
|
|
191
|
+
detail: `pinned INTO the npx cache (~/.npm/_npx/\u2026) \u2014 npx cache GC will silently kill the hooks (exit 127 every turn); ${HOOK_REPIN_FIX}`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (states.includes("npx")) {
|
|
195
|
+
return {
|
|
196
|
+
name,
|
|
197
|
+
ok: "warn",
|
|
198
|
+
detail: `unpinned (npx -y) \u2014 pays npx resolution every turn and can stall on a registry fetch; ${HOOK_REPIN_FIX}`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return { name, ok: true, detail: `pinned to a durable binary (${commands.length} hook${commands.length === 1 ? "" : "s"})` };
|
|
202
|
+
}
|
|
152
203
|
async function collectDoctorReport(deps = {}) {
|
|
153
204
|
const fetchFn = deps.fetchFn ?? fetch;
|
|
154
205
|
const isPidAlive = deps.isPidAlive ?? defaultIsPidAlive;
|
|
@@ -183,6 +234,8 @@ async function collectDoctorReport(deps = {}) {
|
|
|
183
234
|
detail: pairedScope.present ? "present (legacy global slot)" : "MISSING \u2014 run `kojee-mcp connect <code> --runtime <id>` (or `kojee-mcp pair`)"
|
|
184
235
|
});
|
|
185
236
|
}
|
|
237
|
+
const hookCommands = (deps.readHookCommands ?? defaultReadHookCommands)();
|
|
238
|
+
checks.push(hookPinCheck(hookCommands));
|
|
186
239
|
let logPath = null;
|
|
187
240
|
if (!discovery) {
|
|
188
241
|
checks.push({
|
|
@@ -354,17 +407,23 @@ function formatDoctorReport(report) {
|
|
|
354
407
|
async function runDoctor() {
|
|
355
408
|
const { readRecordedRuntime } = await import("./runtime-record-OXRLTOLC.js");
|
|
356
409
|
if (readRecordedRuntime() === "codex") {
|
|
357
|
-
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-
|
|
410
|
+
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-6H4OOS6O.js");
|
|
358
411
|
const report2 = collectCodexDoctorReport();
|
|
359
412
|
console.error(formatCodexDoctorReport(report2));
|
|
360
413
|
return report2.verdict === "broken" ? 1 : 0;
|
|
361
414
|
}
|
|
362
415
|
if (readRecordedRuntime() === "openclaw") {
|
|
363
|
-
const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-
|
|
416
|
+
const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-B5KADVHD.js");
|
|
364
417
|
const report2 = collectOpenclawDoctorReport();
|
|
365
418
|
console.error(formatOpenclawDoctorReport(report2));
|
|
366
419
|
return report2.verdict === "broken" ? 1 : 0;
|
|
367
420
|
}
|
|
421
|
+
const { hermesArtifactsPresent, collectHermesDoctorReport, formatHermesDoctorReport } = await import("./doctor-hermes-KMHM27Y3.js");
|
|
422
|
+
if (hermesArtifactsPresent()) {
|
|
423
|
+
const hermesReport = collectHermesDoctorReport();
|
|
424
|
+
console.error(formatHermesDoctorReport(hermesReport));
|
|
425
|
+
return hermesReport.verdict === "broken" ? 1 : 0;
|
|
426
|
+
}
|
|
368
427
|
const report = await collectDoctorReport();
|
|
369
428
|
console.error(formatDoctorReport(report));
|
|
370
429
|
return report.verdict === "broken" ? 1 : 0;
|