kojee-mcp 0.5.10 → 0.5.12

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 (35) hide show
  1. package/README.md +24 -0
  2. package/dist/ancestry-ONFBQEP5.js +8 -0
  3. package/dist/{codex-stop-hook-SWA53ECG.js → chunk-35XBRG3V.js} +4 -3
  4. package/dist/{chunk-MKDMAAMN.js → chunk-5XP2UOFK.js} +12 -0
  5. package/dist/{chunk-DS26OORG.js → chunk-CO73VGWM.js} +41 -23
  6. package/dist/chunk-FQZCENSG.js +459 -0
  7. package/dist/{chunk-YKS6YZKM.js → chunk-PHXO5P25.js} +1 -4
  8. package/dist/chunk-WLMPCX7T.js +116 -0
  9. package/dist/chunk-XLKGPGZT.js +0 -0
  10. package/dist/chunk-XXFVWP6H.js +44 -0
  11. package/dist/{ensure-join-7AEDJMPE.js → chunk-YKW54DKF.js} +45 -15
  12. package/dist/cli.js +16 -13
  13. package/dist/codex-stop-hook-VY7DOMAG.js +16 -0
  14. package/dist/{doctor-XK335W7B.js → doctor-FVTALRQD.js} +110 -15
  15. package/dist/ensure-join-5Y5IJ7HN.js +8 -0
  16. package/dist/{event-log-B27VVEMK.js → event-log-VZD7NKYX.js} +1 -1
  17. package/dist/event-stream-FOT7MJZH.js +19 -0
  18. package/dist/{gateway-client-93P1E0CZ.d.ts → gateway-client-C6yx1mfM.d.ts} +6 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +7 -5
  21. package/dist/lib.d.ts +181 -3
  22. package/dist/lib.js +7 -7
  23. package/dist/{parent-watchdog-RZLHYP7T.js → parent-watchdog-TLU355FB.js} +1 -1
  24. package/dist/registry-A3VT6VJD.js +348 -0
  25. package/dist/server-77QRWKJM.js +14 -0
  26. package/dist/{stop-hook-OTCJGL6V.js → stop-hook-CUVDKXP7.js} +8 -7
  27. package/dist/{tail-stream-JNR4WFW3.js → tail-stream-VZ462ZON.js} +3 -2
  28. package/dist/{user-prompt-submit-hook-QXMC7EZU.js → user-prompt-submit-hook-PMBUPKUV.js} +6 -6
  29. package/dist/{webhook-sink-NWGCUDGY.js → webhook-sink-N6AUTFL3.js} +1 -1
  30. package/package.json +1 -1
  31. package/dist/chunk-SCDWPGH3.js +0 -637
  32. package/dist/{chunk-BJMASMKX.js → chunk-VHKPWUX7.js} +0 -0
  33. package/dist/{doctor-codex-SMROUYGV.js → doctor-codex-PA3WO6LR.js} +1 -1
  34. package/dist/{send-cli-CN5EX7PO.js → send-cli-NZP5XE7T.js} +5 -5
  35. package/dist/{wizard-PLGHYCT3.js → wizard-L4MYRLJI.js} +11 -11
package/dist/lib.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { KeyLike, JWK } from 'jose';
2
- import { L as LoadedKeyPair, K as KeystoreData, G as GatewayClient } from './gateway-client-93P1E0CZ.js';
3
- export { P as ProxyConfig, T as ToolCallResult } from './gateway-client-93P1E0CZ.js';
2
+ import { L as LoadedKeyPair, K as KeystoreData, G as GatewayClient, M as McpToolDefinition, T as ToolCallResult, P as ProxyConfig } from './gateway-client-C6yx1mfM.js';
4
3
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
4
 
6
5
  /**
@@ -271,6 +270,19 @@ interface EventStreamOptions {
271
270
  * webhook delivery, zero behavior change.
272
271
  */
273
272
  webhookSink?: WebhookSink;
