@codeforbreakfast/commy-mcp 0.16.0 → 0.18.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 +152 -51
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeforbreakfast/commy-mcp",
3
- "version": "0.16.0",
3
+ "version": "0.18.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, {
@@ -56816,6 +56816,7 @@ var envConfigError = (issues) => new EnvConfigError({
56816
56816
  });
56817
56817
  var SessionIdSchema = exports_Schema.UUID.pipe(exports_Schema.brand("SessionId"));
56818
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());
56819
56820
  var BOT_NAME_RE = /^[a-z][a-z0-9_-]*$/;
56820
56821
  var parseBotName = (raw3) => {
56821
56822
  if (raw3.length === 0 || raw3.length > 40)
@@ -56895,7 +56896,8 @@ var envConfig = exports_Config.all({
56895
56896
  botApiKey: optionalApiKey("COMMY_BOT_API_KEY"),
56896
56897
  subscribe: optionalUserConfig("COMMY_SUBSCRIBE"),
56897
56898
  project: optionalNonEmpty("COMMY_PROJECT"),
56898
- catchupWindowSeconds: optionalNonNegativeInt("COMMY_CATCHUP_WINDOW_SECONDS")
56899
+ catchupWindowSeconds: optionalNonNegativeInt("COMMY_CATCHUP_WINDOW_SECONDS"),
56900
+ downloadDir: optionalNonEmpty("COMMY_DOWNLOAD_DIR")
56899
56901
  }).pipe(exports_Config.mapOrFail((raw3) => {
56900
56902
  if (exports_Option.isSome(raw3.botApiKey) && exports_Option.isNone(raw3.botName)) {
56901
56903
  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"));
@@ -56915,6 +56917,10 @@ var envConfig = exports_Config.all({
56915
56917
  ...exports_Option.match(raw3.catchupWindowSeconds, {
56916
56918
  onNone: () => ({}),
56917
56919
  onSome: (catchupWindowSeconds) => ({ catchupWindowSeconds })
56920
+ }),
56921
+ ...exports_Option.match(raw3.downloadDir, {
56922
+ onNone: () => ({}),
56923
+ onSome: (downloadDir) => ({ downloadDir })
56918
56924
  })
56919
56925
  });
56920
56926
  }));
@@ -57163,6 +57169,35 @@ var createEnsureBound = (deps) => exports_Effect.map(exports_Ref.make({ kind: "i
57163
57169
  return ensureBound;
57164
57170
  });
57165
57171
 
57172
+ // packages/mcp/target-cursor.ts
57173
+ import { homedir as homedir2 } from "node:os";
57174
+ import { join as join8 } from "node:path";
57175
+ var deliveryTargetOf = (message) => exports_Data.struct({
57176
+ channel: message.ref.channel.name,
57177
+ thread: exports_Option.map(message.ref.thread, (observed) => observed.name)
57178
+ });
57179
+ var DeliveryTargetSchema = exports_Schema.Data(exports_Schema.Struct({
57180
+ channel: ChannelNameSchema,
57181
+ thread: exports_Schema.OptionFromNullOr(ThreadNameSchema)
57182
+ }));
57183
+ var TargetCursorsFileSchema = exports_Schema.parseJson(exports_Schema.HashMap({ key: DeliveryTargetSchema, value: TimestampSchema }));
57184
+ var decodeTargetCursorsFile = exports_Schema.decodeUnknown(TargetCursorsFileSchema);
57185
+ var encodeTargetCursorsFile = exports_Schema.encode(TargetCursorsFileSchema);
57186
+ var FILENAME_SAFE2 = /[^a-zA-Z0-9._-]/g;
57187
+ var cursorsFilename = (id3) => `${id3.replace(FILENAME_SAFE2, "_")}.json`;
57188
+ var isNotFound2 = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
57189
+ 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())));
57190
+ var advanceTargetCursor = (fs, dir, id3, target, ts) => {
57191
+ const path = join8(dir, cursorsFilename(id3));
57192
+ 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) => {
57193
+ const prior = exports_HashMap.get(cursors, target);
57194
+ 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)))));
57195
+ }));
57196
+ };
57197
+ var STATE_SEGMENT2 = "commy";
57198
+ var stateBaseConfig2 = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join8(homedir2(), ".local", "state")));
57199
+ var targetCursorDirConfig = stateBaseConfig2.pipe(exports_Config.map((base) => join8(base, STATE_SEGMENT2, "target-cursors")));
57200
+
57166
57201
  // packages/mcp/event-pump.ts
