kojee-mcp 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-KPMD72FY.js → chunk-6XWTUDWW.js} +65 -26
- package/dist/{chunk-GNLCUJBK.js → chunk-A4IOKD4Z.js} +100 -2
- package/dist/{chunk-FBJCPRVH.js → chunk-ABECLEYE.js} +62 -11
- package/dist/{chunk-SSW5AQSR.js → chunk-FJUAMJHU.js} +3 -6
- package/dist/{chunk-FSKGQ6GT.js → chunk-HI42GBQ3.js} +5 -0
- package/dist/chunk-IZN7IZPW.js +132 -0
- package/dist/{chunk-UPJV7GBE.js → chunk-TCWIXG5C.js} +1 -1
- package/dist/{chunk-QJFMU4QC.js → chunk-TMCNB4JH.js} +1 -1
- package/dist/chunk-XLRF5ATG.js +103 -0
- package/dist/{chunk-UFHGZUST.js → chunk-XPIW4N55.js} +22 -14
- package/dist/cli.js +23 -14
- package/dist/codex-prompt-submit-hook-J5PZEJSK.js +39 -0
- package/dist/codex-stop-hook-32W4BIOM.js +136 -0
- package/dist/{connect-handler-4DRTFMOB.js → connect-handler-NZINEMG3.js} +14 -7
- package/dist/{doctor-5QJ3HGNR.js → doctor-WUU5BVPT.js} +2 -2
- package/dist/doctor-codex-PJFWIABF.js +370 -0
- package/dist/{event-stream-KRYWEYWO.js → event-stream-WPN3EN7C.js} +5 -1
- package/dist/index.js +5 -5
- package/dist/{install-V7LSQCYZ.js → install-JQNDGAAQ.js} +14 -2
- package/dist/lib.d.ts +40 -15
- package/dist/lib.js +7 -7
- package/dist/pending-state-6TVRR63P.js +134 -0
- package/dist/{registry-ZZZ26WGA.js → registry-2QS42EQF.js} +114 -51
- package/dist/{server-ITPFQVTK.js → server-DU3LFS32.js} +4 -2
- package/dist/{setup-handler-JFM45NCN.js → setup-handler-44ASXYMS.js} +7 -7
- package/dist/{stop-hook-5ABGTC2O.js → stop-hook-N6TX4YQT.js} +2 -2
- package/dist/{tail-stream-NBGHHBS4.js → tail-stream-N43D53RC.js} +99 -19
- package/package.json +1 -1
- package/skills/using-tandems/SKILL.md +2 -2
- package/dist/chunk-EBYUJM3H.js +0 -14
- package/dist/codex-stop-hook-BMOJVM6O.js +0 -96
- package/dist/doctor-codex-VGJKTX2E.js +0 -163
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
recordDrained
|
|
3
|
+
} from "./chunk-IZN7IZPW.js";
|
|
4
|
+
|
|
5
|
+
// src/tandem/pending-state.ts
|
|
6
|
+
var PENDING_TOOL_RUNTIMES = /* @__PURE__ */ new Set(["codex"]);
|
|
7
|
+
var LEDGER_DRAIN_RUNTIMES = /* @__PURE__ */ new Set(["codex"]);
|
|
8
|
+
var TANDEM_PENDING_TOOL_NAME = "tandem_pending";
|
|
9
|
+
var REJOIN_ADVICE = "if you were in rooms before a restart, rejoin with your previous seat_name \u2014 the room rebinds your seat";
|
|
10
|
+
var MAX_TRACKED_ROOMS = 256;
|
|
11
|
+
function usableCursor(cursor) {
|
|
12
|
+
if (!Number.isFinite(cursor) || cursor < 0) return null;
|
|
13
|
+
return Math.floor(cursor);
|
|
14
|
+
}
|
|
15
|
+
function createSessionPendingState() {
|
|
16
|
+
const rooms = /* @__PURE__ */ new Map();
|
|
17
|
+
let clock = 0;
|
|
18
|
+
const touch = (tandemId) => {
|
|
19
|
+
let rec = rooms.get(tandemId);
|
|
20
|
+
if (rec === void 0) {
|
|
21
|
+
rec = { seat: false, delivered: 0, drained: 0, touched: 0 };
|
|
22
|
+
rooms.set(tandemId, rec);
|
|
23
|
+
evictIfOverCap(tandemId);
|
|
24
|
+
}
|
|
25
|
+
rec.touched = ++clock;
|
|
26
|
+
return rec;
|
|
27
|
+
};
|
|
28
|
+
const evictIfOverCap = (justInserted) => {
|
|
29
|
+
while (rooms.size > MAX_TRACKED_ROOMS) {
|
|
30
|
+
let victim = null;
|
|
31
|
+
let victimTouched = Number.POSITIVE_INFINITY;
|
|
32
|
+
let victimPending = true;
|
|
33
|
+
for (const [id, rec] of rooms) {
|
|
34
|
+
if (id === justInserted) continue;
|
|
35
|
+
const isPending = rec.delivered > rec.drained;
|
|
36
|
+
const better = victimPending && !isPending || victimPending === isPending && rec.touched < victimTouched;
|
|
37
|
+
if (better) {
|
|
38
|
+
victim = id;
|
|
39
|
+
victimTouched = rec.touched;
|
|
40
|
+
victimPending = isPending;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (victim === null) return;
|
|
44
|
+
rooms.delete(victim);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
noteSeat(tandemId) {
|
|
49
|
+
if (tandemId) touch(tandemId).seat = true;
|
|
50
|
+
},
|
|
51
|
+
dropSeat(tandemId) {
|
|
52
|
+
rooms.delete(tandemId);
|
|
53
|
+
},
|
|
54
|
+
noteDelivered(tandemId, cursor) {
|
|
55
|
+
const next = usableCursor(cursor);
|
|
56
|
+
if (tandemId === "" || next === null) return;
|
|
57
|
+
const rec = touch(tandemId);
|
|
58
|
+
rec.delivered = Math.max(rec.delivered, next);
|
|
59
|
+
},
|
|
60
|
+
noteDrained(tandemId, cursor) {
|
|
61
|
+
const next = usableCursor(cursor);
|
|
62
|
+
if (tandemId === "" || next === null) return;
|
|
63
|
+
const rec = touch(tandemId);
|
|
64
|
+
rec.drained = Math.max(rec.drained, next);
|
|
65
|
+
},
|
|
66
|
+
snapshot(filterTandemId) {
|
|
67
|
+
const pending = [];
|
|
68
|
+
let sessionHasSeats = false;
|
|
69
|
+
for (const [tandemId, rec] of rooms) {
|
|
70
|
+
if (!rec.seat && rec.delivered === 0) continue;
|
|
71
|
+
sessionHasSeats = true;
|
|
72
|
+
if (filterTandemId !== void 0 && tandemId !== filterTandemId) continue;
|
|
73
|
+
if (rec.delivered > rec.drained) {
|
|
74
|
+
pending.push({
|
|
75
|
+
tandem_id: tandemId,
|
|
76
|
+
since_cursor: rec.drained,
|
|
77
|
+
delivered_cursor: rec.delivered
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const advice = !sessionHasSeats ? REJOIN_ADVICE : pending.length > 0 ? "drain each pending room with tandem_messages(tandem_id, since=since_cursor), then reply in the room" : "no pending Tandem events for this window";
|
|
82
|
+
return { pending, session_has_seats: sessionHasSeats, advice };
|
|
83
|
+
},
|
|
84
|
+
trackedRoomCount() {
|
|
85
|
+
return rooms.size;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function tandemPendingToolDefinition() {
|
|
90
|
+
return {
|
|
91
|
+
name: TANDEM_PENDING_TOOL_NAME,
|
|
92
|
+
description: "Report THIS window's pending Tandem events \u2014 answered entirely from this proxy's in-process state (rooms this session holds seats in, cursors delivered on this proxy's stream, cursors drained through this proxy's tool calls). Precise per-window: it never reads shared machine-wide files. Call it when the turn-end bell says events may be pending, then drain each listed room with tandem_messages(tandem_id, since=since_cursor) and reply in the room. If it lists nothing, the bell was for another window \u2014 ignore it. After a proxy restart it correctly reports nothing until you rejoin (use your previous seat_name \u2014 the room rebinds your seat).",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
tandem_id: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Optional: report only this room."
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function installPendingTool(runtime, registry) {
|
|
105
|
+
if (!PENDING_TOOL_RUNTIMES.has(runtime)) return null;
|
|
106
|
+
const state = createSessionPendingState();
|
|
107
|
+
registry.registerLocalTool(tandemPendingToolDefinition(), (args) => {
|
|
108
|
+
const filter = typeof args["tandem_id"] === "string" && args["tandem_id"] !== "" ? args["tandem_id"] : void 0;
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: JSON.stringify(state.snapshot(filter)) }],
|
|
111
|
+
isError: false
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
function createRuntimeDrainHook(runtime, state) {
|
|
117
|
+
const writesLedger = LEDGER_DRAIN_RUNTIMES.has(runtime);
|
|
118
|
+
if (!writesLedger && state === null) return void 0;
|
|
119
|
+
return (tandemId, cursor) => {
|
|
120
|
+
if (writesLedger) recordDrained(tandemId, cursor);
|
|
121
|
+
state?.noteDrained(tandemId, cursor);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
LEDGER_DRAIN_RUNTIMES,
|
|
126
|
+
MAX_TRACKED_ROOMS,
|
|
127
|
+
PENDING_TOOL_RUNTIMES,
|
|
128
|
+
REJOIN_ADVICE,
|
|
129
|
+
TANDEM_PENDING_TOOL_NAME,
|
|
130
|
+
createRuntimeDrainHook,
|
|
131
|
+
createSessionPendingState,
|
|
132
|
+
installPendingTool,
|
|
133
|
+
tandemPendingToolDefinition
|
|
134
|
+
};
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CODEX_STATUS_HEARTBEAT_MS,
|
|
3
|
+
sweepDeadCodexStatusFiles,
|
|
4
|
+
writeCodexStatus
|
|
5
|
+
} from "./chunk-XLRF5ATG.js";
|
|
1
6
|
import {
|
|
2
7
|
createCachedOpenclawWakeResolver,
|
|
3
8
|
postOpenclawWake
|
|
4
9
|
} from "./chunk-NE2F5CKS.js";
|
|
5
10
|
import "./chunk-PHXO5P25.js";
|
|
6
11
|
import {
|
|
7
|
-
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
recordDelivered
|
|
13
|
+
} from "./chunk-IZN7IZPW.js";
|
|
9
14
|
import {
|
|
10
15
|
claudeCodeAdapter
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import
|
|
14
|
-
|
|
16
|
+
} from "./chunk-TCWIXG5C.js";
|
|
17
|
+
import "./chunk-A4IOKD4Z.js";
|
|
18
|
+
import {
|
|
19
|
+
VERSION
|
|
20
|
+
} from "./chunk-5DHIUN73.js";
|
|
21
|
+
import "./chunk-FJUAMJHU.js";
|
|
15
22
|
import "./chunk-PPTKGWFF.js";
|
|
16
23
|
|
|
17
24
|
// src/delivery/lib/fanout.ts
|
|
@@ -42,16 +49,16 @@ async function deliverEvent(event, sinks) {
|
|
|
42
49
|
} catch (err) {
|
|
43
50
|
console.error("[event-stream] webhook enqueue failed:", err);
|
|
44
51
|
}
|
|
45
|
-
if (sinks.
|
|
52
|
+
if (sinks.pendingLedger) {
|
|
46
53
|
try {
|
|
47
|
-
const pending = sinks.
|
|
54
|
+
const pending = sinks.pendingLedger(event);
|
|
48
55
|
if (pending && typeof pending.then === "function") {
|
|
49
56
|
pending.catch((err) => {
|
|
50
|
-
console.error("[event-stream]
|
|
57
|
+
console.error("[event-stream] pending-ledger record failed:", err);
|
|
51
58
|
});
|
|
52
59
|
}
|
|
53
60
|
} catch (err) {
|
|
54
|
-
console.error("[event-stream]
|
|
61
|
+
console.error("[event-stream] pending-ledger record failed:", err);
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
}
|
|
@@ -119,8 +126,8 @@ function createClaudeCodeDelivery() {
|
|
|
119
126
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
120
127
|
const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
|
|
121
128
|
const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
|
|
122
|
-
const { startEventStream } = await import("./event-stream-
|
|
123
|
-
const { createMcpServer } = await import("./server-
|
|
129
|
+
const { startEventStream } = await import("./event-stream-WPN3EN7C.js");
|
|
130
|
+
const { createMcpServer } = await import("./server-DU3LFS32.js");
|
|
124
131
|
const { deriveDiscoveryKey } = await import("./ancestry-A2F5KQ6A.js");
|
|
125
132
|
const { resolveSharedSessionId } = await import("./cc-session-id-RURNIHHC.js");
|
|
126
133
|
sweepStaleDiscovery();
|
|
@@ -282,6 +289,9 @@ function createClaudeCodeDelivery() {
|
|
|
282
289
|
// claude-code, so it is always the channel-capable path.
|
|
283
290
|
formatChannelNotification,
|
|
284
291
|
...webhookSink ? { webhookSink } : {}
|
|
292
|
+
// No pendingLedger sink: claude-code's event log is already
|
|
293
|
+
// room-qualified (tandem=<id> cursor=<n> per line) and its stop-hook
|
|
294
|
+
// /poll feed is per-session — the general ledger adds nothing here.
|
|
285
295
|
});
|
|
286
296
|
},
|
|
287
297
|
async stop() {
|
|
@@ -302,7 +312,7 @@ function createClaudeCodeDelivery() {
|
|
|
302
312
|
}
|
|
303
313
|
|
|
304
314
|
// src/delivery/hermes.ts
|
|
305
|
-
function createWebhookDelivery(name,
|
|
315
|
+
function createWebhookDelivery(name, pendingLedger) {
|
|
306
316
|
let eventLog = null;
|
|
307
317
|
let webhookSink = null;
|
|
308
318
|
let streamHandle = null;
|
|
@@ -313,8 +323,8 @@ function createWebhookDelivery(name, codexPendingMarker) {
|
|
|
313
323
|
const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
|
|
314
324
|
const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
|
|
315
325
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
316
|
-
const { startEventStream } = await import("./event-stream-
|
|
317
|
-
const { createMcpServer } = await import("./server-
|
|
326
|
+
const { startEventStream } = await import("./event-stream-WPN3EN7C.js");
|
|
327
|
+
const { createMcpServer } = await import("./server-DU3LFS32.js");
|
|
318
328
|
sweepStaleEventLogs();
|
|
319
329
|
eventLog = startEventLog({
|
|
320
330
|
key: ctx.instanceKey,
|
|
@@ -384,8 +394,8 @@ function createWebhookDelivery(name, codexPendingMarker) {
|
|
|
384
394
|
await deliverEvent(event, {
|
|
385
395
|
...eventLog ? { eventLog } : {},
|
|
386
396
|
...webhookSink ? { webhookSink } : {},
|
|
387
|
-
// OPTIONAL
|
|
388
|
-
...
|
|
397
|
+
// OPTIONAL pending-ledger sink (undefined ⇒ byte-identical hermes path).
|
|
398
|
+
...pendingLedger ? { pendingLedger } : {}
|
|
389
399
|
});
|
|
390
400
|
},
|
|
391
401
|
async stop() {
|
|
@@ -402,39 +412,92 @@ function createWebhookDelivery(name, codexPendingMarker) {
|
|
|
402
412
|
};
|
|
403
413
|
}
|
|
404
414
|
|
|
405
|
-
// src/delivery/codex
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
ensuredDir = dir;
|
|
415
|
+
// src/delivery/codex.ts
|
|
416
|
+
var MAX_TANDEMS_SEEN = 32;
|
|
417
|
+
function createCodexDelivery(onDelivered) {
|
|
418
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
419
|
+
let deliveredEvents = 0;
|
|
420
|
+
let lastLedgerWriteAt = null;
|
|
421
|
+
let lastEventAt = null;
|
|
422
|
+
let subscribedTandemCount = -1;
|
|
423
|
+
const tandemsSeen = /* @__PURE__ */ new Set();
|
|
424
|
+
let heartbeat = null;
|
|
425
|
+
const ledgerSink = (event) => {
|
|
426
|
+
recordDelivered(event.tandem_id, event.cursor);
|
|
427
|
+
if (typeof event.tandem_id === "string" && event.tandem_id !== "" && Number.isFinite(event.cursor) && event.cursor >= 1) {
|
|
428
|
+
lastLedgerWriteAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
420
429
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
} catch {
|
|
427
|
-
ensuredDir = null;
|
|
428
|
-
try {
|
|
429
|
-
await fsp.unlink(tmp);
|
|
430
|
-
} catch {
|
|
430
|
+
if (onDelivered) {
|
|
431
|
+
try {
|
|
432
|
+
onDelivered(event);
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
431
435
|
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
436
|
+
};
|
|
437
|
+
const inner = createWebhookDelivery("codex", ledgerSink);
|
|
438
|
+
const innerDeliver = inner.deliver.bind(inner);
|
|
439
|
+
const deliverWithStatus = async (event) => {
|
|
440
|
+
await innerDeliver(event);
|
|
441
|
+
deliveredEvents += 1;
|
|
442
|
+
lastEventAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
443
|
+
const tid = typeof event.tandem_id === "string" ? event.tandem_id : "";
|
|
444
|
+
if (tid && tandemsSeen.size < MAX_TANDEMS_SEEN) tandemsSeen.add(tid);
|
|
445
|
+
refreshStatus();
|
|
446
|
+
};
|
|
447
|
+
inner.deliver = deliverWithStatus;
|
|
448
|
+
const streamStatus = () => {
|
|
449
|
+
const health = inner.health?.();
|
|
450
|
+
const stream = health?.detail?.stream;
|
|
451
|
+
const iso = (v) => typeof v === "number" && Number.isFinite(v) ? new Date(v).toISOString() : null;
|
|
452
|
+
return {
|
|
453
|
+
connected: health ? health.connected : null,
|
|
454
|
+
connected_since: iso(stream?.["connectedSince"]),
|
|
455
|
+
last_event_at: iso(stream?.["lastEventAt"]) ?? lastEventAt,
|
|
456
|
+
reconnects: typeof stream?.["reconnectCount"] === "number" ? stream["reconnectCount"] : null
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
const snapshot = () => ({
|
|
460
|
+
pid: process.pid,
|
|
461
|
+
proxy_version: VERSION,
|
|
462
|
+
started_at: startedAt,
|
|
463
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
464
|
+
stream: streamStatus(),
|
|
465
|
+
delivered_events: deliveredEvents,
|
|
466
|
+
last_marker_write_at: lastLedgerWriteAt,
|
|
467
|
+
subscribed_tandem_count: subscribedTandemCount,
|
|
468
|
+
tandems_seen: [...tandemsSeen]
|
|
469
|
+
});
|
|
470
|
+
const refreshStatus = () => {
|
|
471
|
+
void writeCodexStatus(snapshot()).catch(() => {
|
|
472
|
+
});
|
|
473
|
+
};
|
|
474
|
+
return {
|
|
475
|
+
name: inner.name,
|
|
476
|
+
async start(ctx) {
|
|
477
|
+
const started = await inner.start(ctx);
|
|
478
|
+
subscribedTandemCount = ctx.tandemMembershipCount;
|
|
479
|
+
void sweepDeadCodexStatusFiles().catch(() => {
|
|
480
|
+
});
|
|
481
|
+
refreshStatus();
|
|
482
|
+
heartbeat = setInterval(refreshStatus, CODEX_STATUS_HEARTBEAT_MS);
|
|
483
|
+
heartbeat.unref?.();
|
|
484
|
+
started.teardown.push(() => {
|
|
485
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
486
|
+
heartbeat = null;
|
|
487
|
+
});
|
|
488
|
+
return started;
|
|
489
|
+
},
|
|
490
|
+
deliver: deliverWithStatus,
|
|
491
|
+
async stop() {
|
|
492
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
493
|
+
heartbeat = null;
|
|
494
|
+
await inner.stop();
|
|
495
|
+
refreshStatus();
|
|
496
|
+
},
|
|
497
|
+
health() {
|
|
498
|
+
return inner.health?.() ?? { connected: null };
|
|
499
|
+
}
|
|
500
|
+
};
|
|
438
501
|
}
|
|
439
502
|
|
|
440
503
|
// src/delivery/openclaw.ts
|
|
@@ -454,8 +517,8 @@ function createOpenclawDelivery(deps = {}) {
|
|
|
454
517
|
async start(ctx) {
|
|
455
518
|
const { startEventLog, sweepStaleEventLogs } = await import("./event-log-2NBJEIEP.js");
|
|
456
519
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
457
|
-
const { startEventStream } = await import("./event-stream-
|
|
458
|
-
const { createMcpServer } = await import("./server-
|
|
520
|
+
const { startEventStream } = await import("./event-stream-WPN3EN7C.js");
|
|
521
|
+
const { createMcpServer } = await import("./server-DU3LFS32.js");
|
|
459
522
|
sweepStaleEventLogs();
|
|
460
523
|
eventLog = startEventLog({
|
|
461
524
|
key: ctx.instanceKey,
|
|
@@ -549,7 +612,7 @@ function selectDelivery(runtime, opts) {
|
|
|
549
612
|
return createClaudeCodeDelivery();
|
|
550
613
|
}
|
|
551
614
|
if (runtime === "codex") {
|
|
552
|
-
return createCodexDelivery();
|
|
615
|
+
return createCodexDelivery(opts.onDelivered);
|
|
553
616
|
}
|
|
554
617
|
if (runtime === "openclaw") {
|
|
555
618
|
return createOpenclawDelivery();
|
|
@@ -3,15 +3,17 @@ import {
|
|
|
3
3
|
buildNonChannelInstructions,
|
|
4
4
|
createMcpServer,
|
|
5
5
|
executeToolCall,
|
|
6
|
+
extractDrainCursor,
|
|
6
7
|
startMcpServer
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-A4IOKD4Z.js";
|
|
8
9
|
import "./chunk-5DHIUN73.js";
|
|
9
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-FJUAMJHU.js";
|
|
10
11
|
import "./chunk-PPTKGWFF.js";
|
|
11
12
|
export {
|
|
12
13
|
buildChannelInstructions,
|
|
13
14
|
buildNonChannelInstructions,
|
|
14
15
|
createMcpServer,
|
|
15
16
|
executeToolCall,
|
|
17
|
+
extractDrainCursor,
|
|
16
18
|
startMcpServer
|
|
17
19
|
};
|
|
@@ -3,17 +3,17 @@ import {
|
|
|
3
3
|
pairSlotFor,
|
|
4
4
|
reconcileConnectPairSlot,
|
|
5
5
|
runWizard
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XPIW4N55.js";
|
|
7
|
+
import "./chunk-6XWTUDWW.js";
|
|
7
8
|
import "./chunk-E6WMFMM2.js";
|
|
8
|
-
import
|
|
9
|
-
|
|
9
|
+
import {
|
|
10
|
+
isWizardRuntime
|
|
11
|
+
} from "./chunk-77HWBSRH.js";
|
|
12
|
+
import "./chunk-TMCNB4JH.js";
|
|
10
13
|
import "./chunk-D6JKFJ6A.js";
|
|
11
14
|
import "./chunk-EIH2LNF4.js";
|
|
12
15
|
import "./chunk-RDLF4NQC.js";
|
|
13
16
|
import "./chunk-SQL56SEB.js";
|
|
14
|
-
import {
|
|
15
|
-
isWizardRuntime
|
|
16
|
-
} from "./chunk-77HWBSRH.js";
|
|
17
17
|
import "./chunk-V5VZPYMZ.js";
|
|
18
18
|
import {
|
|
19
19
|
runPair
|
|
@@ -26,7 +26,7 @@ import "./chunk-MIEI4PLB.js";
|
|
|
26
26
|
import "./chunk-6G6YYST6.js";
|
|
27
27
|
import "./chunk-U5HHHRXA.js";
|
|
28
28
|
import "./chunk-5DHIUN73.js";
|
|
29
|
-
import "./chunk-
|
|
29
|
+
import "./chunk-FJUAMJHU.js";
|
|
30
30
|
|
|
31
31
|
// src/wizard/setup-handler.ts
|
|
32
32
|
var SETUP_SUPPORTED_RUNTIMES = ["claude-code", "codex"];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import "./chunk-XLKGPGZT.js";
|
|
1
2
|
import {
|
|
2
3
|
resolveHookDiscovery
|
|
3
4
|
} from "./chunk-WBXU27BF.js";
|
|
4
5
|
import {
|
|
5
6
|
readHookStdin
|
|
6
7
|
} from "./chunk-LSUB6QMP.js";
|
|
7
|
-
import "./chunk-XLKGPGZT.js";
|
|
8
8
|
import {
|
|
9
9
|
controlTokenAuthHeaders
|
|
10
10
|
} from "./chunk-GKXHKR3B.js";
|
|
@@ -19,7 +19,7 @@ import "./chunk-67F67AQ6.js";
|
|
|
19
19
|
import "./chunk-U5HHHRXA.js";
|
|
20
20
|
import {
|
|
21
21
|
buildMonitorNudge
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-FJUAMJHU.js";
|
|
23
23
|
import "./chunk-XJEBJIQE.js";
|
|
24
24
|
import "./chunk-KNEJTD6G.js";
|
|
25
25
|
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
} from "./chunk-TNZITJAB.js";
|
|
7
7
|
import "./chunk-67F67AQ6.js";
|
|
8
8
|
import {
|
|
9
|
+
LOG_HEARTBEAT_INTERVAL_MS,
|
|
10
|
+
UNARMED_FALLBACK_MS,
|
|
9
11
|
createAdaptiveWatchdog
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HI42GBQ3.js";
|
|
11
13
|
import "./chunk-Z5LPNJQ6.js";
|
|
12
14
|
import "./chunk-MIEI4PLB.js";
|
|
13
15
|
import "./chunk-U5HHHRXA.js";
|
|
@@ -19,17 +21,87 @@ var HEARTBEAT_FIELDS_RE = /(^|\s)status=heartbeat(\s|$)/;
|
|
|
19
21
|
var ROTATED_FIELDS_RE = /(^|\s)status=rotated(\s|$)/;
|
|
20
22
|
var ROTATED_DROPPED_RE = /(^|\s)dropped=(\d+)(\s|$)/;
|
|
21
23
|
var MESSAGE_CURSOR_RE = /(^|\s)cursor=(\S+)(\s|$)/;
|
|
22
|
-
var
|
|
24
|
+
var TAIL_ABS_FLOOR_MS = 5 * 6e4;
|
|
25
|
+
var TAIL_FLOOR_SLACK_MS = 3e4;
|
|
26
|
+
var TAIL_STALE_FLOOR_MS = envInt(
|
|
27
|
+
"KOJEE_TAIL_STALE_MS",
|
|
28
|
+
Math.max(TAIL_ABS_FLOOR_MS, 2 * LOG_HEARTBEAT_INTERVAL_MS + TAIL_FLOOR_SLACK_MS)
|
|
29
|
+
);
|
|
23
30
|
var STALE_CHECK_INTERVAL_MS = envInt("KOJEE_TAIL_CHECK_MS", 5e3);
|
|
31
|
+
var TAIL_REWARN_AFTER_MS = 30 * 6e4;
|
|
32
|
+
var TAIL_HEALTHY_RESET_MS = 30 * 6e4;
|
|
33
|
+
var TAIL_BACKOFF_CAP_CYCLES = 3;
|
|
24
34
|
function envInt(name, fallback) {
|
|
25
35
|
const v = Number.parseInt(process.env[name] ?? "", 10);
|
|
26
36
|
return Number.isFinite(v) && v > 0 ? v : fallback;
|
|
27
37
|
}
|
|
38
|
+
function createStallDetector(startNow, options = {}) {
|
|
39
|
+
const heartbeatIntervalMs = options.heartbeatIntervalMs ?? LOG_HEARTBEAT_INTERVAL_MS;
|
|
40
|
+
const floorMs = options.floorMs ?? Math.max(TAIL_ABS_FLOOR_MS, 2 * heartbeatIntervalMs + TAIL_FLOOR_SLACK_MS);
|
|
41
|
+
const rewarnAfterMs = options.rewarnAfterMs ?? TAIL_REWARN_AFTER_MS;
|
|
42
|
+
const healthyResetMs = options.healthyResetMs ?? TAIL_HEALTHY_RESET_MS;
|
|
43
|
+
const backoffCapCycles = options.backoffCapCycles ?? TAIL_BACKOFF_CAP_CYCLES;
|
|
44
|
+
const watchdog = createAdaptiveWatchdog({
|
|
45
|
+
floorMs,
|
|
46
|
+
multiplier: options.multiplier,
|
|
47
|
+
unarmedFallbackMs: options.unarmedFallbackMs
|
|
48
|
+
});
|
|
49
|
+
let lastLifeAt = startNow;
|
|
50
|
+
let stale = false;
|
|
51
|
+
let warnedAt = 0;
|
|
52
|
+
let rewarned = false;
|
|
53
|
+
let backoffCycles = 0;
|
|
54
|
+
let lastRecoveredAt = null;
|
|
55
|
+
let outageSpanned = false;
|
|
56
|
+
return {
|
|
57
|
+
onLife(now) {
|
|
58
|
+
const silentMs = now - lastLifeAt;
|
|
59
|
+
lastLifeAt = now;
|
|
60
|
+
if (stale) {
|
|
61
|
+
stale = false;
|
|
62
|
+
rewarned = false;
|
|
63
|
+
backoffCycles = Math.min(backoffCycles + 1, backoffCapCycles);
|
|
64
|
+
lastRecoveredAt = now;
|
|
65
|
+
return { kind: "recovered", silentMs };
|
|
66
|
+
}
|
|
67
|
+
if (backoffCycles > 0 && lastRecoveredAt !== null && now - lastRecoveredAt >= healthyResetMs) {
|
|
68
|
+
backoffCycles = 0;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
},
|
|
72
|
+
onHeartbeat(now) {
|
|
73
|
+
if (outageSpanned) {
|
|
74
|
+
watchdog.resetCadenceAnchor();
|
|
75
|
+
outageSpanned = false;
|
|
76
|
+
}
|
|
77
|
+
watchdog.onHeartbeat(now);
|
|
78
|
+
},
|
|
79
|
+
check(now) {
|
|
80
|
+
if (stale) {
|
|
81
|
+
if (!rewarned && now - warnedAt >= rewarnAfterMs) {
|
|
82
|
+
rewarned = true;
|
|
83
|
+
return { kind: "rewarn", silentMs: now - lastLifeAt };
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const base = watchdog.armedThresholdMs() ?? (options.unarmedFallbackMs ?? UNARMED_FALLBACK_MS);
|
|
88
|
+
const thresholdMs = Math.min(
|
|
89
|
+
base * 2 ** backoffCycles,
|
|
90
|
+
Math.max(floorMs, rewarnAfterMs)
|
|
91
|
+
);
|
|
92
|
+
const silentMs = now - lastLifeAt;
|
|
93
|
+
if (silentMs < thresholdMs) return null;
|
|
94
|
+
stale = true;
|
|
95
|
+
warnedAt = now;
|
|
96
|
+
rewarned = false;
|
|
97
|
+
outageSpanned = true;
|
|
98
|
+
return { kind: "warn", silentMs, thresholdMs };
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
28
102
|
async function runTail(messagesPath) {
|
|
29
103
|
const statusPath = statusLogPath(messagesPath);
|
|
30
|
-
|
|
31
|
-
const watchdog = createAdaptiveWatchdog({ floorMs: HEARTBEAT_STALE_FLOOR_MS });
|
|
32
|
-
let staleAnnounced = false;
|
|
104
|
+
const detector = createStallDetector(Date.now(), { floorMs: TAIL_STALE_FLOOR_MS });
|
|
33
105
|
const heartbeatPath = monitorHeartbeatPath(messagesPath);
|
|
34
106
|
const touchHeartbeat = () => {
|
|
35
107
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -44,10 +116,10 @@ async function runTail(messagesPath) {
|
|
|
44
116
|
};
|
|
45
117
|
let lastCursor = null;
|
|
46
118
|
function onMessageLine(line) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
119
|
+
if (detector.onLife(Date.now())?.kind === "recovered") {
|
|
120
|
+
process.stdout.write(
|
|
121
|
+
"[kojee] stream recovered \u2014 events flowing again (recovery is automatic; no action needed).\n"
|
|
122
|
+
);
|
|
51
123
|
}
|
|
52
124
|
const cm = MESSAGE_CURSOR_RE.exec(line);
|
|
53
125
|
if (cm && cm[2]) lastCursor = cm[2];
|
|
@@ -59,12 +131,12 @@ async function runTail(messagesPath) {
|
|
|
59
131
|
const m = STATUS_LINE_RE.exec(line);
|
|
60
132
|
const fields = m ? m[1] ?? "" : "";
|
|
61
133
|
if (HEARTBEAT_FIELDS_RE.test(fields)) {
|
|
62
|
-
|
|
134
|
+
detector.onHeartbeat(now);
|
|
63
135
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
136
|
+
if (detector.onLife(now)?.kind === "recovered") {
|
|
137
|
+
process.stdout.write(
|
|
138
|
+
"[kojee] stream recovered \u2014 heartbeats resumed (the stall was transient, likely idle-period timing or brief host load; no action needed).\n"
|
|
139
|
+
);
|
|
68
140
|
}
|
|
69
141
|
if (statusSeeded && ROTATED_FIELDS_RE.test(fields)) {
|
|
70
142
|
const dm = ROTATED_DROPPED_RE.exec(fields);
|
|
@@ -88,13 +160,20 @@ async function runTail(messagesPath) {
|
|
|
88
160
|
]).catch(() => {
|
|
89
161
|
});
|
|
90
162
|
const staleTimer = setInterval(() => {
|
|
91
|
-
if (staleAnnounced) return;
|
|
92
163
|
const now = Date.now();
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
164
|
+
const signal = detector.check(now);
|
|
165
|
+
if (!signal) return;
|
|
166
|
+
const silentS = Math.round(signal.silentMs / 1e3);
|
|
167
|
+
if (signal.kind === "rewarn") {
|
|
168
|
+
process.stdout.write(
|
|
169
|
+
`[kojee] WARNING: still no events or heartbeat after ${silentS}s \u2014 the earlier stall never recovered, so the stream may be dead; run \`npx kojee-mcp doctor\`.
|
|
170
|
+
`
|
|
171
|
+
);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const thresholdS = Math.round((signal.thresholdMs ?? 0) / 1e3);
|
|
96
175
|
process.stdout.write(
|
|
97
|
-
`[kojee] WARNING: no events or heartbeat for ${
|
|
176
|
+
`[kojee] WARNING: no events or heartbeat for ${silentS}s (threshold ${thresholdS}s) \u2014 stream may be dead, but this is often benign (idle stream, or brief host load delaying heartbeats) and recovery is automatic; if it persists, run \`npx kojee-mcp doctor\`.
|
|
98
177
|
`
|
|
99
178
|
);
|
|
100
179
|
}, STALE_CHECK_INTERVAL_MS);
|
|
@@ -198,6 +277,7 @@ function sleep(ms) {
|
|
|
198
277
|
return new Promise((r) => setTimeout(r, ms));
|
|
199
278
|
}
|
|
200
279
|
export {
|
|
280
|
+
createStallDetector,
|
|
201
281
|
makeFollower,
|
|
202
282
|
runTail
|
|
203
283
|
};
|
package/package.json
CHANGED
|
@@ -23,9 +23,9 @@ not your every sentence. When the hats tension, the principal wins.
|
|
|
23
23
|
- **Sync before you speak.** Catch up to the latest cursor; a gap means you missed messages — fetch them. Never answer from a stale view.
|
|
24
24
|
- **Answer in the channel you were approached in.** Approached in the **room** (the Tandem)? Answer in the room. Approached in your **session** (your principal's direct channel — including gate / review surfaces)? Answer in the session. It does **not** matter whether it's a 1:1 or a group chat — match the inbound channel; never migrate a conversation to a channel it didn't start in. Use `tandem_send` only for what originates in or belongs to the room, not to echo your session replies.
|
|
25
25
|
- **Close every loop.** Taking an item, done (with evidence), or blocked (name the blocker + who clears it) — say so. Silence is a bug on a team.
|
|
26
|
-
- **Plan your wake path per task, re-plan as it changes.** Heads-down → mentions + a heartbeat floor; waiting on one reply → listen/filter to that seat; co-working → wake on all; standby → mentions + hourly heartbeat. Move your filter deliberately, and tell the room your posture.
|
|
26
|
+
- **Plan your wake path per task, re-plan as it changes.** Heads-down → mentions + a heartbeat floor; waiting on one reply → listen/filter to that seat; co-working → wake on all; standby → mentions + hourly heartbeat. Move your filter deliberately, and tell the room your posture when it changes — a plain re-join/reconnect with the same posture needs no check-in message.
|
|
27
27
|
- **Don't assume others use the room well — engineer around it.** Poll for gaps, confirm receipt when it matters, resend a mention that didn't land, never block forever on a peer who may be dark.
|
|
28
|
-
- **Signal cheap, wake rarely.** Acknowledge with a non-waking
|
|
28
|
+
- **Signal cheap, wake rarely.** Acknowledge with a reaction or ack (non-waking); reserve a message for what *changes someone's next move*. Cheap signals are reactions/acks — never `kind=status` messages (status is lifecycle-only, system-generated for joined/left events; never send it yourself).
|
|
29
29
|
- **Take your lane.** Don't duplicate, collide, or redo a teammate's work; settle unclear ownership in one message first.
|
|
30
30
|
- **Be a reliable, considerate presence.** Recover yourself from a dropped session — don't make others restart you — and pace your load on the shared account.
|
|
31
31
|
|
package/dist/chunk-EBYUJM3H.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// src/hooks/codex-pending-path.ts
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
function codexPendingMarkerPath() {
|
|
5
|
-
return path.join(os.homedir(), ".kojee", "codex-pending");
|
|
6
|
-
}
|
|
7
|
-
function codexPendingAckPath() {
|
|
8
|
-
return codexPendingMarkerPath() + ".ack";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export {
|
|
12
|
-
codexPendingMarkerPath,
|
|
13
|
-
codexPendingAckPath
|
|
14
|
-
};
|