273
+ /**
274
+ * Pluggable per-runtime WAKE DELIVERY (#32 Phase 1). When wired, consumeSse
275
+ * routes each normalized, wake-filtered TandemEvent through `delivery.deliver`
276
+ * INSTEAD OF the inline queue→event-log→channel→webhook fan-out below. The
277
+ * delivery owns the exact same per-sink isolation + ordering; this is purely
278
+ * WHERE the fan-out lives, not WHAT it does. When UNSET (the kojee-mcp/lib
279
+ * consumers — the OpenClaw plugin — and the event-stream unit/integration
280
+ * tests that pass `queue`/`eventLog`/`webhookSink` directly), the historical
281
+ * inline fan-out runs unchanged. The two paths are behaviorally identical.
282
+ */
283
+ delivery?: {
284
+ deliver(event: TandemEvent): Promise<void>;
285
+ };
274
286
  /**
275
287
  * Resubscribe-on-start hook (P0 audit item #2). Called once after each
276
288
  * successful (re)connect with the live `Response`. Implementations touch
@@ -426,4 +438,170 @@ interface ResubscribeOptions {
426
438
  */
427
439
  declare function resubscribeMemberships(opts: ResubscribeOptions): Promise<number>;
428
440
 
429
- export { AuthModule, type EventLog, type EventStreamOptions, GatewayClient, type HarnessAdapter, type PairedConfig, type Runtime, type StreamHandle, type StreamState, type TandemEvent, type WebhookSink, createDPoPProof, defaultPairedKeystorePath, deriveKeystorePath, generateES256KeyPair, loadKeystore, loadPairedConfig, normalizeBackendEvent, pairedConfigPath, resubscribeMemberships, sanitizeDisplayname, saveKeystore, startEventStream };
441
+ /**
442
+ * Flat tool registry — fetches all Kojee tools with full schemas on startup
443
+ * and registers them directly with the MCP server. No discovery indirection,
444
+ * no meta-tools, no call_tool wrapper.
445
+ *
446
+ * The agent sees each Kojee tool directly (e.g. gmail_send_email, github_list_repos)
447
+ * and calls it like any native MCP tool. Prompt caching handles the token cost
448
+ * of ~15K tokens after the first turn.
449
+ */
450
+ declare class ToolRegistry {
451
+ private readonly gateway;
452
+ /** Flat map: tool name → full tool definition */
453
+ private tools;
454
+ constructor(gateway: GatewayClient);
455
+ /**
456
+ * Fetch all tools with full schemas from the gateway in a single RPC call.
457
+ */
458
+ discoverTools(): Promise<void>;
459
+ /**
460
+ * Return all registered tools for the MCP ListTools response.
461
+ */
462
+ getAllTools(): McpToolDefinition[];
463
+ /**
464
+ * Call a tool through the gateway.
465
+ */
466
+ callTool(name: string, args: Record<string, unknown>): Promise<ToolCallResult>;
467
+ /** Total number of registered tools. */
468
+ get toolCount(): number;
469
+ }
470
+
471
+ /**
472
+ * Side-channel hooks observed by the tool-call path (0.5.4 wake continuity).
473
+ */
474
+ interface ToolCallHooks {
475
+ /**
476
+ * Fired after a SUCCESSFUL tandem_join performed through this proxy by its
477
+ * agent, with the joined tandem id (null if it couldn't be read from args).
478
+ * The proxy wires this to the debounced stream-reconnect scheduler so the
479
+ * event-stream's membership snapshot picks up the just-acquired seat without
480
+ * a daemon restart, AND (#28) records the room in this instance's local
481
+ * room-memory for session-faithful re-seat. A throwing hook never breaks the
482
+ * tool reply.
483
+ */
484
+ onTandemJoin?: (tandemId: string | null) => void;
485
+ /**
486
+ * Fired after a SUCCESSFUL tandem_leave through this proxy, with the left
487
+ * tandem id (#28). The proxy drops the room from this instance's local
488
+ * room-memory so a restart doesn't rejoin a room the agent deliberately
489
+ * left. A throwing hook never breaks the tool reply.
490
+ */
491
+ onTandemLeave?: (tandemId: string | null) => void;
492
+ }
493
+
494
+ /**
495
+ * The pluggable per-runtime WAKE-DELIVERY interface (#32 Phase 1).
496
+ *
497
+ * Historically the dispatch of a normalized TandemEvent to its wake sinks lived
498
+ * in two places: the THREE-WAY branch in `startProxy` (src/index.ts) that wired
499
+ * the event-log / webhook / hook-server / discovery surfaces per runtime, and
500
+ * the inline fan-out in `consumeSse` (src/tandem/event-stream.ts) that, per
501
+ * event, appended to the event log (Monitor wake), emitted a channel
502
+ * notification (claude-code only), and enqueued the webhook POST.
503
+ *
504
+ * Phase 1 extracts that into one interface so each runtime owns its own wake
505
+ * delivery as a composable unit, selected from a registry instead of an
506
+ * if/else-if/else ladder. The extraction is BEHAVIOR-PRESERVING: each runtime's
507
+ * `start`/`deliver`/`stop` replicate exactly what its branch did before.
508
+ *
509
+ * - start(ctx) — build + start this runtime's sinks/surfaces and the SSE
510
+ * stream. Returns the wired MCP `server` (so the proxy can
511
+ * drive the stdio transport) plus a stop handle.
512
+ * - deliver(event) — the per-event fan-out, formerly inlined in consumeSse
513
+ * (event-stream.ts:594-642). MUST preserve per-sink
514
+ * isolation + ordering: event-log append (primary Monitor
515
+ * wake) FIRST, channel notification SECOND (claude-code
516
+ * only), webhook enqueue LAST + fire-and-forget. Each sink
517
+ * is wrapped in its own try/catch so one failing sink can
518
+ * never starve another.
519
+ * - stop() — idempotent teardown (webhook sink stop, discovery
520
+ * cleanup, event-log cleanup, hook-server stop).
521
+ * - health() — optional liveness snapshot for GET /status + doctor.
522
+ */
523
+
524
+ /**
525
+ * Everything a {@link WakeDelivery.start} needs from the proxy to build its
526
+ * sinks/surfaces and wire the SSE stream. Assembled once in `startProxy` after
527
+ * auth + ensure-join, before the stream connects.
528
+ */
529
+ interface WakeDeliveryContext {
530
+ /**
531
+ * This instance's stable key (#28): the CC session_id when readable, else
532
+ * KOJEE_INSTANCE / a working-dir hash. Used to derive the per-runtime
533
+ * event-log filename and other per-session artifacts.
534
+ */
535
+ instanceKey: string;
536
+ /** The detected runtime label (drives capability + which delivery is built). */
537
+ runtime: Runtime;
538
+ /** The selected harness adapter (channel capability + formatTandemEvent). */
539
+ adapter: HarnessAdapter;
540
+ /** Directory the event log lives in (defaults to os.tmpdir()). */
541
+ eventLogDir?: string;
542
+ /** Resolved proxy config (broker url + token + auth mode). */
543
+ config: ProxyConfig;
544
+ /** The discovered tool registry (used to build the MCP server). */
545
+ registry: ToolRegistry;
546
+ /** The authenticated gateway JSON-RPC client. */
547
+ gateway: GatewayClient;
548
+ /** The Claude Code ancestor pid, or null if none was found. */
549
+ ccPid: number | null;
550
+ /** Boot-probed membership count for the handshake instructions (-1 = unknown). */
551
+ tandemMembershipCount: number;
552
+ /** tandem_id lister for the resubscribe-on-connect touch. */
553
+ listTandemIds: () => Promise<string[] | null>;
554
+ /** join/leave side-channel hooks (stream reconnect + room-memory record). */
555
+ toolCallHooks: ToolCallHooks;
556
+ /** Called once the stream handle is armed, to flush a boot-race reconnect. */
557
+ onStreamReady?: (handle: StreamLike) => void;
558
+ /** Best-effort line sink for boot/observability logs (status sibling backed). */
559
+ log: (line: string) => void;
560
+ }
561
+ /** The subset of the SSE stream handle the proxy + scheduler depend on. */
562
+ interface StreamLike {
563
+ (): void;
564
+ reconnect: () => void;
565
+ getState: () => unknown;
566
+ }
567
+ /** Optional liveness snapshot surfaced via GET /status + doctor. */
568
+ interface DeliveryHealth {
569
+ /** Whether the SSE stream is currently connected (null = no stream). */
570
+ connected: boolean | null;
571
+ /** Implementation-specific extra state (stream state, hook-server, etc.). */
572
+ detail?: Record<string, unknown>;
573
+ }
574
+ /** The result of {@link WakeDelivery.start}: the wired server + teardown. */
575
+ interface StartedDelivery {
576
+ /** The MCP server to drive over stdio. */
577
+ server: Server;
578
+ /** Synchronous teardown steps to register with the shutdown coordinator. */
579
+ teardown: Array<() => void | Promise<void>>;
580
+ /** Last-ditch synchronous cleanup for process 'exit' (cannot await). */
581
+ exitCleanup?: () => void;
582
+ }
583
+ /**
584
+ * A per-runtime wake-delivery unit. Built from the registry, started once, and
585
+ * driven per-event by consumeSse via {@link WakeDelivery.deliver}.
586
+ */
587
+ interface WakeDelivery {
588
+ /** Stable identifier (the runtime label + variant, e.g. "claude-code"). */
589
+ readonly name: string;
590
+ /**
591
+ * Build + start this delivery's sinks/surfaces and the SSE stream. The stream
592
+ * is wired so consumeSse calls back into {@link WakeDelivery.deliver}.
593
+ */
594
+ start(ctx: WakeDeliveryContext): Promise<StartedDelivery>;
595
+ /**
596
+ * Per-event fan-out (the seam formerly inlined in consumeSse). Called for
597
+ * every normalized, wake-filtered TandemEvent. MUST preserve per-sink
598
+ * isolation + ordering and never throw.
599
+ */
600
+ deliver(event: TandemEvent): Promise<void>;
601
+ /** Idempotent teardown. */
602
+ stop(): Promise<void>;
603
+ /** Optional liveness snapshot. */
604
+ health?(): DeliveryHealth;
605
+ }
606
+
607
+ export { AuthModule, type DeliveryHealth, type EventLog, type EventStreamOptions, GatewayClient, type HarnessAdapter, type PairedConfig, ProxyConfig, type Runtime, type StartedDelivery, type StreamHandle, type StreamLike, type StreamState, type TandemEvent, ToolCallResult, type WakeDelivery, type WakeDeliveryContext, type WebhookSink, createDPoPProof, defaultPairedKeystorePath, deriveKeystorePath, generateES256KeyPair, loadKeystore, loadPairedConfig, normalizeBackendEvent, pairedConfigPath, resubscribeMemberships, sanitizeDisplayname, saveKeystore, startEventStream };
package/dist/lib.js CHANGED
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  resubscribeMemberships
3
3
  } from "./chunk-OT2GILXC.js";