57167
57202
  var originatorId = (event) => exports_Match.value(event).pipe(exports_Match.discriminatorsExhaustive("kind")({
57168
57203
  "message-posted": (message) => message.message.sender.id,
@@ -57218,9 +57253,11 @@ var startEventPump = (deps) => exports_Effect.gen(function* () {
57218
57253
  deps.rememberIdentity(identity6);
57219
57254
  }
57220
57255
  const mentionAdvance = deps.onMention !== undefined && event.kind === "mention-received" ? deps.onMention(event.message.ts) : exports_Effect.void;
57256
+ const deliveryAdvance = deps.onDelivery !== undefined && (event.kind === "message-posted" || event.kind === "mention-received") ? deps.onDelivery(deliveryTargetOf(event.message), event.message.ts) : exports_Effect.void;
57257
+ const advance = exports_Effect.zipRight(mentionAdvance, deliveryAdvance);
57221
57258
  if (recordMessageDelivery(event, seenDelivered) === "duplicate")
57222
- return mentionAdvance;
57223
- return mentionAdvance.pipe(exports_Effect.zipRight(renderEvent(event, botIdentityId)), exports_Effect.flatMap((payload) => exports_Effect.tryPromise({
57259
+ return advance;
57260
+ return advance.pipe(exports_Effect.zipRight(renderEvent(event, botIdentityId)), exports_Effect.flatMap((payload) => exports_Effect.tryPromise({
57224
57261
  try: () => deps.notifier(payload),
57225
57262
  catch: (cause3) => new DispatchFailure({ cause: cause3 })
57226
57263
  })));
@@ -58730,7 +58767,7 @@ class Server extends Protocol {
58730
58767
  }
58731
58768
 
58732
58769
  // packages/mcp/mcp-server.ts
58733
- var PLUGIN_VERSION = "0.16.0";
58770
+ var PLUGIN_VERSION = "0.18.0";
58734
58771
  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.
58735
58772
 
58736
58773
  **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.
@@ -58741,7 +58778,7 @@ var COMMY_INSTRUCTIONS = `**Substrate.** commy is the inter-agent channel: agent
58741
58778
 
58742
58779
  **Links.** Every ref the substrate hands you carries a ready-to-click \`permalink\` — on \`post\` results, \`read_channel\`/\`read_thread\` messages (message \`permalink\` plus \`channel.permalink\` and \`thread.permalink\`), \`list_channels\` (channel \`permalink\`), and inbound \`<channel source="commy">\` frames (\`permalink\` / \`channel_permalink\` / \`thread_permalink\` meta, \`target_permalink\` on reaction frames). **Whenever you show a human a message, channel, or topic reference, render it as that clickable permalink — never a bare name or numeric id.** A human can click a permalink straight to the message; a bare \`#channel > topic\` or message number makes them hunt. When you hold only a message id (e.g. one cited elsewhere, with no permalink to hand), \`message_link(message_id, channel_name?, thread?)\` returns its \`{permalink}\`.
58743
58780
 
58744
- **Tools.** \`post\` (channel; optionally thread or reply), \`react\`/\`unreact\` (emoji on a message), \`subscribe\`/\`unsubscribe\` (live target), \`read_channel\`/\`read_thread\` (history within a range), \`list_channels\` (enumerate channels in the realm), \`message_link\` (canonical permalink for a message id), \`resolve\` (identity by name), \`current_identity\` (passive — never acquires), \`download_file\` (fetch a \`/user_uploads/...\` attachment to a temp file — use Read on the returned path to view images), \`upload_file\` (upload a local file by absolute path; returns a \`reference\` string to embed in a \`post\` body).
58781
+ **Tools.** \`post\` (channel; optionally thread or reply), \`react\`/\`unreact\` (emoji on a message), \`subscribe\`/\`unsubscribe\` (live target), \`read_channel\`/\`read_thread\` (history within a range), \`list_channels\` (enumerate channels in the realm), \`message_link\` (canonical permalink for a message id), \`resolve\` (identity by name), \`current_identity\` (passive — never acquires), \`download_file\` (fetch a \`/user_uploads/...\` attachment to a temp file — rooted under the operator-set \`COMMY_DOWNLOAD_DIR\` when configured so it lands somewhere you can Read, else \`$TMPDIR\`; use Read on the returned path to view images), \`upload_file\` (upload a local file by absolute path; returns a \`reference\` string to embed in a \`post\` body).
58745
58782
 
58746
58783
  **session_id.** Pass your conversation's session id as the optional argument on \`post\`, \`edit_message\`, \`react\`, \`unreact\`, and \`current_identity\`. **Must be a UUID** (e.g. \`crypto.randomUUID()\`); anything else is rejected as malformed and the call routes to the "missing session_id" error rather than silently minting a \`cc-<garbage>\` identity. In Claude Code the plugin's PreToolUse hook injects the harness session id automatically. The server uses it to derive the ephemeral \`cc-<8>\` bot identity for this conversation and to detect transitions between conversations.`;
58747
58784
  var buildMcpServer = () => new Server({ name: "commy", version: PLUGIN_VERSION }, {
@@ -58793,6 +58830,8 @@ var mentionsMatches = (intents, mentions, botIdentityId) => {
58793
58830
  var createNarrowSet = () => {
58794
58831
  let intents = exports_HashSet.empty();
58795
58832
  let seenTopics = exports_HashSet.empty();
58833
+ const pending3 = [];
58834
+ let recording = false;
58796
58835
  const newTopicsMatches = (ref) => exports_Option.match(threadKey(ref), {
58797
58836
  onNone: () => false,
58798
58837
  onSome: (tk) => {
@@ -58807,15 +58846,30 @@ var createNarrowSet = () => {
58807
58846
  const messagePostedMatches = (ref) => refMatches(ref, intents) || newTopicsMatches(ref);
58808
58847
  return {
58809
58848
  add: (intent) => {
58810
- intents = exports_HashSet.add(intents, intentKey(intent));
58849
+ const key = intentKey(intent);
58850
+ intents = exports_HashSet.add(intents, key);
58851
+ if (recording)
58852
+ pending3.push({ op: "add", key });
58811
58853
  },
58812
58854
  remove: (intent) => {
58813
- intents = exports_HashSet.remove(intents, intentKey(intent));
58855
+ const key = intentKey(intent);
58856
+ intents = exports_HashSet.remove(intents, key);
58857
+ if (recording)
58858
+ pending3.push({ op: "remove", key });
58814
58859
  },
58815
58860
  size: () => exports_HashSet.size(intents),
58816
58861
  intents: () => exports_Array.fromIterable(intents).map(keyToIntent),
58817
- replace: (next4) => {
58818
- intents = exports_HashSet.fromIterable(next4.map(intentKey));
58862
+ beginBuffering: () => {
58863
+ recording = true;
58864
+ },
58865
+ load: (base) => {
58866
+ const seeded = exports_Option.match(base, {
58867
+ onNone: () => intents,
58868
+ onSome: (next4) => exports_HashSet.fromIterable(next4.map(intentKey))
58869
+ });
58870
+ intents = pending3.reduce((acc, delta) => delta.op === "add" ? exports_HashSet.add(acc, delta.key) : exports_HashSet.remove(acc, delta.key), seeded);
58871
+ pending3.length = 0;
58872
+ recording = false;
58819
58873
  },
58820
58874
  matches: (event, botIdentityId) => exports_Match.value(event).pipe(exports_Match.discriminatorsExhaustive("kind")({
58821
58875
  "message-posted": (e) => messagePostedMatches(e.message.ref) || mentionsMatches(intents, e.message.mentions, botIdentityId),
@@ -58830,24 +58884,28 @@ var createNarrowSet = () => {
58830
58884
  var DEFAULT_TIMEOUT_MS = 5000;
58831
58885
  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));
58832
58886
 
58887
+ // packages/mcp/session-id.ts
58888
+ class SessionId extends exports_Context.Tag("commy/mcp/SessionId")() {
58889
+ }
58890
+ var SessionIdLive = exports_Layer.effect(SessionId, exports_Deferred.make());
58891
+
58833
58892
  // packages/mcp/subscription-restore.ts
58834
- var applyRestored = (deps, intents) => exports_Effect.sync(() => deps.narrowSet.replace(intents)).pipe(exports_Effect.zipRight(exports_Effect.forEach(intents, (intent) => deps.inbox.subscribe(intentToTarget(intent)), {
58893
+ 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)), {
58835
58894
  discard: true
58836
58895
  })));
58837
- var seedDefaultsIfFresh = (deps, sessionId, project3) => deps.subscriptionStore.read(sessionId).pipe(exports_Effect.flatMap(exports_Option.match({
58896
+ var seedDefaultsIfFresh = (deps, project3) => deps.subscriptionStore.read().pipe(exports_Effect.flatMap(exports_Option.match({
58838
58897
  onNone: () => deps.registerDefaults(project3),
58839
58898
  onSome: () => exports_Effect.void
58840
58899
  })));
58841
- var restoreSubscriptions = (deps, sessionId) => deps.subscriptionStore.read(sessionId).pipe(exports_Effect.flatMap(exports_Option.match({
58842
- onNone: () => exports_Effect.void,
58900
+ var restoreSubscriptions = (deps) => deps.subscriptionStore.read().pipe(exports_Effect.flatMap(exports_Option.match({
58901
+ onNone: () => exports_Effect.sync(() => deps.narrowSet.load(exports_Option.none())),
58843
58902
  onSome: (intents) => applyRestored(deps, intents)
58844
58903
  })));
58845
- var makeSessionRestore = (deps) => exports_Effect.map(exports_Deferred.make(), (known) => (sessionId) => exports_Deferred.succeed(known, sessionId).pipe(exports_Effect.flatMap((won) => won ? restoreSubscriptions(deps, sessionId) : exports_Effect.void)));
58846
- var persistSubscriptions = (store, narrowSet, sessionId) => store.write(sessionId, narrowSet.intents());
58904
+ var persistSubscriptions = (store, narrowSet) => store.write(narrowSet.intents());
58847
58905
 
58848
58906
  // packages/mcp/subscription-store.ts
58849
- import { homedir as homedir2 } from "node:os";
58850
- import { join as join8 } from "node:path";
58907
+ import { homedir as homedir3 } from "node:os";
58908
+ import { join as join9 } from "node:path";
58851
58909
  class SubscriptionStoreTag extends exports_Context.Tag("SubscriptionStore")() {
58852
58910
  }
58853
58911
  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({
@@ -58857,24 +58915,33 @@ var SubscribeIntentSchema = exports_Schema.Union(exports_Schema.Struct({ kind: e
58857
58915
  }), exports_Schema.Struct({ kind: exports_Schema.Literal("new-topics-in-channel"), channelName: ChannelNameSchema }));
58858
58916
  var SubscriptionsFileSchema = exports_Schema.parseJson(exports_Schema.Array(SubscribeIntentSchema));
58859
58917
  var decodeSubscriptionsFile = exports_Schema.decodeUnknown(SubscriptionsFileSchema);
58860
- var FILENAME_SAFE2 = /[^a-zA-Z0-9._-]/g;
58918
+ var FILENAME_SAFE3 = /[^a-zA-Z0-9._-]/g;
58861
58919
  var subscriptionFilename = (id3) => {
58862
- const safe = id3.replace(FILENAME_SAFE2, "_");
58920
+ const safe = id3.replace(FILENAME_SAFE3, "_");
58863
58921
  return `${safe}.json`;
58864
58922
  };
58865
- var isNotFound2 = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
58866
- 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())));
58923
+ var isNotFound3 = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
58924
+ 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())));
58867
58925
  var createFileSubscriptionStore = (deps) => {
58868
- const { dir, fs } = deps;
58869
- const pathFor = (id3) => join8(dir, subscriptionFilename(id3));
58870
- const read3 = (id3) => readSubscriptions(fs, pathFor(id3));
58871
- const write4 = (id3, intents) => fs.makeDirectory(dir, { recursive: true }).pipe(exports_Effect.zipRight(fs.writeFileString(pathFor(id3), JSON.stringify(intents))));
58872
- return { read: read3, write: write4 };
58873
- };
58874
- var STATE_SEGMENT2 = "commy";
58875
- var stateBaseConfig2 = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join8(homedir2(), ".local", "state")));
58876
- var subscriptionDirConfig = stateBaseConfig2.pipe(exports_Config.map((base) => join8(base, STATE_SEGMENT2, "subscriptions")));
58877
- 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 }))));
58926
+ const { dir, fs, cursorDir, session } = deps;
58927
+ const pathFor = (id3) => join9(dir, subscriptionFilename(id3));
58928
+ const read3 = () => exports_Effect.flatMap(exports_Deferred.await(session), (id3) => readSubscriptions(fs, pathFor(id3)));
58929
+ 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)))));
58930
+ const advanceCursor = (target, ts) => exports_Effect.flatMap(exports_Deferred.poll(session), exports_Option.match({
58931
+ onNone: () => exports_Effect.void,
58932
+ onSome: (awaitId) => exports_Effect.flatMap(awaitId, (id3) => advanceTargetCursor(fs, cursorDir, id3, target, ts))
58933
+ }));
58934
+ return { read: read3, write: write4, advanceCursor };
58935
+ };
58936
+ var STATE_SEGMENT3 = "commy";
58937
+ var stateBaseConfig3 = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join9(homedir3(), ".local", "state")));
58938
+ var subscriptionDirConfig = stateBaseConfig3.pipe(exports_Config.map((base) => join9(base, STATE_SEGMENT3, "subscriptions")));
58939
+ var FileSubscriptionStoreLive = exports_Layer.effect(SubscriptionStoreTag, exports_Effect.all([
58940
+ exports_FileSystem.FileSystem,
58941
+ exports_Effect.orDie(subscriptionDirConfig),
58942
+ exports_Effect.orDie(targetCursorDirConfig),
58943
+ SessionId
58944
+ ]).pipe(exports_Effect.map(([fs, dir, cursorDir, session]) => createFileSubscriptionStore({ dir, fs, cursorDir, session }))));
58878
58945
 
58879
58946
  // packages/mcp/tools.ts
58880
58947
  var emptyObjectSchema = {
@@ -59048,11 +59115,17 @@ var buildToolDefs = (deps, cache) => {
59048
59115
  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)."
59049
59116
  };
59050
59117
  const readSessionId = (args2) => exports_Option.getOrUndefined(parseSessionId(args2["session_id"]));
59118
+ const feedSession = (sessionId) => sessionId === undefined || deps.feedSessionId === undefined ? exports_Effect.void : deps.feedSessionId(sessionId);
59051
59119
  const readCwd = (args2) => {
59052
59120
  const raw3 = args2["cwd"];
59053
59121
  return exports_Predicate.isString(raw3) ? raw3 : undefined;
59054
59122
  };
59055
59123
  const projectForArgs = (args2) => projectForCwd(readCwd(args2));
59124
+ const ensureBoundForArgs = (args2) => exports_Effect.gen(function* () {
59125
+ const sessionId = readSessionId(args2);
59126
+ yield* feedSession(sessionId);
59127
+ return yield* identityCache.ensureBoundFor(sessionId, yield* projectForArgs(args2));
59128
+ });
59056
59129
  const stickyThreadEngagement = async (channel, threadName3) => {
59057
59130
  if (exports_Option.isNone(threadName3))
59058
59131
  return;
@@ -59074,8 +59147,7 @@ var buildToolDefs = (deps, cache) => {
59074
59147
  additionalProperties: false
59075
59148
  },
59076
59149
  handler: async (args2) => {
59077
- const sessionId = readSessionId(args2);
59078
- const ensureBound = await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(sessionId, project3)));
59150
+ const ensureBound = await runEdge(ensureBoundForArgs(args2));
59079
59151
  const current2 = ensureBound.current();
59080
59152
  if (current2 === undefined) {
59081
59153
  return { state: "unbound", identity: null };
@@ -59181,7 +59253,7 @@ var buildToolDefs = (deps, cache) => {
59181
59253
  additionalProperties: false
59182
59254
  },
59183
59255
  handler: async (args2) => {
59184
- await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(readSessionId(args2), project3)).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59256
+ await runEdge(ensureBoundForArgs(args2).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59185
59257
  const ref = await runEdge(exports_Effect.gen(function* () {
59186
59258
  const { channel_name, body, thread, mentions, reply_to } = yield* exports_Schema.decodeUnknown(PostArgs)(args2);
59187
59259
  const channel = yield* decodeChannelName(channel_name);
@@ -59235,7 +59307,7 @@ var buildToolDefs = (deps, cache) => {
59235
59307
  additionalProperties: false
59236
59308
  },
59237
59309
  handler: async (args2) => {
59238
- await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(readSessionId(args2), project3)).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59310
+ await runEdge(ensureBoundForArgs(args2).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59239
59311
  const ref = await runEdge(exports_Effect.gen(function* () {
59240
59312
  const { message_id, body, channel_name } = yield* exports_Schema.decodeUnknown(EditMessageArgs)(args2);
59241
59313
  const target = yield* reconstructMessageRef(cache, message_id, channel_name);
@@ -59269,7 +59341,7 @@ var buildToolDefs = (deps, cache) => {
59269
59341
  additionalProperties: false
59270
59342
  },
59271
59343
  handler: async (args2) => {
59272
- await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(readSessionId(args2), project3)).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59344
+ await runEdge(ensureBoundForArgs(args2).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59273
59345
  const { ref, threadName: threadName3 } = await runEdge(exports_Effect.gen(function* () {
59274
59346
  const { message_id, emoji: emoji2, channel_name, thread } = yield* exports_Schema.decodeUnknown(ReactArgs)(args2);
59275
59347
  const target = yield* reconstructMessageRef(cache, message_id, channel_name);
@@ -59300,7 +59372,7 @@ var buildToolDefs = (deps, cache) => {
59300
59372
  additionalProperties: false
59301
59373
  },
59302
59374
  handler: async (args2) => {
59303
- await runEdge(exports_Effect.flatMap(projectForArgs(args2), (project3) => identityCache.ensureBoundFor(readSessionId(args2), project3)).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59375
+ await runEdge(ensureBoundForArgs(args2).pipe(exports_Effect.flatMap((ensureBound) => ensureBound())));
59304
59376
  await runEdge(exports_Effect.gen(function* () {
59305
59377
  const { message_id, emoji: emoji2, channel_name } = yield* exports_Schema.decodeUnknown(ReactArgs)(args2);
59306
59378
  const ref = yield* reconstructMessageRef(cache, message_id, channel_name);
@@ -59330,13 +59402,14 @@ var buildToolDefs = (deps, cache) => {
59330
59402
  const { target: raw3 } = yield* exports_Schema.decodeUnknown(SubscribeArgs)(args2);
59331
59403
  const intent = yield* parseSubscribeTarget(raw3);
59332
59404
  const sessionId = readSessionId(args2);
59405
+ yield* feedSession(sessionId);
59333
59406
  if (sessionId !== undefined && deps.ensureSessionSubscriptions !== undefined) {
59334
59407
  yield* deps.ensureSessionSubscriptions(sessionId, yield* projectForArgs(args2));
59335
59408
  }
59336
59409
  narrowSet.add(intent);
59337
59410
  yield* adapter4.inbox.subscribe(intentToTarget(intent));
59338
- if (sessionId !== undefined && deps.persistSessionSubscriptions !== undefined) {
59339
- yield* deps.persistSessionSubscriptions(sessionId);
59411
+ if (deps.persistSessionSubscriptions !== undefined) {
59412
+ yield* deps.persistSessionSubscriptions;
59340
59413
  }
59341
59414
  }));
59342
59415
  return {};
@@ -59363,13 +59436,14 @@ var buildToolDefs = (deps, cache) => {
59363
59436
  const { target: raw3 } = yield* exports_Schema.decodeUnknown(SubscribeArgs)(args2);
59364
59437
  const intent = yield* parseSubscribeTarget(raw3);
59365
59438
  const sessionId = readSessionId(args2);
59439
+ yield* feedSession(sessionId);
59366
59440
  if (sessionId !== undefined && deps.ensureSessionSubscriptions !== undefined) {
59367
59441
  yield* deps.ensureSessionSubscriptions(sessionId, yield* projectForArgs(args2));
59368
59442
  }
59369
59443
  narrowSet.remove(intent);
59370
59444
  yield* adapter4.inbox.unsubscribe(intentToTarget(intent));
59371
- if (sessionId !== undefined && deps.persistSessionSubscriptions !== undefined) {
59372
- yield* deps.persistSessionSubscriptions(sessionId);
59445
+ if (deps.persistSessionSubscriptions !== undefined) {
59446
+ yield* deps.persistSessionSubscriptions;
59373
59447
  }
59374
59448
  }));
59375
59449
  return {};
@@ -59533,7 +59607,7 @@ var buildToolDefs = (deps, cache) => {
59533
59607
  const download = deps.downloadFile;
59534
59608
  optionalTools.push({
59535
59609
  name: "download_file",
59536
- description: "Download a Zulip user upload by its /user_uploads/... path (visible in message bodies). Writes the file to a temp path and returns {file_path, content_type, size}. Use the Read tool on the returned file_path to view images.",
59610
+ description: "Download a Zulip user upload by its /user_uploads/... path (visible in message bodies). Writes the file into a fresh temp directory and returns {file_path, content_type, size}. The temp directory is created under the operator-set COMMY_DOWNLOAD_DIR when configured (so the file lands in a directory you can Read), otherwise under $TMPDIR. Use the Read tool on the returned file_path to view images.",
59537
59611
  inputSchema: {
59538
59612
  type: "object",
59539
59613
  properties: {
@@ -59669,7 +59743,22 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59669
59743
  const adapter4 = yield* SubstrateAdapter;
59670
59744
  const cursorStore = yield* CursorStoreTag;
59671
59745
  const subscriptionStore = yield* SubscriptionStoreTag;
59746
+ const sessionIdDeferred = yield* SessionId;
59747
+ yield* exports_Effect.flatMap(readBootSessionId, exports_Option.match({
59748
+ onNone: () => exports_Effect.void,
59749
+ onSome: (sessionId) => exports_Deferred.succeed(sessionIdDeferred, sessionId).pipe(exports_Effect.asVoid)
59750
+ }));
59751
+ const feedSessionId = (sessionId) => exports_Deferred.succeed(sessionIdDeferred, sessionId).pipe(exports_Effect.asVoid);
59672
59752
  const fs = yield* exports_FileSystem.FileSystem;
59753
+ if (parsed.downloadDir !== undefined) {
59754
+ const base = parsed.downloadDir;
59755
+ const baseStat = yield* fs.stat(base).pipe(exports_Effect.option);
59756
+ if (exports_Option.isNone(baseStat) || baseStat.value.type !== "Directory") {
59757
+ return yield* envConfigError([
59758
+ `COMMY_DOWNLOAD_DIR is set to ${base} but is not an existing directory`
59759
+ ]);
59760
+ }
59761
+ }
59673
59762
  const loggerLayer = params.loggerLayer ?? stderrLoggerLayer;
59674
59763
  const commandExecutor = yield* exports_CommandExecutor.CommandExecutor;
59675
59764
  const readGitContext2 = params.readGitContext ?? ((cwd) => readGitContext(cwd).pipe(exports_Effect.provideService(exports_CommandExecutor.CommandExecutor, commandExecutor)));
@@ -59694,12 +59783,14 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59694
59783
  const mcp = buildMcpServer();
59695
59784
  const notifier = params.notifier ?? channelNotifier(mcp);
59696
59785
  const registerType2Defaults = parsed.botName === undefined ? createType2DefaultsOnAcquire(narrowSet, adapter4.inbox) : undefined;
59697
- const sessionRestoreFeed = parsed.botName === undefined ? yield* makeSessionRestore({ subscriptionStore, narrowSet, inbox: adapter4.inbox }) : undefined;
59786
+ 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));
59698
59787
  const seedDefaults = (project3) => registerType2Defaults !== undefined ? registerType2Defaults(project3) : exports_Effect.void;
59699
- const feedSession = (sessionId) => sessionRestoreFeed === undefined ? exports_Effect.void : sessionRestoreFeed(sessionId).pipe(exports_Effect.catchAll((err) => exports_Effect.logError(`commy plugin: subscription restore failed for ${sessionId}: ${exports_Predicate.isError(err) ? err.message : String(err)}`)), exports_Effect.provide(loggerLayer));
59700
- const seedIfFresh = (sessionId, project3) => parsed.botName !== undefined ? exports_Effect.void : seedDefaultsIfFresh({ subscriptionStore, registerDefaults: seedDefaults }, sessionId, 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));
59701
- const ensureSessionSubscriptions = (sessionId, project3) => feedSession(sessionId).pipe(exports_Effect.zipRight(seedIfFresh(sessionId, project3)));
59702
- 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));
59788
+ 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));
59789
+ const ensureSessionSubscriptions = (sessionId, project3) => seedIfFresh(sessionId, project3);
59790
+ const persistSessionSubscriptions = parsed.botName !== undefined ? exports_Effect.void : exports_Deferred.poll(sessionIdDeferred).pipe(exports_Effect.flatMap(exports_Option.match({
59791
+ onNone: () => exports_Effect.void,
59792
+ 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))
59793
+ })));
59703
59794
  const ephemeralOnAcquire = parsed.botName === undefined ? (acquired, project3, sessionId) => ensureSessionSubscriptions(sessionId, project3).pipe(exports_Effect.zipRight(catchUpMentions({
59704
59795
  cursorStore,
59705
59796
  inbox: adapter4.inbox,
@@ -59723,6 +59814,9 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59723
59814
  yield* exports_Effect.addFinalizer(() => identityCache.boundIdentityIds().size > 0 ? raceReleaseAgainstTimeout(adapter4.identity.release({ persistent: releasePersistent }), RELEASE_TIMEOUT_MS) : exports_Effect.void);
59724
59815
  const runsIdleSweep = parsed.botName === undefined;
59725
59816
  const subscribedIntents = yield* subscribeFromEnv(adapter4.inbox, narrowSet, parsed);
59817
+ if (parsed.botName === undefined) {
59818
+ narrowSet.beginBuffering();
59819
+ }
59726
59820
  const toolsCache = registerTools(mcp, {
59727
59821
  adapter: adapter4,
59728
59822
  identityCache,
@@ -59730,10 +59824,11 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59730
59824
  projectForCwd,
59731
59825
  ensureSessionSubscriptions,
59732
59826
  persistSessionSubscriptions,
59827
+ feedSessionId,
59733
59828
  downloadFile: (urlPath) => exports_Effect.gen(function* () {
59734
59829
  const result = yield* adapter4.downloadFile(urlPath);
59735
- const dir = yield* fs.makeTempDirectory({ prefix: "commy-dl-" });
59736
- const filePath = join9(dir, basename2(urlPath));
59830
+ const dir = yield* fs.makeTempDirectory(parsed.downloadDir === undefined ? { prefix: "commy-dl-" } : { directory: parsed.downloadDir, prefix: "commy-dl-" });
59831
+ const filePath = join10(dir, basename2(urlPath));
59737
59832
  yield* fs.writeFile(filePath, result.data);
59738
59833
  return { filePath, contentType: result.contentType, size: result.data.byteLength };
59739
59834
  }),
@@ -59786,15 +59881,21 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59786
59881
  if (id3 === undefined)
59787
59882
  return exports_Effect.void;
59788
59883
  return cursorStore.write(id3, ts).pipe(exports_Effect.catchAllCause(() => exports_Effect.void));
59884
+ },
59885
+ onDelivery: (target, ts) => {
59886
+ if (parsed.botName !== undefined)
59887
+ return exports_Effect.void;
59888
+ return subscriptionStore.advanceCursor(target, ts).pipe(exports_Effect.catchAllCause(() => exports_Effect.void));
59789
59889
  }
59790
59890
  });
59791
59891
  yield* exports_Effect.addFinalizer(() => pump.cancel);
59792
59892
  if (runsIdleSweep) {
59793
59893
  yield* forkIdleSweep(identityCache, EPHEMERAL_IDLE_SWEEP_INTERVAL_MS);
59794
59894
  }
59895
+ yield* exports_Effect.forkScoped(restoreOnResume);
59795
59896
  yield* exports_Effect.raceFirst(pump.done, params.shutdownSignal ?? exports_Effect.never);
59796
59897
  }));
59797
- var AppLayer = exports_Layer.mergeAll(ZulipAdapterLive, FileCursorStoreLive, FileSubscriptionStoreLive, stderrLoggerLayer);
59898
+ var AppLayer = exports_Layer.mergeAll(ZulipAdapterLive, FileCursorStoreLive, exports_Layer.provideMerge(FileSubscriptionStoreLive, SessionIdLive), stderrLoggerLayer);
59798
59899
  var PlatformLive = exports_Layer.mergeAll(exports_Layer.setConfigProvider(exports_ConfigProvider.fromEnv()), exports_FetchHttpClient.layer, exports_NodeContext.layer);
59799
59900
  var MainLive = exports_Layer.provideMerge(AppLayer, PlatformLive);
59800
59901
  var clientDisconnect = (stdin3) => exports_Effect.async((resume2) => {