@interactive-inc/claude-funnel 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -407,6 +407,14 @@ type ClientData = {
407
407
  connectors: string[];
408
408
  tapAll?: boolean; /** Routing mode resolved from channel config at upgrade time. Defaults to fanout. */
409
409
  delivery?: "fanout" | "exclusive";
410
+ /**
411
+ * Opaque per-client id declared at upgrade time (`?id=<subscriberId>`). When an
412
+ * event carries `meta.target`, only the client whose `subscriberId` equals it
413
+ * receives the event among that channel's regular subscribers. Targeted delivery
414
+ * is how a publisher addresses one specific instance (e.g. a single agent
415
+ * session) without every subscriber having to receive and discard it.
416
+ */
417
+ subscriberId?: string;
410
418
  };
411
419
  type BroadcastEvent = {
412
420
  content: string;
@@ -498,6 +506,11 @@ declare class FunnelBroadcaster {
498
506
  * receive (passive observation). For each per-channel group:
499
507
  * - fanout → every matching client receives
500
508
  * - exclusive → exactly one client receives, picked round-robin per channel
509
+ *
510
+ * `meta.target` narrows the regular (non-tap) recipient set first via
511
+ * `matchesClient`: only the subscriber whose `subscriberId` equals `target`
512
+ * stays in the running, so a targeted event reaches one named instance while
513
+ * still being observable by tap=all clients.
501
514
  */
502
515
  private pickRecipients;
503
516
  addClient(ws: ServerWebSocket<unknown>, data: ClientData): void;
@@ -892,6 +905,7 @@ declare function buildGatewayRoutes(): _$hono_hono_base0.HonoBase<Env$1, {
892
905
  content: string;
893
906
  meta?: Record<string, string> | undefined;
894
907
  connector?: string | undefined;
908
+ target?: string | undefined;
895
909
  };
896
910
  };
897
911
  output: {
@@ -910,6 +924,7 @@ declare function buildGatewayRoutes(): _$hono_hono_base0.HonoBase<Env$1, {
910
924
  content: string;
911
925
  meta?: Record<string, string> | undefined;
912
926
  connector?: string | undefined;
927
+ target?: string | undefined;
913
928
  };
914
929
  };
915
930
  output: {
@@ -928,6 +943,7 @@ declare function buildGatewayRoutes(): _$hono_hono_base0.HonoBase<Env$1, {
928
943
  content: string;
929
944
  meta?: Record<string, string> | undefined;
930
945
  connector?: string | undefined;
946
+ target?: string | undefined;
931
947
  };
932
948
  };
933
949
  output: {
@@ -1303,6 +1319,7 @@ declare const publishRequestSchema: z.ZodObject<{
1303
1319
  content: z.ZodString;
1304
1320
  meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1305
1321
  connector: z.ZodOptional<z.ZodString>;
1322
+ target: z.ZodOptional<z.ZodString>;
1306
1323
  }, z.core.$strip>;
1307
1324
  type PublishRequest = z.infer<typeof publishRequestSchema>;
1308
1325
  declare const publishResponseSchema: z.ZodObject<{
@@ -1472,7 +1489,8 @@ type WsData = {
1472
1489
  channelName: string | null; /** Connector names belonging to that channel; used by tap-all replay filtering. */
1473
1490
  connectors: string[];
1474
1491
  tapAll?: boolean; /** Routing mode for this channel; resolved at upgrade time from settings. */
1475
- delivery: "fanout" | "exclusive"; /** Replay any events with offset strictly greater than this on open, then resume the live stream. */
1492
+ delivery: "fanout" | "exclusive"; /** Opaque client id from `?id=<subscriberId>`; lets publishers target this client via `meta.target`. */
1493
+ subscriberId?: string; /** Replay any events with offset strictly greater than this on open, then resume the live stream. */
1476
1494
  since?: number;
1477
1495
  };
1478
1496
  /**
@@ -2354,7 +2372,7 @@ declare const routes: _$hono_hono_base0.HonoBase<Env, {
2354
2372
  connectors: {
2355
2373
  id: string;
2356
2374
  name: string;
2357
- type: "schedule" | "slack" | "gh" | "discord";
2375
+ type: "discord" | "schedule" | "slack" | "gh";
2358
2376
  }[];
2359
2377
  }[];
2360
2378
  outputFormat: "json";
package/dist/index.js CHANGED
@@ -1740,7 +1740,14 @@ var MemoryFunnelClock = class extends FunnelClock {
1740
1740
  const publishRequestSchema = z.object({
1741
1741
  content: z.string().min(1),
1742
1742
  meta: z.record(z.string(), z.string()).optional(),
1743
- connector: z.string().min(1).optional()
1743
+ connector: z.string().min(1).optional(),
1744
+ /**
1745
+ * Address the event to a single subscriber. When set, only the WS client that
1746
+ * declared this id at upgrade time (`?id=<subscriberId>`) receives it among the
1747
+ * channel's regular subscribers; tap=all observers still see it. Omit for the
1748
+ * default fanout. The route surfaces it to subscribers as `meta.target`.
1749
+ */
1750
+ target: z.string().min(1).optional()
1744
1751
  });
1745
1752
  const publishResponseSchema = z.object({
1746
1753
  ok: z.literal(true),
@@ -2101,6 +2108,8 @@ var FunnelBroadcaster = class {
2101
2108
  }
2102
2109
  matchesClient(event, data) {
2103
2110
  if (data.tapAll) return true;
2111
+ const target = event.meta?.target;
2112
+ if (target && target !== data.subscriberId) return false;
2104
2113
  const channelId = event.meta?.channelId;
2105
2114
  if (channelId && channelId !== data.channel) return false;
2106
2115
  const connector = event.meta?.connector;
@@ -2112,6 +2121,11 @@ var FunnelBroadcaster = class {
2112
2121
  * receive (passive observation). For each per-channel group:
2113
2122
  * - fanout → every matching client receives
2114
2123
  * - exclusive → exactly one client receives, picked round-robin per channel
2124
+ *
2125
+ * `meta.target` narrows the regular (non-tap) recipient set first via
2126
+ * `matchesClient`: only the subscriber whose `subscriberId` equals `target`
2127
+ * stays in the running, so a targeted event reaches one named instance while
2128
+ * still being observable by tap=all clients.
2115
2129
  */
2116
2130
  pickRecipients(event) {
2117
2131
  const exclusiveByChannel = /* @__PURE__ */ new Map();
@@ -2983,13 +2997,17 @@ const channelsPublishHandler$1 = factory$1.createHandlers(zParam(z.object({ chan
2983
2997
  }), (c) => {
2984
2998
  const param = c.req.valid("param");
2985
2999
  const body = c.req.valid("json");
3000
+ const meta = body.target ? {
3001
+ ...body.meta,
3002
+ target: body.target
3003
+ } : body.meta;
2986
3004
  const response = {
2987
3005
  ok: true,
2988
3006
  offset: c.var.deps.emit({
2989
3007
  channel: param.channel,
2990
3008
  connector: body.connector,
2991
3009
  content: body.content,
2992
- meta: body.meta
3010
+ meta
2993
3011
  }).offset
2994
3012
  };
2995
3013
  return c.json(response);
@@ -3453,12 +3471,14 @@ var FunnelGatewayServer = class {
3453
3471
  const sinceRaw = url.searchParams.get("since");
3454
3472
  const sinceParsed = sinceRaw === null ? NaN : Number.parseInt(sinceRaw, 10);
3455
3473
  const since = Number.isFinite(sinceParsed) && sinceParsed >= 0 ? sinceParsed : void 0;
3474
+ const subscriberId = url.searchParams.get("id") ?? void 0;
3456
3475
  if (server.upgrade(request, { data: {
3457
3476
  channel: channelId,
3458
3477
  channelName,
3459
3478
  connectors,
3460
3479
  tapAll,
3461
3480
  delivery,
3481
+ subscriberId,
3462
3482
  since
3463
3483
  } })) return void 0;
3464
3484
  return new Response("WebSocket upgrade failed", { status: 400 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactive-inc/claude-funnel",
3
- "version": "0.38.0",
3
+ "version": "0.40.0",
4
4
  "description": "Hub CLI that routes external events (Slack / GitHub / Discord) to Claude Code agents through subscription channels over MCP.",
5
5
  "keywords": [
6
6
  "bun",