4
+ import {
5
+ normalizeBackendEvent,
6
+ sanitizeDisplayname,
7
+ startEventStream
8
+ } from "./chunk-5XP2UOFK.js";
4
9
  import {
5
10
  loadPairedConfig,
6
11
  pairedConfigPath
7
12
  } from "./chunk-YH27B6SW.js";
8
- import {
9
- AuthModule
10
- } from "./chunk-JXMVZEQ7.js";
11
13
  import {
12
14
  GatewayClient
13
15
  } from "./chunk-HSR3GXCL.js";
14
16
  import {
15
- normalizeBackendEvent,
16
- sanitizeDisplayname,
17
- startEventStream
18
- } from "./chunk-MKDMAAMN.js";
17
+ AuthModule
18
+ } from "./chunk-JXMVZEQ7.js";
19
19
  import {
20
20
  createDPoPProof
21
21
  } from "./chunk-2MIISF2W.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  findClaudeAncestorPid
3
- } from "./chunk-BJMASMKX.js";
3
+ } from "./chunk-VHKPWUX7.js";
4
4
 
5
5
  // src/runtime/parent-watchdog.ts
6
6
  function defaultIsPidAlive(pid) {
@@ -0,0 +1,348 @@
1
+ import "./chunk-35XBRG3V.js";
2
+ import "./chunk-PHXO5P25.js";
3
+ import "./chunk-LSUB6QMP.js";
4
+ import {
5
+ claudeCodeAdapter
6
+ } from "./chunk-XXFVWP6H.js";
7
+ import "./chunk-WLMPCX7T.js";
8
+ import "./chunk-X672ZN7V.js";
9
+ import "./chunk-LDZXU3DW.js";
10
+
11
+ // src/delivery/lib/fanout.ts
12
+ async function deliverEvent(event, sinks) {
13
+ sinks.queue?.push(event);
14
+ if (sinks.eventLog) {
15
+ try {
16
+ await sinks.eventLog.append(event);
17
+ sinks.queue?.markMonitorDelivered(event.id);
18
+ } catch (err) {
19
+ console.error("[event-stream] event-log append failed:", err);
20
+ }
21
+ }
22
+ if (sinks.formatChannelNotification && sinks.server) {
23
+ try {
24
+ const channel = sinks.formatChannelNotification(event);
25
+ await sinks.server.notification({
26
+ method: "notifications/claude/channel",
27
+ params: channel
28
+ });
29
+ sinks.queue?.markChannelDelivered(event.id);
30
+ } catch (err) {
31
+ console.error("[event-stream] channel notification failed:", err);
32
+ }
33
+ }
34
+ try {
35
+ sinks.webhookSink?.enqueue(event);
36
+ } catch (err) {
37
+ console.error("[event-stream] webhook enqueue failed:", err);
38
+ }
39
+ }
40
+
41
+ // src/delivery/lib/channel.ts
42
+ function formatChannelNotification(event) {
43
+ return claudeCodeAdapter.formatTandemEvent(event);
44
+ }
45
+
46
+ // src/delivery/claude-code.ts
47
+ function createClaudeCodeDelivery() {
48
+ let eventLog = null;
49
+ let webhookSink = null;
50
+ let queue = null;
51
+ let server = null;
52
+ let hookServer = null;
53
+ let cleanupDiscoveryFile = null;
54
+ let streamHandle = null;
55
+ return {
56
+ name: "claude-code",
57
+ async start(ctx) {
58
+ const { EventQueue } = await import("./event-queue-5YVJFR3E.js");
59
+ const { startHookServer } = await import("./hook-server-37E2LUKJ.js");
60
+ const {
61
+ writeDiscoveryByKey,
62
+ cleanupDiscoveryByKey,
63
+ sweepStaleDiscovery
64
+ } = await import("./session-discovery-FNMJGFPM.js");
65
+ const { startEventLog, sweepStaleEventLogs } = await import("./event-log-VZD7NKYX.js");
66
+ const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
67
+ const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
68
+ const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
69
+ const { startEventStream } = await import("./event-stream-FOT7MJZH.js");
70
+ const { createMcpServer } = await import("./server-77QRWKJM.js");
71
+ const { deriveDiscoveryKey } = await import("./ancestry-ONFBQEP5.js");
72
+ sweepStaleDiscovery();
73
+ sweepStaleEventLogs();
74
+ const projectDir = process.env["CLAUDE_PROJECT_DIR"];
75
+ const discoveryKey = deriveDiscoveryKey(projectDir, ctx.ccPid);
76
+ eventLog = startEventLog({
77
+ key: ctx.instanceKey,
78
+ ...ctx.eventLogDir ? { dir: ctx.eventLogDir } : {}
79
+ });
80
+ const log = eventLog;
81
+ const webhookResolution = resolveWebhookConfig();
82
+ if (webhookResolution.error) {
83
+ console.error(`[kojee-mcp] webhook sink ERROR: ${webhookResolution.error}`);
84
+ void log.appendStatus(`status=webhook error="${webhookResolution.error}"`).catch(() => {
85
+ });
86
+ }
87
+ if (webhookResolution.warning) {
88
+ console.error(`[kojee-mcp] webhook sink WARNING: ${webhookResolution.warning}`);
89
+ void log.appendStatus(`status=webhook warning="${webhookResolution.warning}"`).catch(() => {
90
+ });
91
+ }
92
+ webhookSink = webhookResolution.enabled && webhookResolution.config ? createWebhookSink(webhookResolution.config, {
93
+ log: (line) => {
94
+ void log.appendStatus(`status=webhook ${line}`).catch(() => {
95
+ });
96
+ }
97
+ }) : null;
98
+ if (webhookSink) {
99
+ console.error(`[kojee-mcp] webhook sink ENABLED (${webhookSink.configSummary()})`);
100
+ void log.appendStatus(`status=webhook enabled ${webhookSink.configSummary()}`).catch(() => {
101
+ });
102
+ }
103
+ server = createMcpServer(
104
+ ctx.registry,
105
+ ctx.adapter,
106
+ ctx.tandemMembershipCount,
107
+ eventLog.path,
108
+ ctx.toolCallHooks
109
+ );
110
+ const { issueControlToken, controlTokenPath } = await import("./control-token-4BUCTYQB.js");
111
+ let controlToken = null;
112
+ try {
113
+ controlToken = issueControlToken();
114
+ } catch (err) {
115
+ console.error(
116
+ "[kojee-mcp] control token write failed \u2014 POST /send disabled; GET /poll and /status left UNGATED (degrade open):",
117
+ err.message
118
+ );
119
+ }
120
+ queue = new EventQueue();
121
+ const localQueue = queue;
122
+ let liveStream = null;
123
+ hookServer = await startHookServer({
124
+ port: 0,
125
+ queue: localQueue,
126
+ adapter: ctx.adapter,
127
+ ...controlToken !== null ? { controlToken, send: { gateway: ctx.gateway, authToken: controlToken } } : {},
128
+ getStreamState: () => liveStream ? liveStream.getState() : {
129
+ connected: false,
130
+ connectedSince: null,
131
+ lastEventAt: null,
132
+ lastHeartbeatAt: null,
133
+ cursors: {},
134
+ reconnectCount: 0,
135
+ staleAfterMs: null
136
+ }
137
+ });
138
+ writeDiscoveryByKey(discoveryKey, {
139
+ schema: 2,
140
+ discoveryKey,
141
+ ccPid: ctx.ccPid,
142
+ projectDir: projectDir ?? null,
143
+ proxyPid: process.pid,
144
+ pid: process.pid,
145
+ port: hookServer.port,
146
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
147
+ brokerUrl: ctx.config.url,
148
+ eventLogPath: eventLog.path,
149
+ ...controlToken !== null ? { controlTokenPath: controlTokenPath() } : {},
150
+ authMode: ctx.config.authMode ?? "paired"
151
+ });
152
+ cleanupDiscoveryFile = () => cleanupDiscoveryByKey(discoveryKey);
153
+ const localCleanupDiscovery = cleanupDiscoveryFile;
154
+ const localHookServer = hookServer;
155
+ const exitCleanup = () => {
156
+ localCleanupDiscovery();
157
+ eventLog?.cleanup();
158
+ };
159
+ const teardown = [
160
+ () => {
161
+ void webhookSink?.stop();
162
+ },
163
+ () => localCleanupDiscovery(),
164
+ () => eventLog?.cleanup(),
165
+ () => localHookServer.stop()
166
+ ];
167
+ streamHandle = await startEventStream({
168
+ brokerUrl: ctx.config.url,
169
+ token: ctx.config.token,
170
+ gateway: ctx.gateway,
171
+ adapter: ctx.adapter,
172
+ server,
173
+ // eventLog is passed so the stream's status/heartbeat lines land in the
174
+ // status sibling (emitStatus / heartbeat use opts.eventLog.appendStatus).
175
+ // The PER-EVENT fan-out (queue/eventLog/channel/webhook) is owned by
176
+ // `delivery.deliver` below, so queue/webhookSink are NOT re-wired here —
177
+ // the queue still feeds the hook-server /poll directly (startHookServer).
178
+ eventLog,
179
+ delivery: this,
180
+ onConnected: /* @__PURE__ */ (() => {
181
+ const debounceState = { lastRunAt: 0 };
182
+ return async () => {
183
+ await resubscribeMemberships({
184
+ gateway: ctx.gateway,
185
+ eventLog,
186
+ listTandems: async () => await ctx.listTandemIds() ?? [],
187
+ debounceState
188
+ });
189
+ };
190
+ })()
191
+ });
192
+ liveStream = streamHandle;
193
+ ctx.onStreamReady?.(streamHandle);
194
+ return { server, teardown, exitCleanup };
195
+ },
196
+ async deliver(event) {
197
+ await deliverEvent(event, {
198
+ ...queue ? { queue } : {},
199
+ ...eventLog ? { eventLog } : {},
200
+ ...server ? { server } : {},
201
+ // Channel notification is gated by providing the formatter (claude-code
202
+ // supportsChannels=true). formatChannelNotification throws by contract
203
+ // for other runtimes, but this delivery is only ever built for
204
+ // claude-code, so it is always the channel-capable path.
205
+ formatChannelNotification,
206
+ ...webhookSink ? { webhookSink } : {}
207
+ });
208
+ },
209
+ async stop() {
210
+ await webhookSink?.stop();
211
+ cleanupDiscoveryFile?.();
212
+ eventLog?.cleanup();
213
+ await hookServer?.stop();
214
+ },
215
+ health() {
216
+ const state = streamHandle?.getState();
217
+ return {
218
+ connected: state ? state.connected ?? null : null,
219
+ ...state ? { detail: { stream: state } } : {}
220
+ };
221
+ }
222
+ };
223
+ }
224
+
225
+ // src/delivery/hermes.ts
226
+ function createWebhookDelivery(name) {
227
+ let eventLog = null;
228
+ let webhookSink = null;
229
+ let streamHandle = null;
230
+ return {
231
+ name,
232
+ async start(ctx) {
233
+ const { startEventLog, sweepStaleEventLogs } = await import("./event-log-VZD7NKYX.js");
234
+ const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
235
+ const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
236
+ const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
237
+ const { startEventStream } = await import("./event-stream-FOT7MJZH.js");
238
+ const { createMcpServer } = await import("./server-77QRWKJM.js");
239
+ sweepStaleEventLogs();
240
+ eventLog = startEventLog({
241
+ key: ctx.instanceKey,
242
+ ...ctx.eventLogDir ? { dir: ctx.eventLogDir } : {}
243
+ });
244
+ const log = eventLog;
245
+ const webhookResolution = resolveWebhookConfig();
246
+ if (webhookResolution.error) {
247
+ console.error(`[kojee-mcp] webhook sink ERROR: ${webhookResolution.error}`);
248
+ void log.appendStatus(`status=webhook error="${webhookResolution.error}"`).catch(() => {
249
+ });
250
+ }
251
+ if (webhookResolution.warning) {
252
+ console.error(`[kojee-mcp] webhook sink WARNING: ${webhookResolution.warning}`);
253
+ void log.appendStatus(`status=webhook warning="${webhookResolution.warning}"`).catch(() => {
254
+ });
255
+ }
256
+ webhookSink = webhookResolution.enabled && webhookResolution.config ? createWebhookSink(webhookResolution.config, {
257
+ log: (line) => {
258
+ void log.appendStatus(`status=webhook ${line}`).catch(() => {
259
+ });
260
+ }
261
+ }) : null;
262
+ if (webhookSink) {
263
+ console.error(`[kojee-mcp] webhook sink ENABLED (${webhookSink.configSummary()})`);
264
+ void log.appendStatus(`status=webhook enabled ${webhookSink.configSummary()}`).catch(() => {
265
+ });
266
+ }
267
+ const server = createMcpServer(
268
+ ctx.registry,
269
+ ctx.adapter,
270
+ ctx.tandemMembershipCount,
271
+ void 0,
272
+ ctx.toolCallHooks
273
+ );
274
+ const exitCleanup = () => eventLog?.cleanup();
275
+ const teardown = [
276
+ () => {
277
+ void webhookSink?.stop();
278
+ },
279
+ () => eventLog?.cleanup()
280
+ ];
281
+ streamHandle = await startEventStream({
282
+ brokerUrl: ctx.config.url,
283
+ token: ctx.config.token,
284
+ gateway: ctx.gateway,
285
+ adapter: ctx.adapter,
286
+ server,
287
+ eventLog,
288
+ delivery: this,
289
+ onConnected: /* @__PURE__ */ (() => {
290
+ const debounceState = { lastRunAt: 0 };
291
+ return async () => {
292
+ await resubscribeMemberships({
293
+ gateway: ctx.gateway,
294
+ eventLog,
295
+ listTandems: async () => await ctx.listTandemIds() ?? [],
296
+ debounceState
297
+ });
298
+ };
299
+ })()
300
+ });
301
+ ctx.onStreamReady?.(streamHandle);
302
+ return { server, teardown, exitCleanup };
303
+ },
304
+ async deliver(event) {
305
+ await deliverEvent(event, {
306
+ ...eventLog ? { eventLog } : {},
307
+ ...webhookSink ? { webhookSink } : {}
308
+ });
309
+ },
310
+ async stop() {
311
+ await webhookSink?.stop();
312
+ eventLog?.cleanup();
313
+ },
314
+ health() {
315
+ const state = streamHandle?.getState();
316
+ return {
317
+ connected: state ? state.connected ?? null : null,
318
+ ...state ? { detail: { stream: state } } : {}
319
+ };
320
+ }
321
+ };
322
+ }
323
+
324
+ // src/delivery/codex.ts
325
+ function createCodexDelivery() {
326
+ return createWebhookDelivery("codex");
327
+ }
328
+
329
+ // src/delivery/registry.ts
330
+ function needsWebhookEventStream(env = process.env) {
331
+ return (env["KOJEE_WEBHOOK_URL"] ?? "").trim().length > 0;
332
+ }
333
+ function selectDelivery(runtime, opts) {
334
+ if (opts.supportsChannels) {
335
+ return createClaudeCodeDelivery();
336
+ }
337
+ if (!needsWebhookEventStream(opts.env)) {
338
+ return null;
339
+ }
340
+ if (runtime === "codex") {
341
+ return createCodexDelivery();
342
+ }
343
+ return createWebhookDelivery(runtime);
344
+ }
345
+ export {
346
+ needsWebhookEventStream,
347
+ selectDelivery
348
+ };
@@ -0,0 +1,14 @@
1
+ import {
2
+ buildChannelInstructions,
3
+ createMcpServer,
4
+ executeToolCall,
5
+ startMcpServer
6
+ } from "./chunk-WLMPCX7T.js";
7
+ import "./chunk-X672ZN7V.js";
8
+ import "./chunk-LDZXU3DW.js";
9
+ export {
10
+ buildChannelInstructions,
11
+ createMcpServer,
12
+ executeToolCall,
13
+ startMcpServer
14
+ };
@@ -1,27 +1,28 @@
1
+ import "./chunk-XLKGPGZT.js";
2
+ import {
3
+ controlTokenAuthHeaders
4
+ } from "./chunk-GI2CKKBL.js";
1
5
  import {
2
6
  formatChannelEvents
3
- } from "./chunk-YKS6YZKM.js";
7
+ } from "./chunk-PHXO5P25.js";
4
8
  import {
5
9
  readHookStdin
6
10
  } from "./chunk-LSUB6QMP.js";
