@codeforbreakfast/commy-mcp 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +413 -204
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeforbreakfast/commy-mcp",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "commy MCP server — the inter-agent communication substrate, runnable on node via npx.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/server.js CHANGED
@@ -6657,7 +6657,7 @@ var require_dist = __commonJS((exports, module) => {
6657
6657
  });
6658
6658
 
6659
6659
  // packages/mcp/server.ts
6660
- import { basename as basename2, join as join9 } from "node:path";
6660
+ import { basename as basename2, join as join10 } from "node:path";
6661
6661
  // node_modules/.bun/effect@3.21.4/node_modules/effect/dist/esm/Array.js
6662
6662
  var exports_Array = {};
6663
6663
  __export(exports_Array, {
@@ -45359,6 +45359,9 @@ var MessageIdSchema = exports_Schema.NonEmptyString.pipe(exports_Schema.brand("M
45359
45359
  var decodeMessageId = exports_Schema.decodeUnknown(MessageIdSchema);
45360
45360
  var ThreadNameSchema = exports_Schema.NonEmptyString.pipe(exports_Schema.brand("ThreadName"));
45361
45361
  var decodeThreadName = exports_Schema.decodeUnknown(ThreadNameSchema);
45362
+ var ChannelPermalinkSchema = exports_Schema.NonEmptyString.pipe(exports_Schema.brand("ChannelPermalink"));
45363
+ var ThreadPermalinkSchema = exports_Schema.NonEmptyString.pipe(exports_Schema.brand("ThreadPermalink"));
45364
+ var MessagePermalinkSchema = exports_Schema.NonEmptyString.pipe(exports_Schema.brand("MessagePermalink"));
45362
45365
  var TimestampSchema = exports_Schema.NonNegative.pipe(exports_Schema.brand("Timestamp"));
45363
45366
  var decodeTimestamp = exports_Schema.decodeUnknown(TimestampSchema);
45364
45367
  var MessageBodySchema = exports_Schema.NonEmptyString.pipe(exports_Schema.brand("MessageBody"));
@@ -45379,6 +45382,22 @@ var decodeMessageBodySync = exports_Schema.decodeSync(MessageBodySchema);
45379
45382
  var decodeDisplayNameSync = exports_Schema.decodeSync(DisplayNameSchema);
45380
45383
  var decodeBotNameSync = exports_Schema.decodeSync(BotNameSchema);
45381
45384
  var decodeEmojiSync = exports_Schema.decodeSync(EmojiSchema);
45385
+ var ChannelRefSchema = exports_Schema.Struct({
45386
+ id: ChannelIdSchema,
45387
+ name: ChannelNameSchema,
45388
+ permalink: ChannelPermalinkSchema
45389
+ });
45390
+ var ObservedThreadSchema = exports_Schema.Struct({
45391
+ name: ThreadNameSchema,
45392
+ resolved: exports_Schema.Boolean,
45393
+ permalink: ThreadPermalinkSchema
45394
+ });
45395
+ var MessageRefSchema = exports_Schema.Struct({
45396
+ id: MessageIdSchema,
45397
+ channel: ChannelRefSchema,
45398
+ thread: exports_Schema.OptionFromSelf(ObservedThreadSchema),
45399
+ permalink: MessagePermalinkSchema
45400
+ });
45382
45401
 
45383
45402
  class DirectoryError extends exports_Data.TaggedError("DirectoryError") {
45384
45403
  get message() {
@@ -47425,23 +47444,40 @@ var HASH_REPLACEMENTS = {
47425
47444
  var encodeHashComponent = (value5) => encodeURIComponent(value5).replace(/[%!'()*.]/g, (matched) => HASH_REPLACEMENTS[matched] ?? matched);
47426
47445
  var permalinkBase = (config2) => config2.hostHeader === undefined ? config2.realmUrl.replace(/\/+$/, "") : `https://${config2.hostHeader}`;
47427
47446
  var channelSlug = (channel) => `${channel.id}-${encodeHashComponent(channel.name.replaceAll(" ", "-"))}`;
47428
- var channelPermalink = (base, channel) => `${base}/#narrow/channel/${channelSlug(channel)}`;
47429
- var topicPermalink = (base, channel, topic) => `${channelPermalink(base, channel)}/topic/${encodeHashComponent(topic)}`;
47430
- var messagePermalink = (base, channel, id3, topic) => topic === undefined ? `${channelPermalink(base, channel)}/near/${id3}` : `${topicPermalink(base, channel, topic)}/near/${id3}`;
47447
+ var channelPermalink = (base, channel) => ChannelPermalinkSchema.make(`${base}/#narrow/channel/${channelSlug(channel)}`);
47448
+ var topicPermalink = (base, channel, topic, anchor) => ThreadPermalinkSchema.make(`${channelPermalink(base, channel)}/topic/${encodeHashComponent(topic)}/with/${anchor}`);
47449
+ var messagePermalink = (base, id3) => MessagePermalinkSchema.make(`${base}/#narrow/id/${id3}`);
47431
47450
  var withChannelPermalink = (base, channel) => ({ ...channel, permalink: channelPermalink(base, channel) });
47432
- var buildMessageRef = (base, id3, channel, threadName3) => {
47451
+ var buildMessageRef = (base, id3, channel, thread) => {
47433
47452
  const decoratedChannel = withChannelPermalink(base, channel);
47434
- return threadName3 === undefined ? { id: id3, channel: decoratedChannel, permalink: messagePermalink(base, decoratedChannel, id3) } : {
47453
+ const permalink = messagePermalink(base, id3);
47454
+ return thread === undefined ? {
47435
47455
  id: id3,
47436
47456
  channel: decoratedChannel,
47437
- thread: {
47438
- name: threadName3,
47439
- permalink: topicPermalink(base, decoratedChannel, threadName3)
47440
- },
47441
- permalink: messagePermalink(base, decoratedChannel, id3, threadName3)
47457
+ thread: exports_Option.none(),
47458
+ permalink
47459
+ } : {
47460
+ id: id3,
47461
+ channel: decoratedChannel,
47462
+ thread: exports_Option.some({
47463
+ name: thread.name,
47464
+ resolved: thread.resolved,
47465
+ permalink: topicPermalink(base, decoratedChannel, thread.name, id3)
47466
+ }),
47467
+ permalink
47442
47468
  };
47443
47469
  };
47444
47470
 
47471
+ // packages/zulip/resolved-topic.ts
47472
+ var RESOLVED_TOPIC_PREFIX = "✔ ";
47473
+ var isResolvedTopic = (topic) => topic.startsWith(RESOLVED_TOPIC_PREFIX);
47474
+ var stripResolvedPrefix = (topic) => isResolvedTopic(topic) ? topic.slice(RESOLVED_TOPIC_PREFIX.length) : topic;
47475
+ var splitTopic = (rawTopic) => {
47476
+ const stripped = stripResolvedPrefix(rawTopic);
47477
+ return isResolvedTopic(rawTopic) && stripped.length > 0 ? { name: stripped, resolved: true } : { name: rawTopic, resolved: false };
47478
+ };
47479
+ var applyResolvedPrefix = (name, resolved) => resolved ? isResolvedTopic(name) ? name : RESOLVED_TOPIC_PREFIX + name : stripResolvedPrefix(name);
47480
+
47445
47481
  // packages/zulip/events.ts
47446
47482
  var createMessageRefCache = (maxSize = 1e4) => {
47447
47483
  const map32 = new Map;
@@ -47535,8 +47571,9 @@ var decodeMessageRef = (message, base) => exports_Effect.gen(function* () {
47535
47571
  const id3 = yield* decodeMessageId(String(message.id));
47536
47572
  const channelId = yield* decodeChannelId(String(message.stream_id));
47537
47573
  const channelName = yield* decodeChannelName(message.display_recipient);
47538
- const threadName3 = yield* decodeThreadName(message.subject);
47539
- return buildMessageRef(base, id3, { id: channelId, name: channelName }, threadName3);
47574
+ const { name, resolved } = splitTopic(message.subject);
47575
+ const threadName3 = yield* decodeThreadName(name);
47576
+ return buildMessageRef(base, id3, { id: channelId, name: channelName }, { name: threadName3, resolved });
47540
47577
  });
47541
47578
  var decodeSenderIdentity = (message, directory) => {
47542
47579
  const cached4 = directory.byId.get(message.sender_id);
@@ -47934,6 +47971,8 @@ var historicalReactionSchema = exports_Schema.Struct({
47934
47971
  });
47935
47972
  var historicalMessageRawSchema = exports_Schema.Struct({
47936
47973
  id: exports_Schema.Int,
47974
+ stream_id: exports_Schema.Int,
47975
+ display_recipient: exports_Schema.String,
47937
47976
  sender_id: exports_Schema.Int,
47938
47977
  sender_full_name: exports_Schema.String,
47939
47978
  subject: exports_Schema.NonEmptyString,
@@ -47942,19 +47981,27 @@ var historicalMessageRawSchema = exports_Schema.Struct({
47942
47981
  reactions: exports_Schema.optional(exports_Schema.Array(historicalReactionSchema))
47943
47982
  });
47944
47983
  var toHistoricalReaction = (r) => ({ userId: ZulipUserRef(r.user_id), emojiName: r.emoji_name });
47945
- var toHistoricalMessage = (m) => exports_Effect.all({
47946
- id: decodeMessageId(String(m.id)),
47947
- subject: decodeThreadName(m.subject),
47948
- ts: decodeTimestamp(m.timestamp)
47949
- }).pipe(exports_Effect.map(({ id: id3, subject, ts }) => ({
47950
- id: id3,
47951
- senderId: ZulipUserRef(m.sender_id),
47952
- senderFullName: m.sender_full_name,
47953
- subject,
47954
- content: m.content,
47955
- ts,
47956
- reactions: (m.reactions ?? []).map(toHistoricalReaction)
47957
- })));
47984
+ var toHistoricalMessage = (m) => {
47985
+ const { name, resolved } = splitTopic(m.subject);
47986
+ return exports_Effect.all({
47987
+ id: decodeMessageId(String(m.id)),
47988
+ channelId: decodeChannelId(String(m.stream_id)),
47989
+ channelName: decodeChannelName(m.display_recipient),
47990
+ subject: decodeThreadName(name),
47991
+ ts: decodeTimestamp(m.timestamp)
47992
+ }).pipe(exports_Effect.map(({ id: id3, channelId, channelName, subject, ts }) => ({
47993
+ id: id3,
47994
+ channelId,
47995
+ channelName,
47996
+ senderId: ZulipUserRef(m.sender_id),
47997
+ senderFullName: m.sender_full_name,
47998
+ subject,
47999
+ resolved,
48000
+ content: m.content,
48001
+ ts,
48002
+ reactions: (m.reactions ?? []).map(toHistoricalReaction)
48003
+ })));
48004
+ };
47958
48005
  var messagesResponseSchema = exports_Schema.Struct({
47959
48006
  result: exports_Schema.Literal("success"),
47960
48007
  messages: exports_Schema.Array(historicalMessageRawSchema)
@@ -48002,17 +48049,17 @@ var toIdentity = (u) => exports_Effect.all({
48002
48049
  kind: u.is_bot ? "agent" : "human"
48003
48050
  })));
48004
48051
  var isActiveOfKind = (kind) => (u) => u.is_active && (kind === "agent" ? u.is_bot : !u.is_bot);
48005
- var streamIsListening = (state, channelId) => exports_HashSet.has(state.subscribedChannels, channelId) || exports_HashSet.has(state.newTopicsChannels, channelId);
48052
+ var streamIsListening = (state, channelName) => exports_HashSet.has(state.subscribedChannels, channelName) || exports_HashSet.has(state.newTopicsChannels, channelName);
48006
48053
  var currentMode = (state) => state.mentionsSubscribed && exports_HashSet.size(state.subscribedChannels) === 0 && exports_HashSet.size(state.newTopicsChannels) === 0 ? "mentions" : "all";
48007
- var observeNewTopic = (state, channelId, threadName3) => {
48008
- const seen = exports_HashMap.get(state.seenTopicsByChannel, channelId).pipe(exports_Option.getOrElse(() => exports_HashSet.empty()));
48054
+ var observeNewTopic = (state, channelName, threadName3) => {
48055
+ const seen = exports_HashMap.get(state.seenTopicsByChannel, channelName).pipe(exports_Option.getOrElse(() => exports_HashSet.empty()));
48009
48056
  if (exports_HashSet.has(seen, threadName3))
48010
48057
  return [false, state];
48011
48058
  return [
48012
48059
  true,
48013
48060
  {
48014
48061
  ...state,
48015
- seenTopicsByChannel: exports_HashMap.set(state.seenTopicsByChannel, channelId, exports_HashSet.add(seen, threadName3))
48062
+ seenTopicsByChannel: exports_HashMap.set(state.seenTopicsByChannel, channelName, exports_HashSet.add(seen, threadName3))
48016
48063
  }
48017
48064
  ];
48018
48065
  };
@@ -48076,7 +48123,8 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48076
48123
  }
48077
48124
  return out;
48078
48125
  });
48079
- const mapHistoricalMessage = (m, channel, directory2) => exports_Effect.gen(function* () {
48126
+ const mapHistoricalMessage = (m, directory2) => exports_Effect.gen(function* () {
48127
+ const channel = decorateChannel({ id: m.channelId, name: m.channelName });
48080
48128
  const cached4 = directory2.byId.get(m.senderId);
48081
48129
  const sender = cached4 ?? (yield* exports_Effect.all({
48082
48130
  id: decodeIdentityId(String(m.senderId)),
@@ -48085,7 +48133,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48085
48133
  const body = yield* decodeMessageBody(m.content);
48086
48134
  const reactions = yield* mapHistoricalReactions(m.reactions, directory2);
48087
48135
  return {
48088
- ref: decorateMessageRef(m.id, channel, m.subject),
48136
+ ref: decorateMessageRef(m.id, channel, { name: m.subject, resolved: m.resolved }),
48089
48137
  sender,
48090
48138
  body,
48091
48139
  ts: m.ts,
@@ -48093,7 +48141,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48093
48141
  reactions
48094
48142
  };
48095
48143
  });
48096
- const readMessages = (channel, range5, narrow) => exports_Effect.all([
48144
+ const readMessages = (range5, narrow) => exports_Effect.all([
48097
48145
  buildDirectoryLookup(),
48098
48146
  minterHttp.get("/messages", messagesResponseSchema, {
48099
48147
  anchor: "newest",
@@ -48102,11 +48150,11 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48102
48150
  narrow: JSON.stringify(narrow),
48103
48151
  apply_markdown: false
48104
48152
  })
48105
- ], { concurrency: 2 }).pipe(exports_Effect.flatMap(([directory2, res]) => exports_Effect.forEach(res.messages, toHistoricalMessage).pipe(exports_Effect.map((historical) => historical.filter(inRange(range5))), exports_Effect.flatMap((inRangeMessages) => exports_Effect.forEach(inRangeMessages, (m) => mapHistoricalMessage(m, channel, directory2))))));
48153
+ ], { concurrency: 2 }).pipe(exports_Effect.flatMap(([directory2, res]) => exports_Effect.forEach(res.messages, toHistoricalMessage).pipe(exports_Effect.map((historical) => historical.filter(inRange(range5))), exports_Effect.flatMap((inRangeMessages) => exports_Effect.forEach(inRangeMessages, (m) => mapHistoricalMessage(m, directory2))))));
48106
48154
  const realmHost = new URL(config2.realmUrl).hostname;
48107
48155
  const base = permalinkBase(config2);
48108
48156
  const decorateChannel = (channel) => withChannelPermalink(base, channel);
48109
- const decorateMessageRef = (id3, channel, threadName3) => buildMessageRef(base, id3, channel, threadName3);
48157
+ const decorateMessageRef = (id3, channel, thread) => buildMessageRef(base, id3, channel, thread);
48110
48158
  const buildBotEmail = (shortName) => BotEmail(`${shortName}-bot@${realmHost}`);
48111
48159
  const findAnyBotByName = (name) => minterHttp.get("/users", usersSchema).pipe(exports_Effect.map((res) => exports_Option.fromNullable(res.members.find((u) => u.full_name === name && u.is_bot))));
48112
48160
  const mintBot = (name) => {
@@ -48262,11 +48310,42 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48262
48310
  onNone: () => refreshKnownStreams(),
48263
48311
  onSome: exports_Effect.succeed
48264
48312
  })), exports_Effect.flatMap((map32) => map32.has(name) ? exports_Effect.succeed(map32) : refreshKnownStreams()), exports_Effect.map((map32) => exports_Option.fromNullable(map32.get(name))));
48265
- const resolvePublishChannel = (requested) => lookupChannel(requested.name).pipe(exports_Effect.flatMap(exports_Option.match({
48266
- onNone: () => exports_Effect.fail(new UnknownChannel({ channel: requested.name, substrate: "zulip" })),
48313
+ const resolvePublishChannel = (requested) => lookupChannel(requested).pipe(exports_Effect.flatMap(exports_Option.match({
48314
+ onNone: () => exports_Effect.fail(new UnknownChannel({ channel: requested, substrate: "zulip" })),
48267
48315
  onSome: exports_Effect.succeed
48268
48316
  })));
48269
48317
  const listKnownChannels = () => refreshKnownStreams().pipe(exports_Effect.map((map32) => [...map32.values()]));
48318
+ const findNewestInTopic = (channel, topic) => minterHttp.get("/messages", messagesResponseSchema, {
48319
+ anchor: "newest",
48320
+ num_before: 1,
48321
+ num_after: 0,
48322
+ narrow: JSON.stringify([
48323
+ { operator: "channel", operand: channel },
48324
+ { operator: "topic", operand: topic }
48325
+ ]),
48326
+ apply_markdown: false
48327
+ }).pipe(exports_Effect.flatMap((res) => exports_Array.last(res.messages).pipe(exports_Option.match({
48328
+ onNone: () => exports_Effect.succeed(exports_Option.none()),
48329
+ onSome: (m) => decodeMessageId(String(m.id)).pipe(exports_Effect.map(exports_Option.some))
48330
+ }))));
48331
+ const setThreadResolved = (channel, thread, resolved) => {
48332
+ const operation = resolved ? "resolveThread" : "unresolveThread";
48333
+ const sourceTopic = applyResolvedPrefix(thread, !resolved);
48334
+ const targetTopic = applyResolvedPrefix(thread, resolved);
48335
+ return boundHttp().pipe(exports_Effect.flatMap((http) => findNewestInTopic(channel, sourceTopic).pipe(exports_Effect.flatMap(exports_Option.match({
48336
+ onSome: (id3) => http.patch(`/messages/${encodeURIComponent(id3)}`, successSchema2, {
48337
+ topic: targetTopic,
48338
+ propagate_mode: "change_all"
48339
+ }).pipe(exports_Effect.asVoid),
48340
+ onNone: () => findNewestInTopic(channel, targetTopic).pipe(exports_Effect.flatMap(exports_Option.match({
48341
+ onSome: () => exports_Effect.void,
48342
+ onNone: () => exports_Effect.fail(new PublisherError({
48343
+ operation,
48344
+ cause: new Error(`no thread '${thread}' in ${channel} to ${resolved ? "resolve" : "unresolve"}`)
48345
+ }))
48346
+ })))
48347
+ })))), exports_Effect.mapError((cause3) => cause3 instanceof PublisherError ? cause3 : new PublisherError({ operation, cause: cause3 })));
48348
+ };
48270
48349
  const publisher = {
48271
48350
  post: (channel, body, opts) => {
48272
48351
  const thread = opts?.thread;
@@ -48274,8 +48353,8 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48274
48353
  type: "channel",
48275
48354
  to: effective.name,
48276
48355
  content: body,
48277
- topic: thread === undefined ? "(no topic)" : thread.name
48278
- }).pipe(exports_Effect.flatMap((sent) => decodeMessageId(String(sent.id)).pipe(exports_Effect.map((id3) => decorateMessageRef(id3, effective, thread?.name)))))))), exports_Effect.mapError((cause3) => cause3 instanceof UnknownChannel ? cause3 : new PublisherError({ operation: "post", cause: cause3 })));
48356
+ topic: thread ?? "(no topic)"
48357
+ }).pipe(exports_Effect.flatMap((sent) => decodeMessageId(String(sent.id)).pipe(exports_Effect.map((id3) => decorateMessageRef(id3, effective, thread === undefined ? undefined : { name: thread, resolved: false })))))))), exports_Effect.mapError((cause3) => cause3 instanceof UnknownChannel ? cause3 : new PublisherError({ operation: "post", cause: cause3 })));
48279
48358
  },
48280
48359
  edit: (message, body) => boundHttp().pipe(exports_Effect.flatMap((http) => http.patch(`/messages/${encodeURIComponent(message.id)}`, successSchema2, {
48281
48360
  content: body
@@ -48285,7 +48364,9 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48285
48364
  })), exports_Effect.asVoid, exports_Effect.mapError((cause3) => new PublisherError({ operation: "react", cause: cause3 }))),
48286
48365
  unreact: (message, emoji) => boundHttp().pipe(exports_Effect.flatMap((http) => http.delete(`/messages/${encodeURIComponent(message.id)}/reactions`, successSchema2, {
48287
48366
  emoji_name: emoji
48288
- })), exports_Effect.asVoid, exports_Effect.mapError((cause3) => new PublisherError({ operation: "unreact", cause: cause3 })))
48367
+ })), exports_Effect.asVoid, exports_Effect.mapError((cause3) => new PublisherError({ operation: "unreact", cause: cause3 }))),
48368
+ resolveThread: (channel, thread) => setThreadResolved(channel, thread, true),
48369
+ unresolveThread: (channel, thread) => setThreadResolved(channel, thread, false)
48289
48370
  };
48290
48371
  const inboxRef = yield* exports_SynchronizedRef.make({
48291
48372
  mentionsSubscribed: false,
@@ -48300,15 +48381,18 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48300
48381
  if (event.kind !== "message-posted")
48301
48382
  return exports_Effect.succeed(true);
48302
48383
  const message = event.message;
48303
- const cid = message.ref.channel.id;
48384
+ const cname = message.ref.channel.name;
48304
48385
  const thread = message.ref.thread;
48305
48386
  return exports_SynchronizedRef.get(boundRef).pipe(exports_Effect.flatMap((current2) => {
48306
48387
  const me = exports_Option.getOrUndefined(current2)?.identity;
48307
48388
  return exports_SynchronizedRef.modify(inboxRef, (state) => {
48308
- if (!exports_HashSet.has(state.newTopicsChannels, cid))
48389
+ if (!exports_HashSet.has(state.newTopicsChannels, cname))
48309
48390
  return [true, state];
48310
- const [isFirstOfTopic, ticked] = thread === undefined ? [false, state] : observeNewTopic(state, cid, thread.name);
48311
- if (exports_HashSet.has(state.subscribedChannels, cid))
48391
+ const [isFirstOfTopic, ticked] = exports_Option.match(thread, {
48392
+ onNone: () => [false, state],
48393
+ onSome: (t) => observeNewTopic(state, cname, t.name)
48394
+ });
48395
+ if (exports_HashSet.has(state.subscribedChannels, cname))
48312
48396
  return [true, ticked];
48313
48397
  if (state.mentionsSubscribed && me !== undefined && message.mentions.some((m) => m.id === me.id)) {
48314
48398
  return [true, ticked];
@@ -48339,18 +48423,18 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48339
48423
  if (channel === undefined)
48340
48424
  return ensureQueueRegistered();
48341
48425
  return exports_SynchronizedRef.modify(inboxRef, (state) => {
48342
- const wasListening = streamIsListening(state, channel.id);
48426
+ const wasListening = streamIsListening(state, channel);
48343
48427
  const next4 = exports_Predicate.hasProperty(target, "kind") ? {
48344
48428
  ...state,
48345
- newTopicsChannels: exports_HashSet.add(state.newTopicsChannels, channel.id)
48429
+ newTopicsChannels: exports_HashSet.add(state.newTopicsChannels, channel)
48346
48430
  } : {
48347
48431
  ...state,
48348
- subscribedChannels: exports_HashSet.add(state.subscribedChannels, channel.id)
48432
+ subscribedChannels: exports_HashSet.add(state.subscribedChannels, channel)
48349
48433
  };
48350
48434
  return [wasListening, next4];
48351
48435
  }).pipe(exports_Effect.flatMap((wasListening) => {
48352
48436
  const subscribeRemote = wasListening ? exports_Effect.void : minterHttp.post("/users/me/subscriptions", subscriptionsResponseSchema, {
48353
- subscriptions: JSON.stringify([{ name: channel.name }])
48437
+ subscriptions: JSON.stringify([{ name: channel }])
48354
48438
  }).pipe(exports_Effect.asVoid);
48355
48439
  return subscribeRemote.pipe(exports_Effect.andThen(ensureQueueRegistered()));
48356
48440
  }));
@@ -48368,15 +48452,15 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48368
48452
  return exports_SynchronizedRef.modify(inboxRef, (state) => {
48369
48453
  const next4 = exports_Predicate.hasProperty(target, "kind") ? {
48370
48454
  ...state,
48371
- newTopicsChannels: exports_HashSet.remove(state.newTopicsChannels, channel.id),
48372
- seenTopicsByChannel: exports_HashMap.remove(state.seenTopicsByChannel, channel.id)
48455
+ newTopicsChannels: exports_HashSet.remove(state.newTopicsChannels, channel),
48456
+ seenTopicsByChannel: exports_HashMap.remove(state.seenTopicsByChannel, channel)
48373
48457
  } : {
48374
48458
  ...state,
48375
- subscribedChannels: exports_HashSet.remove(state.subscribedChannels, channel.id)
48459
+ subscribedChannels: exports_HashSet.remove(state.subscribedChannels, channel)
48376
48460
  };
48377
- return [streamIsListening(next4, channel.id), next4];
48461
+ return [streamIsListening(next4, channel), next4];
48378
48462
  }).pipe(exports_Effect.flatMap((stillListening) => stillListening ? exports_Effect.void : minterHttp.delete("/users/me/subscriptions", subscriptionsResponseSchema, {
48379
- subscriptions: JSON.stringify([channel.name])
48463
+ subscriptions: JSON.stringify([channel])
48380
48464
  }).pipe(exports_Effect.asVoid)));
48381
48465
  }).pipe(exports_Effect.mapError((cause3) => new InboxError({ operation: "unsubscribe", cause: cause3 }))),
48382
48466
  events: () => exports_Stream.unwrap(exports_Effect.all([exports_SynchronizedRef.get(boundRef), exports_SynchronizedRef.get(inboxRef)]).pipe(exports_Effect.map(([current2, state]) => inboxEvents({
@@ -48426,11 +48510,14 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48426
48510
  }
48427
48511
  };
48428
48512
  const history = {
48429
- readChannel: (channel, range5) => readMessages(channel, range5, [{ operator: "channel", operand: channel.name }]).pipe(exports_Effect.mapError((cause3) => new HistoryError({ operation: "readChannel", cause: cause3 }))),
48430
- readThread: (channel, threadName3, range5) => readMessages(channel, range5 ?? {}, [
48431
- { operator: "channel", operand: channel.name },
48432
- { operator: "topic", operand: threadName3 }
48433
- ]).pipe(exports_Effect.mapError((cause3) => new HistoryError({ operation: "readThread", cause: cause3 }))),
48513
+ readChannel: (channel, range5) => readMessages(range5, [{ operator: "channel", operand: channel }]).pipe(exports_Effect.mapError((cause3) => new HistoryError({ operation: "readChannel", cause: cause3 }))),
48514
+ readThread: (channel, threadName3, range5) => {
48515
+ const readTopic = (topic) => readMessages(range5 ?? {}, [
48516
+ { operator: "channel", operand: channel },
48517
+ { operator: "topic", operand: topic }
48518
+ ]);
48519
+ return readTopic(threadName3).pipe(exports_Effect.flatMap((messages) => messages.length > 0 ? exports_Effect.succeed(messages) : readTopic(applyResolvedPrefix(threadName3, true))), exports_Effect.mapError((cause3) => new HistoryError({ operation: "readThread", cause: cause3 })));
48520
+ },
48434
48521
  recentThreads: (sender, opts) => {
48435
48522
  const limit = opts?.limit ?? RECENT_THREADS_DEFAULT_LIMIT;
48436
48523
  return buildDirectoryLookup().pipe(exports_Effect.flatMap((lookup) => {
@@ -48449,7 +48536,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48449
48536
  let seen = exports_HashMap.empty();
48450
48537
  for (const m of res.messages) {
48451
48538
  const channel = yield* decodeChannelName(m.display_recipient);
48452
- const thread = yield* decodeThreadName(m.subject);
48539
+ const thread = yield* decodeThreadName(splitTopic(m.subject).name);
48453
48540
  const key = threadKey(channel, thread);
48454
48541
  if (!exports_HashMap.has(seen, key)) {
48455
48542
  seen = exports_HashMap.set(seen, key, {
@@ -48467,7 +48554,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48467
48554
  })));
48468
48555
  }), exports_Effect.mapError((cause3) => new HistoryError({ operation: "recentThreads", cause: cause3 })));
48469
48556
  },
48470
- messagePermalink: (id3, hint) => (hint === undefined ? fetchMessageRef(minterHttp, Number(id3), base).pipe(exports_Effect.map(exports_Option.flatMap((ref) => exports_Option.fromNullable(ref.permalink)))) : lookupChannel(hint.channel).pipe(exports_Effect.map(exports_Option.flatMap((channel) => exports_Option.fromNullable(buildMessageRef(base, id3, channel, hint.thread).permalink))))).pipe(exports_Effect.mapError((cause3) => new HistoryError({ operation: "messagePermalink", cause: cause3 })))
48557
+ messagePermalink: (id3, hint) => (hint === undefined ? fetchMessageRef(minterHttp, Number(id3), base).pipe(exports_Effect.map(exports_Option.map((ref) => ref.permalink))) : lookupChannel(hint.channel).pipe(exports_Effect.map(exports_Option.map((channel) => buildMessageRef(base, id3, channel, hint.thread === undefined ? undefined : { name: hint.thread, resolved: false }).permalink)))).pipe(exports_Effect.mapError((cause3) => new HistoryError({ operation: "messagePermalink", cause: cause3 })))
48471
48558
  };
48472
48559
  const reconcileMinter = () => reconcileMinterSubscriptions({
48473
48560
  listUnsubscribedPublicStreams: () => minterHttp.get("/streams", streamsListResponseSchema, {
@@ -56706,16 +56793,16 @@ var parseSubscribeTarget = (token) => exports_Effect.gen(function* () {
56706
56793
  });
56707
56794
  });
56708
56795
  var intentToTarget = (intent) => exports_Match.value(intent).pipe(exports_Match.discriminatorsExhaustive("kind")({
56709
- mentions: () => exports_Effect.succeed("mentions"),
56710
- channel: ({ channelName }) => decodeChannelId(channelName).pipe(exports_Effect.map((id3) => ({ id: id3, name: channelName })), exports_Effect.orDie),
56711
- "new-topics-in-channel": ({ channelName }) => decodeChannelId(channelName).pipe(exports_Effect.map((id3) => ({
56796
+ mentions: () => "mentions",
56797
+ channel: ({ channelName }) => channelName,
56798
+ "new-topics-in-channel": ({ channelName }) => ({
56712
56799
  kind: "new-topics-in-channel",
56713
- channel: { id: id3, name: channelName }
56714
- })), exports_Effect.orDie),
56715
- thread: ({ channelName, threadName: threadName3 }) => decodeChannelId(channelName).pipe(exports_Effect.map((id3) => ({
56716
- channel: { id: id3, name: channelName },
56717
- thread: { name: threadName3 }
56718
- })), exports_Effect.orDie)
56800
+ channel: channelName
56801
+ }),
56802
+ thread: ({ channelName, threadName: threadName3 }) => ({
56803
+ channel: channelName,
56804
+ thread: threadName3
56805
+ })
56719
56806
  }));
56720
56807
 
56721
56808
  // packages/mcp/bootstrap.ts
@@ -56729,6 +56816,7 @@ var envConfigError = (issues) => new EnvConfigError({
56729
56816
  });
56730
56817
  var SessionIdSchema = exports_Schema.UUID.pipe(exports_Schema.brand("SessionId"));
56731
56818
  var parseSessionId = exports_Schema.decodeUnknownOption(SessionIdSchema);
56819
+ var readBootSessionId = exports_Effect.orElseSucceed(exports_Effect.map(exports_Config.option(exports_Config.string("CLAUDE_CODE_SESSION_ID")), exports_Option.flatMap(parseSessionId)), () => exports_Option.none());
56732
56820
  var BOT_NAME_RE = /^[a-z][a-z0-9_-]*$/;
56733
56821
  var parseBotName = (raw3) => {
56734
56822
  if (raw3.length === 0 || raw3.length > 40)
@@ -56739,7 +56827,7 @@ var parseBotName = (raw3) => {
56739
56827
  };
56740
56828
  var placeholderShape = /^\$\{[^}]+\}$/;
56741
56829
  var userConfigPlaceholderShape = /^\$\{user_config\.[^}]+\}$/;
56742
- var placeholderMessage = (key, raw3) => `${key} value is an unsubstituted placeholder${raw3 === undefined ? "" : ` (${raw3})`} — ` + `the host did not expand it. ` + `Claude Code only substitutes \${user_config.X} (and \${CLAUDE_PLUGIN_ROOT} in args) ` + `in .mcp.json; host-env vars like \${CLAUDE_CODE_SESSION_ID} must be inherited from ` + `the parent environment instead. See ass-ukz6 for context.`;
56830
+ var placeholderMessage = (key, raw3) => `${key} value is an unsubstituted placeholder${raw3 === undefined ? "" : ` (${raw3})`} — ` + `the host did not expand it. ` + `Claude Code only substitutes \${user_config.X} (and \${CLAUDE_PLUGIN_ROOT} in args) ` + `in .mcp.json; host-env vars like \${CLAUDE_CODE_SESSION_ID} must be inherited from ` + `the parent environment instead.`;
56743
56831
  var rejectPlaceholder = (key) => exports_Config.validate({
56744
56832
  message: placeholderMessage(key),
56745
56833
  validation: (value5) => !placeholderShape.test(value5)
@@ -56786,15 +56874,6 @@ var optionalNonNegativeInt = (key) => optionalUserConfig(key).pipe(exports_Confi
56786
56874
  return Number.isInteger(parsed) && parsed >= 0 ? exports_Either.right(exports_Option.some(parsed)) : exports_Either.left(exports_ConfigError.InvalidData([key], `${key} must be a non-negative integer — received: ${raw3}`));
56787
56875
  }
56788
56876
  })));
56789
- var optionalSessionId = (key) => exports_Config.option(exports_Config.string(key).pipe(exports_Config.mapOrFail((value5) => {
56790
- if (value5.length === 0) {
56791
- return exports_Either.left(exports_ConfigError.MissingData([key], `${key} is unset`));
56792
- }
56793
- if (placeholderShape.test(value5)) {
56794
- return exports_Either.left(exports_ConfigError.InvalidData([key], placeholderMessage(key, value5)));
56795
- }
56796
- return exports_Either.fromOption(parseSessionId(value5), () => exports_ConfigError.InvalidData([key], `${key} must be a UUID — received: ${value5}`));
56797
- })));
56798
56877
  var optionalBotName = (key) => optionalNonEmpty(key).pipe(exports_Config.mapOrFail((option7) => exports_Option.match(option7, {
56799
56878
  onNone: () => exports_Either.right(exports_Option.none()),
56800
56879
  onSome: (raw3) => exports_Either.fromOption(parseBotName(raw3), () => exports_ConfigError.InvalidData([key], `${key} must be substrate-safe (lowercase ASCII, digits, dashes, underscores; starts with letter; max 40 chars) — received: ${raw3}`)).pipe(exports_Either.map(exports_Option.some))
@@ -56817,8 +56896,7 @@ var envConfig = exports_Config.all({
56817
56896
  botApiKey: optionalApiKey("COMMY_BOT_API_KEY"),
56818
56897
  subscribe: optionalUserConfig("COMMY_SUBSCRIBE"),
56819
56898
  project: optionalNonEmpty("COMMY_PROJECT"),
56820
- catchupWindowSeconds: optionalNonNegativeInt("COMMY_CATCHUP_WINDOW_SECONDS"),
56821
- sessionId: optionalSessionId("CLAUDE_CODE_SESSION_ID")
56899
+ catchupWindowSeconds: optionalNonNegativeInt("COMMY_CATCHUP_WINDOW_SECONDS")
56822
56900
  }).pipe(exports_Config.mapOrFail((raw3) => {
56823
56901
  if (exports_Option.isSome(raw3.botApiKey) && exports_Option.isNone(raw3.botName)) {
56824
56902
  return exports_Either.left(exports_ConfigError.InvalidData(["COMMY_BOT_API_KEY"], "COMMY_BOT_API_KEY requires COMMY_BOT_NAME — the key identifies which provisioned persona to attach to"));
@@ -56834,10 +56912,6 @@ var envConfig = exports_Config.all({
56834
56912
  onNone: () => ({}),
56835
56913
  onSome: (subscribe4) => ({ subscribe: subscribe4 })
56836
56914
  }),
56837
- ...exports_Option.match(raw3.sessionId, {
56838
- onNone: () => ({}),
56839
- onSome: (sessionId) => ({ sessionId })
56840
- }),
56841
56915
  ...exports_Option.match(project3, { onNone: () => ({}), onSome: (slug) => ({ project: slug }) }),
56842
56916
  ...exports_Option.match(raw3.catchupWindowSeconds, {
56843
56917
  onNone: () => ({}),
@@ -56911,7 +56985,7 @@ var ZulipAdapterLive = substrateAdapterLayer(exports_Effect.gen(function* () {
56911
56985
  var subscribeFromEnv = (inbox, narrowSet, parsed) => {
56912
56986
  if (parsed.subscribe === undefined)
56913
56987
  return exports_Effect.succeed([]);
56914
- return exports_Effect.forEach(parsed.subscribe.split(","), (raw3) => parseSubscribeTarget(raw3.trim()).pipe(exports_Effect.tap((intent) => exports_Effect.sync(() => narrowSet.add(intent))), exports_Effect.flatMap((intent) => intentToTarget(intent).pipe(exports_Effect.flatMap((target) => inbox.subscribe(target)), exports_Effect.as(intent)))));
56988
+ return exports_Effect.forEach(parsed.subscribe.split(","), (raw3) => parseSubscribeTarget(raw3.trim()).pipe(exports_Effect.tap((intent) => exports_Effect.sync(() => narrowSet.add(intent))), exports_Effect.flatMap((intent) => inbox.subscribe(intentToTarget(intent)).pipe(exports_Effect.as(intent)))));
56915
56989
  };
56916
56990
 
56917
56991
  // packages/mcp/events.ts
@@ -56928,11 +57002,11 @@ var formatMessage = (event, botIdentityId) => {
56928
57002
  const meta2 = buildMeta([
56929
57003
  ["channel_id", ref.channel.id],
56930
57004
  ["channel_name", ref.channel.name],
56931
- ["thread", ref.thread?.name],
57005
+ ["thread", exports_Option.getOrUndefined(exports_Option.map(ref.thread, (t) => t.name))],
56932
57006
  ["message_id", ref.id],
56933
57007
  ["permalink", ref.permalink],
56934
57008
  ["channel_permalink", ref.channel.permalink],
56935
- ["thread_permalink", ref.thread?.permalink],
57009
+ ["thread_permalink", exports_Option.getOrUndefined(exports_Option.map(ref.thread, (t) => t.permalink))],
56936
57010
  ["sender_id", sender.id],
56937
57011
  ["sender_name", sender.name],
56938
57012
  ["sender_kind", sender.kind],
@@ -56950,7 +57024,7 @@ var formatReaction = (event, observedAt) => {
56950
57024
  const meta2 = buildMeta([
56951
57025
  ["target_message_id", event.target.id],
56952
57026
  ["target_channel_name", event.target.channel.name],
56953
- ["target_thread", event.target.thread?.name],
57027
+ ["target_thread", exports_Option.getOrUndefined(exports_Option.map(event.target.thread, (t) => t.name))],
56954
57028
  ["target_permalink", event.target.permalink],
56955
57029
  ["reaction_emoji", event.emoji],
56956
57030
  ["reaction_action", action],
@@ -56994,7 +57068,6 @@ var catchUpMentions = (deps) => exports_Effect.gen(function* () {
56994
57068
  });
56995
57069
 
56996
57070
  // packages/mcp/channels-catch-up.ts
56997
- var channelRefFromName = (name) => decodeChannelId(name).pipe(exports_Effect.map((id3) => ({ id: id3, name })), exports_Effect.orDie);
56998
57071
  var clampedSince = (now2, windowSeconds) => decodeTimestamp(Math.max(0, now2 - windowSeconds)).pipe(exports_Effect.orDie);
56999
57072
  var firstMessagePerTopic = (messages) => {
57000
57073
  const seen = new Set;
@@ -57002,20 +57075,20 @@ var firstMessagePerTopic = (messages) => {
57002
57075
  const ordered = exports_Array.sort(messages, exports_Order.mapInput(exports_Order.number, (m) => m.ts));
57003
57076
  for (const m of ordered) {
57004
57077
  const thread = m.ref.thread;
57005
- if (thread === undefined)
57078
+ if (exports_Option.isNone(thread))
57006
57079
  continue;
57007
- if (seen.has(thread.name))
57080
+ if (seen.has(thread.value.name))
57008
57081
  continue;
57009
- seen.add(thread.name);
57082
+ seen.add(thread.value.name);
57010
57083
  out.push(m);
57011
57084
  }
57012
57085
  return out;
57013
57086
  };
57014
57087
  var fetchForIntent = (intent, history, defaultSince, newTopicsSince) => exports_Match.value(intent).pipe(exports_Match.discriminatorsExhaustive("kind")({
57015
57088
  mentions: () => exports_Effect.succeed([]),
57016
- channel: (i) => channelRefFromName(i.channelName).pipe(exports_Effect.flatMap((ref) => history.readChannel(ref, { since: defaultSince }))),
57017
- "new-topics-in-channel": (i) => channelRefFromName(i.channelName).pipe(exports_Effect.flatMap((ref) => history.readChannel(ref, { since: newTopicsSince })), exports_Effect.map(firstMessagePerTopic)),
57018
- thread: (i) => channelRefFromName(i.channelName).pipe(exports_Effect.flatMap((ref) => history.readThread(ref, i.threadName, { since: defaultSince })))
57089
+ channel: (i) => history.readChannel(i.channelName, { since: defaultSince }),
57090
+ "new-topics-in-channel": (i) => history.readChannel(i.channelName, { since: newTopicsSince }).pipe(exports_Effect.map(firstMessagePerTopic)),
57091
+ thread: (i) => history.readThread(i.channelName, i.threadName, { since: defaultSince })
57019
57092
  }));
57020
57093
  var catchUpChannels = (deps) => exports_Effect.gen(function* () {
57021
57094
  if (deps.intents.length === 0)
@@ -57091,6 +57164,35 @@ var createEnsureBound = (deps) => exports_Effect.map(exports_Ref.make({ kind: "i
57091
57164
  return ensureBound;
57092
57165
  });
57093
57166
 
57167
+ // packages/mcp/target-cursor.ts
57168
+ import { homedir as homedir2 } from "node:os";
57169
+ import { join as join8 } from "node:path";
57170
+ var deliveryTargetOf = (message) => exports_Data.struct({
57171
+ channel: message.ref.channel.name,
57172
+ thread: exports_Option.map(message.ref.thread, (observed) => observed.name)
57173
+ });
57174
+ var DeliveryTargetSchema = exports_Schema.Data(exports_Schema.Struct({
57175
+ channel: ChannelNameSchema,
57176
+ thread: exports_Schema.OptionFromNullOr(ThreadNameSchema)
57177
+ }));
57178
+ var TargetCursorsFileSchema = exports_Schema.parseJson(exports_Schema.HashMap({ key: DeliveryTargetSchema, value: TimestampSchema }));
57179
+ var decodeTargetCursorsFile = exports_Schema.decodeUnknown(TargetCursorsFileSchema);
57180
+ var encodeTargetCursorsFile = exports_Schema.encode(TargetCursorsFileSchema);
57181
+ var FILENAME_SAFE2 = /[^a-zA-Z0-9._-]/g;
57182
+ var cursorsFilename = (id3) => `${id3.replace(FILENAME_SAFE2, "_")}.json`;
57183
+ var isNotFound2 = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
57184
+ var readCursors = (fs, path) => fs.readFileString(path).pipe(exports_Effect.flatMap(decodeTargetCursorsFile), exports_Effect.map(exports_Option.some), exports_Effect.catchIf(isNotFound2, () => exports_Effect.succeed(exports_Option.none())));
57185
+ var advanceTargetCursor = (fs, dir, id3, target, ts) => {
57186
+ const path = join8(dir, cursorsFilename(id3));
57187
+ return readCursors(fs, path).pipe(exports_Effect.catchTag("ParseError", () => exports_Effect.succeed(exports_Option.none())), exports_Effect.map(exports_Option.getOrElse(() => exports_HashMap.empty())), exports_Effect.flatMap((cursors) => {
57188
+ const prior = exports_HashMap.get(cursors, target);
57189
+ return exports_Option.isSome(prior) && prior.value >= ts ? exports_Effect.void : encodeTargetCursorsFile(exports_HashMap.set(cursors, target, ts)).pipe(exports_Effect.orDie, exports_Effect.flatMap((json4) => fs.makeDirectory(dir, { recursive: true }).pipe(exports_Effect.zipRight(fs.writeFileString(path, json4)))));
57190
+ }));
57191
+ };
57192
+ var STATE_SEGMENT2 = "commy";
57193
+ var stateBaseConfig2 = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join8(homedir2(), ".local", "state")));
57194
+ var targetCursorDirConfig = stateBaseConfig2.pipe(exports_Config.map((base) => join8(base, STATE_SEGMENT2, "target-cursors")));
57195
+
57094
57196
  // packages/mcp/event-pump.ts
57095
57197
  var originatorId = (event) => exports_Match.value(event).pipe(exports_Match.discriminatorsExhaustive("kind")({
57096
57198
  "message-posted": (message) => message.message.sender.id,
@@ -57146,9 +57248,11 @@ var startEventPump = (deps) => exports_Effect.gen(function* () {
57146
57248
  deps.rememberIdentity(identity6);
57147
57249
  }
57148
57250
  const mentionAdvance = deps.onMention !== undefined && event.kind === "mention-received" ? deps.onMention(event.message.ts) : exports_Effect.void;
57251
+ const deliveryAdvance = deps.onDelivery !== undefined && (event.kind === "message-posted" || event.kind === "mention-received") ? deps.onDelivery(deliveryTargetOf(event.message), event.message.ts) : exports_Effect.void;
57252
+ const advance = exports_Effect.zipRight(mentionAdvance, deliveryAdvance);
57149
57253
  if (recordMessageDelivery(event, seenDelivered) === "duplicate")
57150
- return mentionAdvance;
57151
- return mentionAdvance.pipe(exports_Effect.zipRight(renderEvent(event, botIdentityId)), exports_Effect.flatMap((payload) => exports_Effect.tryPromise({
57254
+ return advance;
57255
+ return advance.pipe(exports_Effect.zipRight(renderEvent(event, botIdentityId)), exports_Effect.flatMap((payload) => exports_Effect.tryPromise({
57152
57256
  try: () => deps.notifier(payload),
57153
57257
  catch: (cause3) => new DispatchFailure({ cause: cause3 })
57154
57258
  })));
@@ -58658,12 +58762,12 @@ class Server extends Protocol {
58658
58762
  }
58659
58763
 
58660
58764
  // packages/mcp/mcp-server.ts
58661
- var PLUGIN_VERSION = "0.15.0";
58765
+ var PLUGIN_VERSION = "0.17.0";
58662
58766
  var COMMY_INSTRUCTIONS = `**Substrate.** commy is the inter-agent channel: agents and humans coordinate here. If you run it alongside other agent-messaging tools, keep one substrate canonical and don't fan the same message across all of them.
58663
58767
 
58664
58768
  **Channels.** Each project has one channel: \`#<project-slug>\` where the slug is resolved per session as \`COMMY_PROJECT\` env > git remote \`origin\` basename > git root basename. Sessions launched outside a git repo (e.g. \`/tmp\`, \`$HOME\`) have no project channel and should post to \`#general\` instead. Don't invent a channel name from a metaphor — never post to a literal \`#home\`, \`#project\`, or \`#<project>\`. Use \`list_channels\` to enumerate what actually exists in the realm; posting to a non-existent channel throws \`UnknownChannel\` rather than silently routing to Notification Bot.
58665
58769
 
58666
- **Topics.** A topic is a logical thread of conversation within a channel; the \`post\` tool's \`thread\` argument names it (Zulip calls these "topics"). Open a new topic when the work shifts — a fresh task, a new question, a different incident — and reply into an existing one when you're continuing the same line of work. Name topics by the work, not the speaker: \`payments-migration\`, \`auth-token-refresh\`, \`events-queue-expiry\` — not \`bot-debugging\` or \`graeme-asked\`. Top-level channel chatter (omit \`thread\`) is for terse status pings only; anything substantive deserves a topic.
58770
+ **Topics.** A topic is a logical thread of conversation within a channel; the \`post\` tool's \`thread\` argument names it (Zulip calls these "topics"). Open a new topic when the work shifts — a fresh task, a new question, a different incident — and reply into an existing one when you're continuing the same line of work. Name topics by the work, not the speaker: \`payments-migration\`, \`auth-token-refresh\`, \`events-queue-expiry\` — not \`bot-debugging\` or \`alice-asked\`. Top-level channel chatter (omit \`thread\`) is for terse status pings only; anything substantive deserves a topic.
58667
58771
 
58668
58772
  **Subscriptions.** Be on your project channel and \`#general\` only — not on other projects' channels. Boot-time defaults come from the plugin's \`COMMY_SUBSCRIBE\` userConfig (tokens: \`channel:<name>\`, \`thread:<channel>/<thread>\`, \`new-topics:<channel>\`, \`mentions\`); adjust the live set via \`subscribe\`/\`unsubscribe\` at runtime. Inbound matches arrive as \`<channel source="commy" ...>\` blocks. If one arrives from a channel you aren't subscribed to, treat it as background context and don't reply — only post into your project channel, into \`#general\`, or into threads you've explicitly joined. **Refer to peers by name** — the \`sender_name\` (and reaction \`by_name\`) in those blocks — never by a bare number; reserve numbers for message ids, which otherwise collide with identity in a thread.
58669
58773
 
@@ -58709,7 +58813,7 @@ var keyToIntent = (key) => exports_Match.value(key).pipe(exports_Match.discrimin
58709
58813
  }));
58710
58814
  var channelKey = (ref) => channelKeyOf(ref.channel.name);
58711
58815
  var newTopicsKey = (ref) => newTopicsKeyOf(ref.channel.name);
58712
- var threadKey = (ref) => exports_Option.fromNullable(ref.thread).pipe(exports_Option.map((thread) => threadKeyOf(ref.channel.name, thread.name)));
58816
+ var threadKey = (ref) => exports_Option.map(ref.thread, (thread) => threadKeyOf(ref.channel.name, thread.name));
58713
58817
  var refMatches = (ref, intents) => exports_HashSet.has(intents, channelKey(ref)) || exports_Option.exists(threadKey(ref), (tk) => exports_HashSet.has(intents, tk));
58714
58818
  var mentionsMatches = (intents, mentions, botIdentityId) => {
58715
58819
  if (botIdentityId === undefined)
@@ -58721,6 +58825,8 @@ var mentionsMatches = (intents, mentions, botIdentityId) => {
58721
58825
  var createNarrowSet = () => {
58722
58826
  let intents = exports_HashSet.empty();
58723
58827
  let seenTopics = exports_HashSet.empty();
58828
+ const pending3 = [];
58829
+ let recording = false;
58724
58830
  const newTopicsMatches = (ref) => exports_Option.match(threadKey(ref), {
58725
58831
  onNone: () => false,
58726
58832
  onSome: (tk) => {
@@ -58735,15 +58841,30 @@ var createNarrowSet = () => {
58735
58841
  const messagePostedMatches = (ref) => refMatches(ref, intents) || newTopicsMatches(ref);
58736
58842
  return {
58737
58843
  add: (intent) => {
58738
- intents = exports_HashSet.add(intents, intentKey(intent));
58844
+ const key = intentKey(intent);
58845
+ intents = exports_HashSet.add(intents, key);
58846
+ if (recording)
58847
+ pending3.push({ op: "add", key });
58739
58848
  },
58740
58849
  remove: (intent) => {
58741
- intents = exports_HashSet.remove(intents, intentKey(intent));
58850
+ const key = intentKey(intent);
58851
+ intents = exports_HashSet.remove(intents, key);
58852
+ if (recording)
58853
+ pending3.push({ op: "remove", key });
58742
58854
  },
58743
58855
  size: () => exports_HashSet.size(intents),
58744
58856
  intents: () => exports_Array.fromIterable(intents).map(keyToIntent),
58745
- replace: (next4) => {
58746
- intents = exports_HashSet.fromIterable(next4.map(intentKey));
58857
+ beginBuffering: () => {
58858
+ recording = true;
58859
+ },
58860
+ load: (base) => {
58861
+ const seeded = exports_Option.match(base, {
58862
+ onNone: () => intents,
58863
+ onSome: (next4) => exports_HashSet.fromIterable(next4.map(intentKey))
58864
+ });
58865
+ intents = pending3.reduce((acc, delta) => delta.op === "add" ? exports_HashSet.add(acc, delta.key) : exports_HashSet.remove(acc, delta.key), seeded);
58866
+ pending3.length = 0;
58867
+ recording = false;
58747
58868
  },
58748
58869
  matches: (event, botIdentityId) => exports_Match.value(event).pipe(exports_Match.discriminatorsExhaustive("kind")({
58749
58870
  "message-posted": (e) => messagePostedMatches(e.message.ref) || mentionsMatches(intents, e.message.mentions, botIdentityId),
@@ -58758,17 +58879,28 @@ var createNarrowSet = () => {
58758
58879
  var DEFAULT_TIMEOUT_MS = 5000;
58759
58880
  var raceReleaseAgainstTimeout = (release, timeoutMs = DEFAULT_TIMEOUT_MS) => release.pipe(exports_Effect.tapDefect((cause3) => exports_Effect.logError(`release failed: ${exports_Cause.pretty(cause3)}`)), exports_Effect.catchAllDefect(() => exports_Effect.void), exports_Effect.timeoutOption(exports_Duration.millis(timeoutMs)), exports_Effect.flatMap((result) => exports_Option.isNone(result) ? exports_Effect.logError(`release timed out after ${timeoutMs}ms`) : exports_Effect.void));
58760
58881
 
58882
+ // packages/mcp/session-id.ts
58883
+ class SessionId extends exports_Context.Tag("commy/mcp/SessionId")() {
58884
+ }
58885
+ var SessionIdLive = exports_Layer.effect(SessionId, exports_Deferred.make());
58886
+
58761
58887
  // packages/mcp/subscription-restore.ts
58762
- var applyRestored = (deps, intents) => exports_Effect.sync(() => deps.narrowSet.replace(intents)).pipe(exports_Effect.zipRight(exports_Effect.forEach(intents, (intent) => intentToTarget(intent).pipe(exports_Effect.flatMap(deps.inbox.subscribe)), { discard: true })));
58763
- var restoreOrSeedSubscriptions = (deps, sessionId, project3) => deps.subscriptionStore.read(sessionId).pipe(exports_Effect.flatMap(exports_Option.match({
58888
+ var applyRestored = (deps, intents) => exports_Effect.sync(() => deps.narrowSet.load(exports_Option.some(intents))).pipe(exports_Effect.zipRight(exports_Effect.forEach(intents, (intent) => deps.inbox.subscribe(intentToTarget(intent)), {
58889
+ discard: true
58890
+ })));
58891
+ var seedDefaultsIfFresh = (deps, project3) => deps.subscriptionStore.read().pipe(exports_Effect.flatMap(exports_Option.match({
58764
58892
  onNone: () => deps.registerDefaults(project3),
58893
+ onSome: () => exports_Effect.void
58894
+ })));
58895
+ var restoreSubscriptions = (deps) => deps.subscriptionStore.read().pipe(exports_Effect.flatMap(exports_Option.match({
58896
+ onNone: () => exports_Effect.sync(() => deps.narrowSet.load(exports_Option.none())),
58765
58897
  onSome: (intents) => applyRestored(deps, intents)
58766
58898
  })));
58767
- var persistSubscriptions = (store, narrowSet, sessionId) => store.write(sessionId, narrowSet.intents());
58899
+ var persistSubscriptions = (store, narrowSet) => store.write(narrowSet.intents());
58768
58900
 
58769
58901
  // packages/mcp/subscription-store.ts
58770
- import { homedir as homedir2 } from "node:os";
58771
- import { join as join8 } from "node:path";
58902
+ import { homedir as homedir3 } from "node:os";
58903
+ import { join as join9 } from "node:path";
58772
58904
  class SubscriptionStoreTag extends exports_Context.Tag("SubscriptionStore")() {
58773
58905
  }
58774
58906
  var SubscribeIntentSchema = exports_Schema.Union(exports_Schema.Struct({ kind: exports_Schema.Literal("mentions") }), exports_Schema.Struct({ kind: exports_Schema.Literal("channel"), channelName: ChannelNameSchema }), exports_Schema.Struct({
@@ -58778,24 +58910,33 @@ var SubscribeIntentSchema = exports_Schema.Union(exports_Schema.Struct({ kind: e
58778
58910
  }), exports_Schema.Struct({ kind: exports_Schema.Literal("new-topics-in-channel"), channelName: ChannelNameSchema }));
58779
58911
  var SubscriptionsFileSchema = exports_Schema.parseJson(exports_Schema.Array(SubscribeIntentSchema));
58780
58912
  var decodeSubscriptionsFile = exports_Schema.decodeUnknown(SubscriptionsFileSchema);
58781
- var FILENAME_SAFE2 = /[^a-zA-Z0-9._-]/g;
58913
+ var FILENAME_SAFE3 = /[^a-zA-Z0-9._-]/g;
58782
58914
  var subscriptionFilename = (id3) => {
58783
- const safe = id3.replace(FILENAME_SAFE2, "_");
58915
+ const safe = id3.replace(FILENAME_SAFE3, "_");
58784
58916
  return `${safe}.json`;
58785
58917
  };
58786
- var isNotFound2 = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
58787
- var readSubscriptions = (fs, path) => fs.readFileString(path).pipe(exports_Effect.flatMap(decodeSubscriptionsFile), exports_Effect.map((intents) => exports_Option.some(intents)), exports_Effect.catchIf(isNotFound2, () => exports_Effect.succeed(exports_Option.none())));
58918
+ var isNotFound3 = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
58919
+ var readSubscriptions = (fs, path) => fs.readFileString(path).pipe(exports_Effect.flatMap(decodeSubscriptionsFile), exports_Effect.map((intents) => exports_Option.some(intents)), exports_Effect.catchIf(isNotFound3, () => exports_Effect.succeed(exports_Option.none())));
58788
58920
  var createFileSubscriptionStore = (deps) => {
58789
- const { dir, fs } = deps;
58790
- const pathFor = (id3) => join8(dir, subscriptionFilename(id3));
58791
- const read3 = (id3) => readSubscriptions(fs, pathFor(id3));
58792
- const write4 = (id3, intents) => fs.makeDirectory(dir, { recursive: true }).pipe(exports_Effect.zipRight(fs.writeFileString(pathFor(id3), JSON.stringify(intents))));
58793
- return { read: read3, write: write4 };
58794
- };
58795
- var STATE_SEGMENT2 = "commy";
58796
- var stateBaseConfig2 = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join8(homedir2(), ".local", "state")));
58797
- var subscriptionDirConfig = stateBaseConfig2.pipe(exports_Config.map((base) => join8(base, STATE_SEGMENT2, "subscriptions")));
58798
- var FileSubscriptionStoreLive = exports_Layer.effect(SubscriptionStoreTag, exports_Effect.all([exports_FileSystem.FileSystem, exports_Effect.orDie(subscriptionDirConfig)]).pipe(exports_Effect.map(([fs, dir]) => createFileSubscriptionStore({ dir, fs }))));
58921
+ const { dir, fs, cursorDir, session } = deps;
58922
+ const pathFor = (id3) => join9(dir, subscriptionFilename(id3));
58923
+ const read3 = () => exports_Effect.flatMap(exports_Deferred.await(session), (id3) => readSubscriptions(fs, pathFor(id3)));
58924
+ const write4 = (intents) => exports_Effect.flatMap(exports_Deferred.await(session), (id3) => fs.makeDirectory(dir, { recursive: true }).pipe(exports_Effect.zipRight(fs.writeFileString(pathFor(id3), JSON.stringify(intents)))));
58925
+ const advanceCursor = (target, ts) => exports_Effect.flatMap(exports_Deferred.poll(session), exports_Option.match({
58926
+ onNone: () => exports_Effect.void,
58927
+ onSome: (awaitId) => exports_Effect.flatMap(awaitId, (id3) => advanceTargetCursor(fs, cursorDir, id3, target, ts))
58928
+ }));
58929
+ return { read: read3, write: write4, advanceCursor };
58930
+ };
58931
+ var STATE_SEGMENT3 = "commy";
58932
+ var stateBaseConfig3 = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join9(homedir3(), ".local", "state")));
58933
+ var subscriptionDirConfig = stateBaseConfig3.pipe(exports_Config.map((base) => join9(base, STATE_SEGMENT3, "subscriptions")));
58934
+ var FileSubscriptionStoreLive = exports_Layer.effect(SubscriptionStoreTag, exports_Effect.all([
58935
+ exports_FileSystem.FileSystem,
58936
+ exports_Effect.orDie(subscriptionDirConfig),
58937
+ exports_Effect.orDie(targetCursorDirConfig),
58938
+ SessionId
58939
+ ]).pipe(exports_Effect.map(([fs, dir, cursorDir, session]) => createFileSubscriptionStore({ dir, fs, cursorDir, session }))));
58799
58940
 
58800
58941
  // packages/mcp/tools.ts
58801
58942
  var emptyObjectSchema = {
@@ -58815,11 +58956,6 @@ var identityShape = (identity6) => ({
58815
58956
  name: identity6.name,
58816
58957
  kind: identity6.kind
58817
58958
  });
58818
- var channelShape = (channel) => ({
58819
- id: channel.id,
58820
- name: channel.name,
58821
- permalink: channel.permalink ?? null
58822
- });
58823
58959
  var createCache = () => {
58824
58960
  const identityById = new Map;
58825
58961
  const channelByName = new Map;
@@ -58852,7 +58988,7 @@ class UnknownMessageError extends exports_Data.TaggedError("UnknownMessage") {
58852
58988
  return `no MessageRef cached for id=${this.id}; pass channel_name (and thread for thread-scoped messages) to reconstruct`;
58853
58989
  }
58854
58990
  }
58855
- var reconstructMessageRef = (cache, rawId, channelNameArg, threadNameArg) => exports_Effect.gen(function* () {
58991
+ var reconstructMessageRef = (cache, rawId, channelNameArg) => exports_Effect.gen(function* () {
58856
58992
  const id3 = yield* decodeMessageId(rawId);
58857
58993
  const cached5 = cache.messageById.get(id3);
58858
58994
  if (cached5 !== undefined)
@@ -58860,11 +58996,8 @@ var reconstructMessageRef = (cache, rawId, channelNameArg, threadNameArg) => exp
58860
58996
  if (channelNameArg === undefined) {
58861
58997
  return yield* new UnknownMessageError({ id: rawId });
58862
58998
  }
58863
- const channel = yield* resolveChannel(cache, channelNameArg);
58864
- if (threadNameArg !== undefined) {
58865
- return { id: id3, channel, thread: { name: yield* decodeThreadName(threadNameArg) } };
58866
- }
58867
- return { id: id3, channel };
58999
+ const channel = yield* addressChannel(cache, yield* decodeChannelName(channelNameArg));
59000
+ return { id: id3, channel, thread: exports_Option.none(), permalink: yield* decodeMessagePermalink(id3) };
58868
59001
  });
58869
59002
  var resolveMentions = (cache, mentionIds) => exports_Effect.forEach(mentionIds, (raw3) => exports_Effect.gen(function* () {
58870
59003
  const identity6 = cache.identityById.get(yield* decodeIdentityId(raw3));
@@ -58873,15 +59006,20 @@ var resolveMentions = (cache, mentionIds) => exports_Effect.forEach(mentionIds,
58873
59006
  }
58874
59007
  return identity6;
58875
59008
  }));
58876
- var messageShape = (m) => ({
59009
+ var encodeThreadFacet = exports_Schema.encode(exports_Schema.OptionFromNullOr(ObservedThreadSchema));
59010
+ var encodeChannel = exports_Schema.encode(ChannelRefSchema);
59011
+ var encodeMessagePermalink = exports_Schema.encode(MessagePermalinkSchema);
59012
+ var decodeChannelPermalink = exports_Schema.decode(ChannelPermalinkSchema);
59013
+ var decodeMessagePermalink = exports_Schema.decode(MessagePermalinkSchema);
59014
+ var messageShape = (m) => exports_Effect.all({
59015
+ channel: encodeChannel(m.ref.channel),
59016
+ thread: encodeThreadFacet(m.ref.thread),
59017
+ permalink: encodeMessagePermalink(m.ref.permalink)
59018
+ }).pipe(exports_Effect.map(({ channel, thread, permalink }) => ({
58877
59019
  id: m.ref.id,
58878
- channel: {
58879
- id: m.ref.channel.id,
58880
- name: m.ref.channel.name,
58881
- permalink: m.ref.channel.permalink ?? null
58882
- },
58883
- thread: m.ref.thread === undefined ? null : { name: m.ref.thread.name, permalink: m.ref.thread.permalink ?? null },
58884
- permalink: m.ref.permalink ?? null,
59020
+ channel,
59021
+ thread,
59022
+ permalink,
58885
59023
  sender: identityShape(m.sender),
58886
59024
  body: m.body,
58887
59025
  ts: m.ts,
@@ -58890,14 +59028,16 @@ var messageShape = (m) => ({
58890
59028
  emoji: r.emoji,
58891
59029
  by: r.by.map(identityShape)
58892
59030
  }))
58893
- });
58894
- var resolveChannel = (cache, rawName) => exports_Effect.gen(function* () {
58895
- const name = yield* decodeChannelName(rawName);
59031
+ })));
59032
+ var addressChannel = (cache, name) => exports_Effect.gen(function* () {
58896
59033
  const cached5 = cache.channelByName.get(name);
58897
59034
  if (cached5 !== undefined)
58898
59035
  return cached5;
58899
- const synthetic = { id: yield* decodeChannelId(rawName), name };
58900
- return synthetic;
59036
+ return {
59037
+ id: yield* decodeChannelId(name),
59038
+ name,
59039
+ permalink: yield* decodeChannelPermalink(name)
59040
+ };
58901
59041
  });
58902
59042
  var parseRange = (range5) => exports_Effect.gen(function* () {
58903
59043
  const out = {};
@@ -58946,6 +59086,10 @@ var ReadThreadArgs = exports_Schema.Struct({
58946
59086
  thread: exports_Schema.String,
58947
59087
  ...RangeArgs
58948
59088
  });
59089
+ var ThreadResolutionArgs = exports_Schema.Struct({
59090
+ channel_name: exports_Schema.String,
59091
+ thread: exports_Schema.String
59092
+ });
58949
59093
  var MessageLinkArgs = exports_Schema.Struct({
58950
59094
  message_id: exports_Schema.String,
58951
59095
  channel_name: exports_Schema.optional(exports_Schema.String),
@@ -58959,28 +59103,34 @@ var buildToolDefs = (deps, cache) => {
58959
59103
  const projectForCwd = deps.projectForCwd ?? (() => exports_Effect.succeed(undefined));
58960
59104
  const sessionIdField = {
58961
59105
  type: "string",
58962
- description: "Per-conversation identifier (UUID). In Claude Code, the plugin's PreToolUse hook injects this from the harness session id; non-CC MCP clients must supply a UUID (e.g. via crypto.randomUUID()). Anything that fails UUID validation is treated as missing — the server returns the unbound-stub error rather than minting a malformed cc-* identity (comms-uqf). Drives ephemeral identity minting (ass-2dhb)."
59106
+ description: "Per-conversation identifier (UUID). In Claude Code, the plugin's PreToolUse hook injects this from the harness session id; non-CC MCP clients must supply a UUID (e.g. via crypto.randomUUID()). Anything that fails UUID validation is treated as missing — the server returns the unbound-stub error rather than minting a malformed cc-* identity. Drives ephemeral identity minting."
58963
59107
  };
58964
59108
  const cwdField = {
58965
59109
  type: "string",
58966
- description: "Calling session's working directory. In Claude Code, the PreToolUse hook injects this from the harness cwd; non-CC MCP clients may supply their own. Drives the project component of ephemeral cc-<project>-<8> names (ass-v7b4) — without this the minted name falls back to bare cc-<8> (or the operator-forced COMMY_PROJECT slug)."
59110
+ description: "Calling session's working directory. In Claude Code, the PreToolUse hook injects this from the harness cwd; non-CC MCP clients may supply their own. Drives the project component of ephemeral cc-<project>-<8> names — without this the minted name falls back to bare cc-<8> (or the operator-forced COMMY_PROJECT slug)."
58967
59111
  };
58968
59112
  const readSessionId = (args2) => exports_Option.getOrUndefined(parseSessionId(args2["session_id"]));
59113
+ const feedSession = (sessionId) => sessionId === undefined || deps.feedSessionId === undefined ? exports_Effect.void : deps.feedSessionId(sessionId);
58969
59114
  const readCwd = (args2) => {
58970
59115
  const raw3 = args2["cwd"];
58971
59116
  return exports_Predicate.isString(raw3) ? raw3 : undefined;
58972
59117
  };
58973
59118
  const projectForArgs = (args2) => projectForCwd(readCwd(args2));
58974
- const stickyThreadEngagement = async (ref) => {
58975
- if (ref.thread === undefined)
59119
+ const ensureBoundForArgs = (args2) => exports_Effect.gen(function* () {
59120
+ const sessionId = readSessionId(args2);
59121
+ yield* feedSession(sessionId);
59122
+ return yield* identityCache.ensureBoundFor(sessionId, yield* projectForArgs(args2));
59123
+ });
59124
+ const stickyThreadEngagement = async (channel, threadName3) => {
59125
+ if (exports_Option.isNone(threadName3))
58976
59126
  return;
58977
59127
  const intent = {
58978
59128
  kind: "thread",
58979
- channelName: ref.channel.name,
58980
- threadName: ref.thread.name
59129
+ channelName: channel.name,
59130
+ threadName: threadName3.value
58981
59131
  };
58982
59132
  narrowSet.add(intent);
58983
- await runEdge(intentToTarget(intent).pipe(exports_Effect.flatMap((target) => adapter4.inbox.subscribe(target))));
59133
+ await runEdge(adapter4.inbox.subscribe(intentToTarget(intent)));
58984
59134
  };
58985
59135
  const coreTools = [
58986
59136
  {
@@ -58992,8 +59142,7 @@ var buildToolDefs = (deps, cache) => {
58992
59142
  additionalProperties: false
58993
59143
  },
58994
59144
  handler: async (args2) => {
58995
- const sessionId = readSessionId(args2);
58996
- const ensureBound = await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(sessionId, project3)));
59145
+ const ensureBound = await runEdge(ensureBoundForArgs(args2));
58997
59146
  const current2 = ensureBound.current();
58998
59147
  if (current2 === undefined) {
58999
59148
  return { state: "unbound", identity: null };
@@ -59068,12 +59217,12 @@ var buildToolDefs = (deps, cache) => {
59068
59217
  const channels = await runEdge(adapter4.directory.listChannels());
59069
59218
  for (const c of channels)
59070
59219
  cache.rememberChannel(c);
59071
- return { channels: channels.map(channelShape) };
59220
+ return { channels: await runEdge(exports_Effect.forEach(channels, (c) => encodeChannel(c))) };
59072
59221
  }
59073
59222
  },
59074
59223
  {
59075
59224
  name: "post",
59076
- description: "Post a message to a channel by name. Optional thread (topic), mentions (identity ids the bot has seen), and reply_to (message id). To ping someone on Zulip, write the @**Name** markup inline in body where you want it rendered — the mentions array is notification metadata only and does NOT modify body. Returns {message_id, channel_id, channel_name, thread, permalink}. When you show a human this message afterwards, link it by the returned permalink — never a bare name or message id.",
59225
+ description: "Post a message to a channel by name. Optional thread (topic), mentions (identity ids the bot has seen), and reply_to (message id). To ping someone on Zulip, write the @**Name** markup inline in body where you want it rendered — the mentions array is notification metadata only and does not modify body. Returns {message_id, channel_id, channel_name, thread, permalink}. When you show a human this message afterwards, link it by the returned permalink — never a bare name or message id.",
59077
59226
  inputSchema: {
59078
59227
  type: "object",
59079
59228
  properties: {
@@ -59099,31 +59248,34 @@ var buildToolDefs = (deps, cache) => {
59099
59248
  additionalProperties: false
59100
59249
  },
59101
59250
  handler: async (args2) => {
59102
- await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(readSessionId(args2), project3)).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59251
+ await runEdge(ensureBoundForArgs(args2).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59103
59252
  const ref = await runEdge(exports_Effect.gen(function* () {
59104
59253
  const { channel_name, body, thread, mentions, reply_to } = yield* exports_Schema.decodeUnknown(PostArgs)(args2);
59105
- const channel = yield* resolveChannel(cache, channel_name);
59254
+ const channel = yield* decodeChannelName(channel_name);
59106
59255
  const messageBody = yield* decodeMessageBody(body);
59107
59256
  const opts = {};
59108
59257
  if (thread !== undefined) {
59109
- opts.thread = { name: yield* decodeThreadName(thread) };
59258
+ opts.thread = yield* decodeThreadName(thread);
59110
59259
  }
59111
59260
  if (mentions !== undefined) {
59112
59261
  opts.mentions = yield* resolveMentions(cache, mentions);
59113
59262
  }
59114
59263
  if (reply_to !== undefined) {
59115
- opts.replyTo = yield* reconstructMessageRef(cache, reply_to, channel_name, thread);
59264
+ opts.replyTo = yield* reconstructMessageRef(cache, reply_to, channel_name);
59116
59265
  }
59117
59266
  return yield* adapter4.publisher.post(channel, messageBody, opts);
59118
59267
  }));
59119
59268
  cache.rememberMessage(ref);
59120
- await stickyThreadEngagement(ref);
59269
+ await stickyThreadEngagement(ref.channel, exports_Option.map(ref.thread, (t) => t.name));
59121
59270
  return {
59122
59271
  message_id: ref.id,
59123
59272
  channel_id: ref.channel.id,
59124
59273
  channel_name: ref.channel.name,
59125
- thread: ref.thread === undefined ? null : { name: ref.thread.name },
59126
- permalink: ref.permalink ?? null
59274
+ thread: exports_Option.match(ref.thread, {
59275
+ onNone: () => null,
59276
+ onSome: (t) => ({ name: t.name })
59277
+ }),
59278
+ permalink: ref.permalink
59127
59279
  };
59128
59280
  }
59129
59281
  },
@@ -59150,10 +59302,10 @@ var buildToolDefs = (deps, cache) => {
59150
59302
  additionalProperties: false
59151
59303
  },
59152
59304
  handler: async (args2) => {
59153
- await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(readSessionId(args2), project3)).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59305
+ await runEdge(ensureBoundForArgs(args2).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59154
59306
  const ref = await runEdge(exports_Effect.gen(function* () {
59155
- const { message_id, body, channel_name, thread } = yield* exports_Schema.decodeUnknown(EditMessageArgs)(args2);
59156
- const target = yield* reconstructMessageRef(cache, message_id, channel_name, thread);
59307
+ const { message_id, body, channel_name } = yield* exports_Schema.decodeUnknown(EditMessageArgs)(args2);
59308
+ const target = yield* reconstructMessageRef(cache, message_id, channel_name);
59157
59309
  yield* adapter4.publisher.edit(target, yield* decodeMessageBody(body));
59158
59310
  return target;
59159
59311
  }));
@@ -59184,15 +59336,17 @@ var buildToolDefs = (deps, cache) => {
59184
59336
  additionalProperties: false
59185
59337
  },
59186
59338
  handler: async (args2) => {
59187
- await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(readSessionId(args2), project3)).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59188
- const ref = await runEdge(exports_Effect.gen(function* () {
59339
+ await runEdge(ensureBoundForArgs(args2).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59340
+ const { ref, threadName: threadName3 } = await runEdge(exports_Effect.gen(function* () {
59189
59341
  const { message_id, emoji: emoji2, channel_name, thread } = yield* exports_Schema.decodeUnknown(ReactArgs)(args2);
59190
- const target = yield* reconstructMessageRef(cache, message_id, channel_name, thread);
59342
+ const target = yield* reconstructMessageRef(cache, message_id, channel_name);
59191
59343
  yield* adapter4.publisher.react(target, yield* decodeEmoji(emoji2));
59192
- return target;
59344
+ const argThread = yield* exports_Effect.transposeMapOption(exports_Option.fromNullable(thread), decodeThreadName);
59345
+ const threadName4 = exports_Option.orElse(exports_Option.map(target.thread, (t) => t.name), () => argThread);
59346
+ return { ref: target, threadName: threadName4 };
59193
59347
  }));
59194
59348
  cache.rememberMessage(ref);
59195
- await stickyThreadEngagement(ref);
59349
+ await stickyThreadEngagement(ref.channel, threadName3);
59196
59350
  return {};
59197
59351
  }
59198
59352
  },
@@ -59213,10 +59367,10 @@ var buildToolDefs = (deps, cache) => {
59213
59367
  additionalProperties: false
59214
59368
  },
59215
59369
  handler: async (args2) => {
59216
- await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(readSessionId(args2), project3)).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59370
+ await runEdge(ensureBoundForArgs(args2).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59217
59371
  await runEdge(exports_Effect.gen(function* () {
59218
- const { message_id, emoji: emoji2, channel_name, thread } = yield* exports_Schema.decodeUnknown(ReactArgs)(args2);
59219
- const ref = yield* reconstructMessageRef(cache, message_id, channel_name, thread);
59372
+ const { message_id, emoji: emoji2, channel_name } = yield* exports_Schema.decodeUnknown(ReactArgs)(args2);
59373
+ const ref = yield* reconstructMessageRef(cache, message_id, channel_name);
59220
59374
  yield* adapter4.publisher.unreact(ref, yield* decodeEmoji(emoji2));
59221
59375
  }));
59222
59376
  return {};
@@ -59243,14 +59397,14 @@ var buildToolDefs = (deps, cache) => {
59243
59397
  const { target: raw3 } = yield* exports_Schema.decodeUnknown(SubscribeArgs)(args2);
59244
59398
  const intent = yield* parseSubscribeTarget(raw3);
59245
59399
  const sessionId = readSessionId(args2);
59400
+ yield* feedSession(sessionId);
59246
59401
  if (sessionId !== undefined && deps.ensureSessionSubscriptions !== undefined) {
59247
59402
  yield* deps.ensureSessionSubscriptions(sessionId, yield* projectForArgs(args2));
59248
59403
  }
59249
59404
  narrowSet.add(intent);
59250
- const target = yield* intentToTarget(intent);
59251
- yield* adapter4.inbox.subscribe(target);
59252
- if (sessionId !== undefined && deps.persistSessionSubscriptions !== undefined) {
59253
- yield* deps.persistSessionSubscriptions(sessionId);
59405
+ yield* adapter4.inbox.subscribe(intentToTarget(intent));
59406
+ if (deps.persistSessionSubscriptions !== undefined) {
59407
+ yield* deps.persistSessionSubscriptions;
59254
59408
  }
59255
59409
  }));
59256
59410
  return {};
@@ -59277,14 +59431,14 @@ var buildToolDefs = (deps, cache) => {
59277
59431
  const { target: raw3 } = yield* exports_Schema.decodeUnknown(SubscribeArgs)(args2);
59278
59432
  const intent = yield* parseSubscribeTarget(raw3);
59279
59433
  const sessionId = readSessionId(args2);
59434
+ yield* feedSession(sessionId);
59280
59435
  if (sessionId !== undefined && deps.ensureSessionSubscriptions !== undefined) {
59281
59436
  yield* deps.ensureSessionSubscriptions(sessionId, yield* projectForArgs(args2));
59282
59437
  }
59283
59438
  narrowSet.remove(intent);
59284
- const target = yield* intentToTarget(intent);
59285
- yield* adapter4.inbox.unsubscribe(target);
59286
- if (sessionId !== undefined && deps.persistSessionSubscriptions !== undefined) {
59287
- yield* deps.persistSessionSubscriptions(sessionId);
59439
+ yield* adapter4.inbox.unsubscribe(intentToTarget(intent));
59440
+ if (deps.persistSessionSubscriptions !== undefined) {
59441
+ yield* deps.persistSessionSubscriptions;
59288
59442
  }
59289
59443
  }));
59290
59444
  return {};
@@ -59305,14 +59459,14 @@ var buildToolDefs = (deps, cache) => {
59305
59459
  handler: async (args2) => {
59306
59460
  const messages = await runEdge(exports_Effect.gen(function* () {
59307
59461
  const parsed = yield* exports_Schema.decodeUnknown(ReadChannelArgs)(args2);
59308
- const channelRef = yield* resolveChannel(cache, parsed.channel_name);
59309
- return yield* adapter4.history.readChannel(channelRef, yield* parseRange(parsed));
59462
+ const channel = yield* decodeChannelName(parsed.channel_name);
59463
+ return yield* adapter4.history.readChannel(channel, yield* parseRange(parsed));
59310
59464
  }));
59311
59465
  for (const m of messages) {
59312
59466
  cache.rememberChannel(m.ref.channel);
59313
59467
  cache.rememberIdentity(m.sender);
59314
59468
  }
59315
- return { messages: messages.map(messageShape) };
59469
+ return { messages: await runEdge(exports_Effect.forEach(messages, messageShape)) };
59316
59470
  }
59317
59471
  },
59318
59472
  {
@@ -59331,14 +59485,54 @@ var buildToolDefs = (deps, cache) => {
59331
59485
  handler: async (args2) => {
59332
59486
  const messages = await runEdge(exports_Effect.gen(function* () {
59333
59487
  const parsed = yield* exports_Schema.decodeUnknown(ReadThreadArgs)(args2);
59334
- const channelRef = yield* resolveChannel(cache, parsed.channel_name);
59335
- return yield* adapter4.history.readThread(channelRef, yield* decodeThreadName(parsed.thread), yield* parseRange(parsed));
59488
+ const channel = yield* decodeChannelName(parsed.channel_name);
59489
+ return yield* adapter4.history.readThread(channel, yield* decodeThreadName(parsed.thread), yield* parseRange(parsed));
59336
59490
  }));
59337
59491
  for (const m of messages) {
59338
59492
  cache.rememberChannel(m.ref.channel);
59339
59493
  cache.rememberIdentity(m.sender);
59340
59494
  }
59341
- return { messages: messages.map(messageShape) };
59495
+ return { messages: await runEdge(exports_Effect.forEach(messages, messageShape)) };
59496
+ }
59497
+ },
59498
+ {
59499
+ name: "resolve_thread",
59500
+ description: "Mark a thread resolved (channel_name + thread, like read_thread). Idempotent — resolving an already-resolved thread is a no-op. Resolution is a status kept separate from the thread name; read it back as a message's thread.resolved via read_thread / read_channel. Use unresolve_thread to clear it.",
59501
+ inputSchema: {
59502
+ type: "object",
59503
+ properties: {
59504
+ channel_name: { type: "string", description: "Channel the thread lives in" },
59505
+ thread: { type: "string", description: "Thread / topic name within the channel" }
59506
+ },
59507
+ required: ["channel_name", "thread"],
59508
+ additionalProperties: false
59509
+ },
59510
+ handler: async (args2) => {
59511
+ await runEdge(exports_Effect.gen(function* () {
59512
+ const parsed = yield* exports_Schema.decodeUnknown(ThreadResolutionArgs)(args2);
59513
+ yield* adapter4.publisher.resolveThread(yield* decodeChannelName(parsed.channel_name), yield* decodeThreadName(parsed.thread));
59514
+ }));
59515
+ return {};
59516
+ }
59517
+ },
59518
+ {
59519
+ name: "unresolve_thread",
59520
+ description: "Clear a thread's resolved status (channel_name + thread, like read_thread). Idempotent — unresolving a thread that is not resolved is a no-op. The inverse of resolve_thread.",
59521
+ inputSchema: {
59522
+ type: "object",
59523
+ properties: {
59524
+ channel_name: { type: "string", description: "Channel the thread lives in" },
59525
+ thread: { type: "string", description: "Thread / topic name within the channel" }
59526
+ },
59527
+ required: ["channel_name", "thread"],
59528
+ additionalProperties: false
59529
+ },
59530
+ handler: async (args2) => {
59531
+ await runEdge(exports_Effect.gen(function* () {
59532
+ const parsed = yield* exports_Schema.decodeUnknown(ThreadResolutionArgs)(args2);
59533
+ yield* adapter4.publisher.unresolveThread(yield* decodeChannelName(parsed.channel_name), yield* decodeThreadName(parsed.thread));
59534
+ }));
59535
+ return {};
59342
59536
  }
59343
59537
  },
59344
59538
  {
@@ -59365,7 +59559,7 @@ var buildToolDefs = (deps, cache) => {
59365
59559
  const parsed = yield* exports_Schema.decodeUnknown(MessageLinkArgs)(args2);
59366
59560
  const id3 = yield* decodeMessageId(parsed.message_id);
59367
59561
  const cached5 = cache.messageById.get(id3);
59368
- if (cached5?.permalink !== undefined)
59562
+ if (cached5 !== undefined)
59369
59563
  return cached5.permalink;
59370
59564
  const hint = parsed.channel_name === undefined ? undefined : {
59371
59565
  channel: yield* decodeChannelName(parsed.channel_name),
@@ -59515,7 +59709,7 @@ var forkIdleSweep = (cache, intervalMs) => {
59515
59709
  return exports_Effect.asVoid(exports_Effect.forkScoped(exports_Effect.repeat(sweep, exports_Schedule.spaced(exports_Duration.millis(intervalMs)))));
59516
59710
  };
59517
59711
  var createType2DefaultsOnAcquire = (narrowSet, inbox) => {
59518
- const registerIntent = (intent) => exports_Effect.sync(() => narrowSet.add(intent)).pipe(exports_Effect.zipRight(intentToTarget(intent).pipe(exports_Effect.flatMap((target) => inbox.subscribe(target)))));
59712
+ const registerIntent = (intent) => exports_Effect.sync(() => narrowSet.add(intent)).pipe(exports_Effect.zipRight(inbox.subscribe(intentToTarget(intent))));
59519
59713
  return (project3) => registerIntent({ kind: "mentions" }).pipe(exports_Effect.zipRight(project3 !== undefined ? exports_Effect.all([decodeChannelName(project3), decodeThreadName("general")]).pipe(exports_Effect.flatMap(([channelName, threadName3]) => registerIntent({ kind: "thread", channelName, threadName: threadName3 }))) : exports_Effect.void), exports_Effect.catchAll((err) => exports_Effect.logError(`commy plugin: Type-2 default narrow registration failed: ${exports_Cause.pretty(exports_Cause.fail(err))}`)));
59520
59714
  };
59521
59715
  var logType1Failure = (err) => exports_Effect.logError(`commy plugin: Type-1 default narrow registration failed: ${exports_Cause.pretty(exports_Cause.fail(err))}`);
@@ -59523,7 +59717,7 @@ var registerType1DefaultsOnBoot = (inbox, narrowSet, project3) => (project3 !==
59523
59717
  { kind: "mentions" },
59524
59718
  { kind: "new-topics-in-channel", channelName },
59525
59719
  { kind: "thread", channelName, threadName: threadName3 }
59526
- ])) : exports_Effect.succeed([{ kind: "mentions" }])).pipe(exports_Effect.flatMap((intents) => exports_Effect.forEach(intents, (intent) => exports_Effect.sync(() => narrowSet.add(intent)).pipe(exports_Effect.zipRight(intentToTarget(intent).pipe(exports_Effect.flatMap((target) => inbox.subscribe(target)))))).pipe(exports_Effect.as(intents), exports_Effect.catchAll((err) => logType1Failure(err).pipe(exports_Effect.as(intents))))), exports_Effect.catchAll((err) => logType1Failure(err).pipe(exports_Effect.as([{ kind: "mentions" }]))));
59720
+ ])) : exports_Effect.succeed([{ kind: "mentions" }])).pipe(exports_Effect.flatMap((intents) => exports_Effect.forEach(intents, (intent) => exports_Effect.sync(() => narrowSet.add(intent)).pipe(exports_Effect.zipRight(inbox.subscribe(intentToTarget(intent))))).pipe(exports_Effect.as(intents), exports_Effect.catchAll((err) => logType1Failure(err).pipe(exports_Effect.as(intents))))), exports_Effect.catchAll((err) => logType1Failure(err).pipe(exports_Effect.as([{ kind: "mentions" }]))));
59527
59721
  var DEFAULT_CATCHUP_WINDOW_SECONDS = 4 * 3600;
59528
59722
  var buildIdentityCache = (adapter4, botName, ephemeralOnAcquire) => {
59529
59723
  if (botName !== undefined) {
@@ -59544,6 +59738,12 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59544
59738
  const adapter4 = yield* SubstrateAdapter;
59545
59739
  const cursorStore = yield* CursorStoreTag;
59546
59740
  const subscriptionStore = yield* SubscriptionStoreTag;
59741
+ const sessionIdDeferred = yield* SessionId;
59742
+ yield* exports_Effect.flatMap(readBootSessionId, exports_Option.match({
59743
+ onNone: () => exports_Effect.void,
59744
+ onSome: (sessionId) => exports_Deferred.succeed(sessionIdDeferred, sessionId).pipe(exports_Effect.asVoid)
59745
+ }));
59746
+ const feedSessionId = (sessionId) => exports_Deferred.succeed(sessionIdDeferred, sessionId).pipe(exports_Effect.asVoid);
59547
59747
  const fs = yield* exports_FileSystem.FileSystem;
59548
59748
  const loggerLayer = params.loggerLayer ?? stderrLoggerLayer;
59549
59749
  const commandExecutor = yield* exports_CommandExecutor.CommandExecutor;
@@ -59569,15 +59769,14 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59569
59769
  const mcp = buildMcpServer();
59570
59770
  const notifier = params.notifier ?? channelNotifier(mcp);
59571
59771
  const registerType2Defaults = parsed.botName === undefined ? createType2DefaultsOnAcquire(narrowSet, adapter4.inbox) : undefined;
59572
- const restoredSessions = yield* exports_SynchronizedRef.make(exports_HashSet.empty());
59772
+ const restoreOnResume = parsed.botName !== undefined ? exports_Effect.void : restoreSubscriptions({ subscriptionStore, narrowSet, inbox: adapter4.inbox }).pipe(exports_Effect.catchAll((err) => exports_Effect.logError(`commy plugin: subscription restore failed: ${exports_Predicate.isError(err) ? err.message : String(err)}`)), exports_Effect.provide(loggerLayer));
59573
59773
  const seedDefaults = (project3) => registerType2Defaults !== undefined ? registerType2Defaults(project3) : exports_Effect.void;
59574
- const ensureSessionSubscriptions = (sessionId, project3) => parsed.botName !== undefined ? exports_Effect.void : exports_SynchronizedRef.updateEffect(restoredSessions, (seen) => exports_HashSet.has(seen, sessionId) ? exports_Effect.succeed(seen) : restoreOrSeedSubscriptions({
59575
- subscriptionStore,
59576
- narrowSet,
59577
- inbox: adapter4.inbox,
59578
- registerDefaults: seedDefaults
59579
- }, sessionId, project3).pipe(exports_Effect.catchAll((err) => exports_Effect.logError(`commy plugin: subscription restore failed for ${sessionId}: ${exports_Predicate.isError(err) ? err.message : String(err)}`).pipe(exports_Effect.zipRight(seedDefaults(project3)))), exports_Effect.provide(loggerLayer), exports_Effect.as(exports_HashSet.add(seen, sessionId))));
59580
- const persistSessionSubscriptions = (sessionId) => parsed.botName !== undefined ? exports_Effect.void : persistSubscriptions(subscriptionStore, narrowSet, sessionId).pipe(exports_Effect.catchAll((err) => exports_Effect.logError(`commy plugin: subscription persist failed for ${sessionId}: ${exports_Predicate.isError(err) ? err.message : String(err)}`)), exports_Effect.provide(loggerLayer));
59774
+ const seedIfFresh = (sessionId, project3) => parsed.botName !== undefined ? exports_Effect.void : seedDefaultsIfFresh({ subscriptionStore, registerDefaults: seedDefaults }, project3).pipe(exports_Effect.catchAll((err) => exports_Effect.logError(`commy plugin: subscription seed check failed for ${sessionId}: ${exports_Predicate.isError(err) ? err.message : String(err)}`).pipe(exports_Effect.zipRight(seedDefaults(project3)))), exports_Effect.provide(loggerLayer));
59775
+ const ensureSessionSubscriptions = (sessionId, project3) => seedIfFresh(sessionId, project3);
59776
+ const persistSessionSubscriptions = parsed.botName !== undefined ? exports_Effect.void : exports_Deferred.poll(sessionIdDeferred).pipe(exports_Effect.flatMap(exports_Option.match({
59777
+ onNone: () => exports_Effect.void,
59778
+ onSome: () => persistSubscriptions(subscriptionStore, narrowSet).pipe(exports_Effect.catchAll((err) => exports_Effect.logError(`commy plugin: subscription persist failed: ${exports_Predicate.isError(err) ? err.message : String(err)}`)), exports_Effect.provide(loggerLayer))
59779
+ })));
59581
59780
  const ephemeralOnAcquire = parsed.botName === undefined ? (acquired, project3, sessionId) => ensureSessionSubscriptions(sessionId, project3).pipe(exports_Effect.zipRight(catchUpMentions({
59582
59781
  cursorStore,
59583
59782
  inbox: adapter4.inbox,
@@ -59601,6 +59800,9 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59601
59800
  yield* exports_Effect.addFinalizer(() => identityCache.boundIdentityIds().size > 0 ? raceReleaseAgainstTimeout(adapter4.identity.release({ persistent: releasePersistent }), RELEASE_TIMEOUT_MS) : exports_Effect.void);
59602
59801
  const runsIdleSweep = parsed.botName === undefined;
59603
59802
  const subscribedIntents = yield* subscribeFromEnv(adapter4.inbox, narrowSet, parsed);
59803
+ if (parsed.botName === undefined) {
59804
+ narrowSet.beginBuffering();
59805
+ }
59604
59806
  const toolsCache = registerTools(mcp, {
59605
59807
  adapter: adapter4,
59606
59808
  identityCache,
@@ -59608,10 +59810,11 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59608
59810
  projectForCwd,
59609
59811
  ensureSessionSubscriptions,
59610
59812
  persistSessionSubscriptions,
59813
+ feedSessionId,
59611
59814
  downloadFile: (urlPath) => exports_Effect.gen(function* () {
59612
59815
  const result = yield* adapter4.downloadFile(urlPath);
59613
59816
  const dir = yield* fs.makeTempDirectory({ prefix: "commy-dl-" });
59614
- const filePath = join9(dir, basename2(urlPath));
59817
+ const filePath = join10(dir, basename2(urlPath));
59615
59818
  yield* fs.writeFile(filePath, result.data);
59616
59819
  return { filePath, contentType: result.contentType, size: result.data.byteLength };
59617
59820
  }),
@@ -59664,15 +59867,21 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59664
59867
  if (id3 === undefined)
59665
59868
  return exports_Effect.void;
59666
59869
  return cursorStore.write(id3, ts).pipe(exports_Effect.catchAllCause(() => exports_Effect.void));
59870
+ },
59871
+ onDelivery: (target, ts) => {
59872
+ if (parsed.botName !== undefined)
59873
+ return exports_Effect.void;
59874
+ return subscriptionStore.advanceCursor(target, ts).pipe(exports_Effect.catchAllCause(() => exports_Effect.void));
59667
59875
  }
59668
59876
  });
59669
59877
  yield* exports_Effect.addFinalizer(() => pump.cancel);
59670
59878
  if (runsIdleSweep) {
59671
59879
  yield* forkIdleSweep(identityCache, EPHEMERAL_IDLE_SWEEP_INTERVAL_MS);
59672
59880
  }
59881
+ yield* exports_Effect.forkScoped(restoreOnResume);
59673
59882
  yield* exports_Effect.raceFirst(pump.done, params.shutdownSignal ?? exports_Effect.never);
59674
59883
  }));
59675
- var AppLayer = exports_Layer.mergeAll(ZulipAdapterLive, FileCursorStoreLive, FileSubscriptionStoreLive, stderrLoggerLayer);
59884
+ var AppLayer = exports_Layer.mergeAll(ZulipAdapterLive, FileCursorStoreLive, exports_Layer.provideMerge(FileSubscriptionStoreLive, SessionIdLive), stderrLoggerLayer);
59676
59885
  var PlatformLive = exports_Layer.mergeAll(exports_Layer.setConfigProvider(exports_ConfigProvider.fromEnv()), exports_FetchHttpClient.layer, exports_NodeContext.layer);
59677
59886
  var MainLive = exports_Layer.provideMerge(AppLayer, PlatformLive);
59678
59887
  var clientDisconnect = (stdin3) => exports_Effect.async((resume2) => {