7
11
  import {
8
12
  monitorHeartbeatPath,
9
13
  nudgeSentinelPath
10
- } from "./chunk-DS26OORG.js";
14
+ } from "./chunk-CO73VGWM.js";
11
15
  import {
12
16
  readSessionDiscoveryByKey
13
17
  } from "./chunk-DO42NPNR.js";
14
- import {
15
- controlTokenAuthHeaders
16
- } from "./chunk-GI2CKKBL.js";
18
+ import "./chunk-BLEGIR35.js";
17
19
  import {
18
20
  buildMonitorNudge
19
21
  } from "./chunk-X672ZN7V.js";
20
22
  import {
21
23
  deriveDiscoveryKey,
22
24
  findClaudeAncestorPid
23
- } from "./chunk-BJMASMKX.js";
24
- import "./chunk-BLEGIR35.js";
25
+ } from "./chunk-VHKPWUX7.js";
25
26
 
26
27
  // src/hooks/stop-hook.ts
27
28
  import fs from "fs";
@@ -1,12 +1,13 @@
1
+ import "./chunk-XLKGPGZT.js";
1
2
  import {
2
3
  STATUS_LINE_PREFIX,
3
4
  monitorHeartbeatPath,
4
5
  statusLogPath
5
- } from "./chunk-DS26OORG.js";
6
+ } from "./chunk-CO73VGWM.js";
6
7
  import "./chunk-DO42NPNR.js";
7
8
  import {
8
9
  createAdaptiveWatchdog
9
- } from "./chunk-MKDMAAMN.js";
10
+ } from "./chunk-5XP2UOFK.js";
10
11
  import "./chunk-2MIISF2W.js";
11
12
  import "./chunk-BLEGIR35.js";
12
13
 
@@ -1,20 +1,20 @@
1
+ import {
2
+ controlTokenAuthHeaders
3
+ } from "./chunk-GI2CKKBL.js";
1
4
  import {
2
5
  formatChannelEvents
3
- } from "./chunk-YKS6YZKM.js";
6
+ } from "./chunk-PHXO5P25.js";
4
7
  import {
5
8
  readHookStdin
6
9
  } from "./chunk-LSUB6QMP.js";
7
10
  import {
8
11
  readSessionDiscoveryByKey
9
12
  } from "./chunk-DO42NPNR.js";
10
- import {
11
- controlTokenAuthHeaders
12
- } from "./chunk-GI2CKKBL.js";
13
+ import "./chunk-BLEGIR35.js";
13
14
  import {
14
15
  deriveDiscoveryKey,
15
16
  findClaudeAncestorPid
16
- } from "./chunk-BJMASMKX.js";
17
- import "./chunk-BLEGIR35.js";
17
+ } from "./chunk-VHKPWUX7.js";
18
18
 
19
19
  // src/hooks/user-prompt-submit-hook.ts
20
20
  async function runUserPromptSubmitHook() {
@@ -3,7 +3,7 @@ import {
3
3
  WEBHOOK_DEFAULT_SIGNATURE_PREFIX
4
4
  } from "./chunk-V5VZPYMZ.js";
5
5
 
6
- // src/tandem/webhook-sink.ts
6
+ // src/delivery/lib/webhook-sink.ts
7
7
  import crypto from "crypto";
8
8
  import { ulid } from "ulidx";
9
9
  var DEFAULT_MAX_QUEUE = 1e3;