@codeforbreakfast/commy-mcp 0.25.2 → 0.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +107 -54
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeforbreakfast/commy-mcp",
3
- "version": "0.25.2",
3
+ "version": "0.25.3",
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
@@ -9501,6 +9501,7 @@ var some3 = /* @__PURE__ */ dual(2, (self, f) => {
9501
9501
  }
9502
9502
  return found;
9503
9503
  });
9504
+ var values2 = (self) => keys2(self._keyMap);
9504
9505
  var size2 = (self) => size(self._keyMap);
9505
9506
  var beginMutation2 = (self) => makeImpl2(beginMutation(self._keyMap));
9506
9507
  var endMutation2 = (self) => {
@@ -9527,6 +9528,18 @@ var union2 = /* @__PURE__ */ dual(2, (self, that) => mutate(empty5(), (set) => {
9527
9528
  }));
9528
9529
  var forEach2 = /* @__PURE__ */ dual(2, (self, f) => forEach(self._keyMap, (_, k) => f(k)));
9529
9530
  var reduce4 = /* @__PURE__ */ dual(3, (self, zero, f) => reduce3(self._keyMap, zero, (z, _, a) => f(z, a)));
9531
+ var filter4 = /* @__PURE__ */ dual(2, (self, f) => {
9532
+ return mutate(empty5(), (set) => {
9533
+ const iterator = values2(self);
9534
+ let next;
9535
+ while (!(next = iterator.next()).done) {
9536
+ const value = next.value;
9537
+ if (f(value)) {
9538
+ add(set, value);
9539
+ }
9540
+ }
9541
+ });
9542
+ });
9530
9543
 
9531
9544
  // node_modules/.bun/effect@3.22.2/node_modules/effect/dist/esm/HashSet.js
9532
9545
  var empty6 = empty5;
@@ -9540,6 +9553,7 @@ var remove5 = remove4;
9540
9553
  var difference3 = difference2;
9541
9554
  var union3 = union2;
9542
9555
  var reduce5 = reduce4;
9556
+ var filter5 = filter4;
9543
9557
 
9544
9558
  // node_modules/.bun/effect@3.22.2/node_modules/effect/dist/esm/internal/opCodes/cause.js
9545
9559
  var OP_DIE = "Die";
@@ -23976,7 +23990,7 @@ var toFilterParseIssue = (out, ast, input) => {
23976
23990
  }
23977
23991
  return none2();
23978
23992
  };
23979
- function filter8(predicate, annotations) {
23993
+ function filter9(predicate, annotations) {
23980
23994
  return (self) => {
23981
23995
  function filter(input, options, ast) {
23982
23996
  return toFilterParseIssue(predicate(input, options, ast), ast, input);
@@ -24001,7 +24015,7 @@ var transform2 = /* @__PURE__ */ dual((args) => isSchema(args[0]) && isSchema(ar
24001
24015
  encode: (toI, _options, _ast, toA) => succeed11(options.encode(toI, toA))
24002
24016
  }));
24003
24017
  var MinLengthSchemaId2 = MinLengthSchemaId;
24004
- var minLength = (minLength, annotations) => (self) => self.pipe(filter8((a) => a.length >= minLength, {
24018
+ var minLength = (minLength, annotations) => (self) => self.pipe(filter9((a) => a.length >= minLength, {
24005
24019
  schemaId: MinLengthSchemaId2,
24006
24020
  title: `minLength(${minLength})`,
24007
24021
  description: `a string at least ${minLength} character(s) long`,
@@ -24013,7 +24027,7 @@ var minLength = (minLength, annotations) => (self) => self.pipe(filter8((a) => a
24013
24027
  var PatternSchemaId = /* @__PURE__ */ Symbol.for("effect/SchemaId/Pattern");
24014
24028
  var pattern = (regex, annotations) => (self) => {
24015
24029
  const source = regex.source;
24016
- return self.pipe(filter8((a) => {
24030
+ return self.pipe(filter9((a) => {
24017
24031
  regex.lastIndex = 0;
24018
24032
  return regex.test(a);
24019
24033
  }, {
@@ -24095,7 +24109,7 @@ class URL$ extends (/* @__PURE__ */ transformOrFail(String$.annotations({
24095
24109
  })) {
24096
24110
  }
24097
24111
  var GreaterThanOrEqualToSchemaId2 = GreaterThanOrEqualToSchemaId;
24098
- var greaterThanOrEqualTo3 = (minimum, annotations) => (self) => self.pipe(filter8((a) => a >= minimum, {
24112
+ var greaterThanOrEqualTo3 = (minimum, annotations) => (self) => self.pipe(filter9((a) => a >= minimum, {
24099
24113
  schemaId: GreaterThanOrEqualToSchemaId2,
24100
24114
  title: `greaterThanOrEqualTo(${minimum})`,
24101
24115
  description: minimum === 0 ? "a non-negative number" : `a number greater than or equal to ${minimum}`,
@@ -24105,7 +24119,7 @@ var greaterThanOrEqualTo3 = (minimum, annotations) => (self) => self.pipe(filter
24105
24119
  ...annotations
24106
24120
  }));
24107
24121
  var IntSchemaId2 = IntSchemaId;
24108
- var int = (annotations) => (self) => self.pipe(filter8((a) => Number.isSafeInteger(a), {
24122
+ var int = (annotations) => (self) => self.pipe(filter9((a) => Number.isSafeInteger(a), {
24109
24123
  schemaId: IntSchemaId2,
24110
24124
  title: "int",
24111
24125
  description: "an integer",
@@ -30394,7 +30408,7 @@ var runMain2 = runMain;
30394
30408
 
30395
30409
  // node_modules/.bun/@effect+platform-node@0.108.2+1edbab697a2570fc/node_modules/@effect/platform-node/dist/esm/NodeRuntime.js
30396
30410
  var runMain3 = runMain2;
30397
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
30411
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
30398
30412
  import process2 from "node:process";
30399
30413
 
30400
30414
  // node_modules/.bun/zod@4.6.5/node_modules/zod/v4/core/util.js
@@ -36330,7 +36344,7 @@ function preprocess(fn, schema) {
36330
36344
  function datetime2(params) {
36331
36345
  return _isoDateTime(ZodISODateTime, params);
36332
36346
  }
36333
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
36347
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
36334
36348
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
36335
36349
  var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
36336
36350
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
@@ -37165,7 +37179,7 @@ class UrlElicitationRequiredError extends McpError {
37165
37179
  }
37166
37180
  }
37167
37181
 
37168
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
37182
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
37169
37183
  var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
37170
37184
 
37171
37185
  class ReadBuffer {
@@ -37205,7 +37219,7 @@ function serializeMessage(message) {
37205
37219
  `;
37206
37220
  }
37207
37221
 
37208
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
37222
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
37209
37223
  class StdioServerTransport {
37210
37224
  constructor(_stdin = process2.stdin, _stdout = process2.stdout, options) {
37211
37225
  this._stdin = _stdin;
@@ -37899,7 +37913,7 @@ var readWindow = (options) => {
37899
37913
 
37900
37914
  // packages/zulip/http.ts
37901
37915
  var decodeUrl = decodeEither(URL$);
37902
- var realmUrlSchema = String$.pipe(filter8((value) => decodeUrl(value).pipe(match({
37916
+ var realmUrlSchema = String$.pipe(filter9((value) => decodeUrl(value).pipe(match({
37903
37917
  onLeft: () => false,
37904
37918
  onRight: (url) => url.protocol === "http:" || url.protocol === "https:"
37905
37919
  })), {
@@ -38660,10 +38674,14 @@ var zulipAdapter = (config) => gen2(function* () {
38660
38674
  unsubscribe: (target) => boundHttp().pipe(flatMap10((http) => suspend4(() => {
38661
38675
  const channel = channelOf(target);
38662
38676
  return modify6(inboxRef, (state) => {
38663
- const next = hasProperty(target, "kind") ? {
38664
- ...state,
38677
+ const dropNewTopics = {
38665
38678
  newTopicsChannels: remove5(state.newTopicsChannels, channel),
38666
38679
  seenTopicsByChannel: remove6(state.seenTopicsByChannel, channel)
38680
+ };
38681
+ const next = hasProperty(target, "kind") ? { ...state, ...dropNewTopics } : isString(target) ? {
38682
+ ...state,
38683
+ ...dropNewTopics,
38684
+ heldNarrows: filter5(state.heldNarrows, (narrow) => narrow.channel !== channel)
38667
38685
  } : {
38668
38686
  ...state,
38669
38687
  heldNarrows: remove5(state.heldNarrows, heldNarrowOf(target))
@@ -39051,11 +39069,12 @@ var optionalApiKey = (key) => {
39051
39069
  }))
39052
39070
  })));
39053
39071
  };
39072
+ var botNameConfig = optionalBotName("COMMY_BOT_NAME");
39054
39073
  var envConfig = all3({
39055
39074
  realmUrl: requiredBrand("ZULIP_SITE", realmUrlSchema, (v) => v),
39056
39075
  minterEmail: requiredBrand("ZULIP_MINTER_EMAIL", botEmailSchema, (v) => v),
39057
39076
  minterApiKey: redacted2(requiredBrand("ZULIP_MINTER_API_KEY", apiKeySchema, (v) => v)),
39058
- botName: optionalBotName("COMMY_BOT_NAME"),
39077
+ botName: botNameConfig,
39059
39078
  botApiKey: optionalApiKey("COMMY_BOT_API_KEY"),
39060
39079
  subscribe: optionalUserConfig("COMMY_SUBSCRIBE"),
39061
39080
  project: optionalNonEmpty("COMMY_PROJECT"),
@@ -39514,7 +39533,7 @@ var createEphemeralIdentityCache = (deps) => map18(make49(undefined), (slotRef)
39514
39533
  releaseAllBound: () => releaseBoundSlot
39515
39534
  };
39516
39535
  });
39517
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
39536
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
39518
39537
  function isZ4Schema(s) {
39519
39538
  const schema = s;
39520
39539
  return !!schema._zod;
@@ -39577,7 +39596,7 @@ function getLiteralValue(schema) {
39577
39596
  return;
39578
39597
  }
39579
39598
 
39580
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
39599
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
39581
39600
  function isTerminal(status) {
39582
39601
  return status === "completed" || status === "failed" || status === "cancelled";
39583
39602
  }
@@ -39586,7 +39605,7 @@ function isTerminal(status) {
39586
39605
  var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
39587
39606
  // node_modules/.bun/zod-to-json-schema@3.25.2+fff7ddf946fac095/node_modules/zod-to-json-schema/dist/esm/parsers/string.js
39588
39607
  var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
39589
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
39608
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
39590
39609
  function getMethodLiteral(schema) {
39591
39610
  const shape = getObjectShape(schema);
39592
39611
  const methodSchema = shape?.method;
@@ -39607,7 +39626,7 @@ function parseWithCompat(schema, data) {
39607
39626
  return result.data;
39608
39627
  }
39609
39628
 
39610
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
39629
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
39611
39630
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 60000;
39612
39631
 
39613
39632
  class Protocol {
@@ -39645,6 +39664,9 @@ class Protocol {
39645
39664
  this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
39646
39665
  const handleTaskResult = async () => {
39647
39666
  const taskId = request.params.taskId;
39667
+ if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
39668
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
39669
+ }
39648
39670
  if (this._taskMessageQueue) {
39649
39671
  let queuedMessage;
39650
39672
  while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
@@ -39675,12 +39697,12 @@ class Protocol {
39675
39697
  throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
39676
39698
  }
39677
39699
  if (!isTerminal(task.status)) {
39678
- await this._waitForTaskUpdate(taskId, extra.signal);
39700
+ await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
39679
39701
  return await handleTaskResult();
39680
39702
  }
39681
39703
  if (isTerminal(task.status)) {
39682
39704
  const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
39683
- this._clearTaskQueue(taskId);
39705
+ this._clearTaskQueue(taskId, extra.sessionId);
39684
39706
  return {
39685
39707
  ...result,
39686
39708
  _meta: {
@@ -39717,7 +39739,7 @@ class Protocol {
39717
39739
  throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
39718
39740
  }
39719
39741
  await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
39720
- this._clearTaskQueue(request.params.taskId);
39742
+ this._clearTaskQueue(request.params.taskId, extra.sessionId);
39721
39743
  const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
39722
39744
  if (!cancelledTask) {
39723
39745
  throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
@@ -39840,6 +39862,19 @@ class Protocol {
39840
39862
  const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
39841
39863
  const capturedTransport = this._transport;
39842
39864
  const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
39865
+ const sessionId = capturedTransport?.sessionId;
39866
+ const store = this._taskStore;
39867
+ let relatedTaskFound = true;
39868
+ let relatedTaskLookup;
39869
+ if (relatedTaskId && store && this._taskMessageQueue && sessionId !== undefined) {
39870
+ relatedTaskFound = false;
39871
+ relatedTaskLookup = (async () => {
39872
+ if (!await store.getTask(relatedTaskId, sessionId)) {
39873
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
39874
+ }
39875
+ relatedTaskFound = true;
39876
+ })();
39877
+ }
39843
39878
  if (handler === undefined) {
39844
39879
  const errorResponse = {
39845
39880
  jsonrpc: "2.0",
@@ -39849,7 +39884,10 @@ class Protocol {
39849
39884
  message: "Method not found"
39850
39885
  }
39851
39886
  };
39852
- if (relatedTaskId && this._taskMessageQueue) {
39887
+ if (relatedTaskId && relatedTaskLookup) {
39888
+ const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
39889
+ relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error) => this._onerror(new Error(`Failed to send an error response: ${error}`)));
39890
+ } else if (relatedTaskId && this._taskMessageQueue) {
39853
39891
  this._enqueueTaskMessage(relatedTaskId, {
39854
39892
  type: "error",
39855
39893
  message: errorResponse,
@@ -39900,7 +39938,10 @@ class Protocol {
39900
39938
  closeSSEStream: extra?.closeSSEStream,
39901
39939
  closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
39902
39940
  };
39903
- Promise.resolve().then(() => {
39941
+ (relatedTaskLookup ?? Promise.resolve()).then(() => {
39942
+ if (relatedTaskLookup && abortController.signal.aborted) {
39943
+ throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
39944
+ }
39904
39945
  if (taskCreationParams) {
39905
39946
  this.assertTaskHandlerCapability(request.method);
39906
39947
  }
@@ -39935,7 +39976,7 @@ class Protocol {
39935
39976
  ...error["data"] !== undefined && { data: error["data"] }
39936
39977
  }
39937
39978
  };
39938
- if (relatedTaskId && this._taskMessageQueue) {
39979
+ if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
39939
39980
  await this._enqueueTaskMessage(relatedTaskId, {
39940
39981
  type: "error",
39941
39982
  message: errorResponse,
@@ -40323,7 +40364,7 @@ class Protocol {
40323
40364
  throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
40324
40365
  }
40325
40366
  const maxQueueSize = this._options?.maxTaskQueueSize;
40326
- await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
40367
+ await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
40327
40368
  }
40328
40369
  async _clearTaskQueue(taskId, sessionId) {
40329
40370
  if (this._taskMessageQueue) {
@@ -40342,10 +40383,10 @@ class Protocol {
40342
40383
  }
40343
40384
  }
40344
40385
  }
40345
- async _waitForTaskUpdate(taskId, signal) {
40386
+ async _waitForTaskUpdate(taskId, signal, sessionId) {
40346
40387
  let interval = this._options?.defaultTaskPollInterval ?? 1000;
40347
40388
  try {
40348
- const task = await this._taskStore?.getTask(taskId);
40389
+ const task = await this._taskStore?.getTask(taskId, sessionId);
40349
40390
  if (task?.pollInterval) {
40350
40391
  interval = task.pollInterval;
40351
40392
  }
@@ -40448,7 +40489,7 @@ function mergeCapabilities(base, additional) {
40448
40489
  return result;
40449
40490
  }
40450
40491
 
40451
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
40492
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
40452
40493
  var import_ajv = __toESM(require_ajv(), 1);
40453
40494
  var import_ajv_formats = __toESM(require_dist(), 1);
40454
40495
  function createDefaultAjvInstance() {
@@ -40488,7 +40529,7 @@ class AjvJsonSchemaValidator {
40488
40529
  }
40489
40530
  }
40490
40531
 
40491
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
40532
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
40492
40533
  class ExperimentalServerTasks {
40493
40534
  constructor(_server) {
40494
40535
  this._server = _server;
@@ -40566,7 +40607,7 @@ class ExperimentalServerTasks {
40566
40607
  }
40567
40608
  }
40568
40609
 
40569
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
40610
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
40570
40611
  function assertToolsCallTaskCapability(requests, method, entityName) {
40571
40612
  if (!requests) {
40572
40613
  throw new Error(`${entityName} does not support task creation (required for ${method})`);
@@ -40601,7 +40642,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
40601
40642
  }
40602
40643
  }
40603
40644
 
40604
- // node_modules/.bun/@modelcontextprotocol+sdk@1.31.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
40645
+ // node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
40605
40646
  class Server extends Protocol {
40606
40647
  constructor(_serverInfo, options) {
40607
40648
  super(options);
@@ -40926,7 +40967,7 @@ class Server extends Protocol {
40926
40967
  }
40927
40968
 
40928
40969
  // packages/mcp/mcp-server.ts
40929
- var PLUGIN_VERSION = "0.25.2";
40970
+ var PLUGIN_VERSION = "0.25.3";
40930
40971
  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.
40931
40972
 
40932
40973
  **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, lowercased with \`_\`, \`/\` and other punctuation turned into \`-\`. The slug is the whole name and is never shortened; your bot name may carry a shortened form of it, so don't read your channel off your bot name. 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.
@@ -40970,6 +41011,7 @@ var keyToIntent = (key) => value3(key).pipe(discriminatorsExhaustive2("kind")({
40970
41011
  channelName: k.channelName
40971
41012
  })
40972
41013
  }));
41014
+ var withoutIntent = (keys, key) => key.kind === "channel" ? filter5(keys, (held) => held.channelName !== key.channelName) : remove5(keys, key);
40973
41015
  var channelKey = (ref) => channelKeyOf(ref.channel.name);
40974
41016
  var newTopicsKey = (ref) => newTopicsKeyOf(ref.channel.name);
40975
41017
  var threadKey = (ref) => map2(ref.thread, (thread) => threadKeyOf(ref.channel.name, thread.name));
@@ -41000,7 +41042,7 @@ var createNarrowSet = () => {
41000
41042
  },
41001
41043
  remove: (intent) => {
41002
41044
  const key = intentKey(intent);
41003
- intents = remove5(intents, key);
41045
+ intents = withoutIntent(intents, key);
41004
41046
  if (recording)
41005
41047
  pending.push({ op: "remove", key });
41006
41048
  },
@@ -41014,7 +41056,7 @@ var createNarrowSet = () => {
41014
41056
  onNone: () => intents,
41015
41057
  onSome: (next) => fromIterable5(next.map(intentKey))
41016
41058
  });
41017
- intents = pending.reduce((acc, delta) => delta.op === "add" ? add2(acc, delta.key) : remove5(acc, delta.key), seeded);
41059
+ intents = pending.reduce((acc, delta) => delta.op === "add" ? add2(acc, delta.key) : withoutIntent(acc, delta.key), seeded);
41018
41060
  pending.length = 0;
41019
41061
  recording = false;
41020
41062
  },
@@ -41033,7 +41075,10 @@ var raceReleaseAgainstTimeout = (release, timeoutMs = DEFAULT_TIMEOUT_MS) => rel
41033
41075
 
41034
41076
  // packages/mcp/subscription-restore.ts
41035
41077
  var isTopicLevel = (intent) => intent.kind !== "channel";
41036
- var topicLevelIntents = (intents) => filter2(intents, isTopicLevel);
41078
+ var topicLevelIntents = (intents) => {
41079
+ const heldWhole = fromIterable5(filterMap2(intents, (intent) => isTopicLevel(intent) ? none2() : some2(intent.channelName)));
41080
+ return filter2(intents, (intent) => isTopicLevel(intent) && !has4(heldWhole, intent.channelName));
41081
+ };
41037
41082
  var sameIntent = (a, b) => a.kind === b.kind && a.channelName === b.channelName && (a.kind !== "thread" || b.kind !== "thread" || a.threadName === b.threadName);
41038
41083
  var combineWithRealm = (realmChannels, topicIntents) => {
41039
41084
  const deduped = dedupeWith(topicIntents, sameIntent);
@@ -41056,7 +41101,7 @@ var restoreSubscriptions = (deps) => deps.persisted.pipe(flatMap10((persisted) =
41056
41101
  ...topicLevelIntents(getOrElse2(persisted, () => [])),
41057
41102
  ...topicLevelIntents(deps.narrowSet.intents())
41058
41103
  ]))))));
41059
- var persistSubscriptions = (store, narrowSet) => store.write(topicLevelIntents(narrowSet.intents()));
41104
+ var persistSubscriptions = (store, narrowSet) => suspend4(() => store.write(topicLevelIntents(narrowSet.intents())));
41060
41105
 
41061
41106
  // packages/mcp/subscription-store.ts
41062
41107
  import { homedir as homedir3 } from "node:os";
@@ -41072,26 +41117,35 @@ var RetiredMentionsSchema = Struct({ kind: Literal2("mentions") });
41072
41117
  var PersistedIntentSchema = Union2(SubscribeIntentSchema, RetiredMentionsSchema);
41073
41118
  var isRetired = (intent) => intent.kind === "mentions";
41074
41119
  var SubscriptionsFileSchema = parseJson(Array$(PersistedIntentSchema));
41075
- var traceRetiredDiscards = (id, intents) => forEach5(filter2(intents, isRetired), (retired) => logInfo2(`commy plugin: subscription-store drop (retired-intent) kind=${retired.kind} session=${id}`)).pipe(asVoid2);
41076
- var decodeSubscriptionsFile = (id, raw) => decodeUnknown2(SubscriptionsFileSchema)(raw).pipe(tap2((intents) => traceRetiredDiscards(id, intents)), map18(filter2((intent) => !isRetired(intent))));
41120
+ var traceRetiredDiscards = (owner, intents) => forEach5(filter2(intents, isRetired), (retired) => logInfo2(`commy plugin: subscription-store drop (retired-intent) kind=${retired.kind} record=${owner}`)).pipe(asVoid2);
41121
+ var decodeSubscriptionsFile = (owner, raw) => decodeUnknown2(SubscriptionsFileSchema)(raw).pipe(tap2((intents) => traceRetiredDiscards(owner, intents)), map18(filter2((intent) => !isRetired(intent))));
41077
41122
  var FILENAME_SAFE3 = /[^a-zA-Z0-9._-]/g;
41078
- var subscriptionFilename = (id) => {
41079
- const safe = id.replace(FILENAME_SAFE3, "_");
41080
- return `${safe}.json`;
41081
- };
41123
+ var subscriptionFilename = (owner) => `${owner.replace(FILENAME_SAFE3, "_")}.json`;
41082
41124
  var isNotFound3 = (error) => error._tag === "SystemError" && error.reason === "NotFound";
41083
- var readSubscriptions = (fs, id, path) => fs.readFileString(path).pipe(flatMap10((raw) => decodeSubscriptionsFile(id, raw)), asSome2, catchIf2(isNotFound3, () => succeed8(none2())));
41125
+ var readSubscriptions = (fs, owner, path) => fs.readFileString(path).pipe(flatMap10((raw) => decodeSubscriptionsFile(owner, raw)), asSome2, catchIf2(isNotFound3, () => succeed8(none2())));
41084
41126
  var createFileSubscriptionStore = (deps) => {
41085
- const { dir, fs, session } = deps;
41086
- const pathFor = (id) => join8(dir, subscriptionFilename(id));
41087
- const read = () => flatMap10(_await(session), (id) => readSubscriptions(fs, id, pathFor(id)));
41088
- const write = (intents) => flatMap10(_await(session), (id) => fs.makeDirectory(dir, { recursive: true }).pipe(zipRight3(fs.writeFileString(pathFor(id), JSON.stringify(intents)))));
41127
+ const { dir, fs, owner } = deps;
41128
+ const pathFor = (key) => join8(dir, subscriptionFilename(key));
41129
+ const read = () => flatMap10(owner, (key) => readSubscriptions(fs, key, pathFor(key)));
41130
+ const write = (intents) => flatMap10(owner, (key) => fs.makeDirectory(dir, { recursive: true }).pipe(zipRight3(fs.writeFileString(pathFor(key), JSON.stringify(intents)))));
41089
41131
  return { read, write };
41090
41132
  };
41091
41133
  var STATE_SEGMENT3 = "commy";
41092
41134
  var stateBaseConfig3 = nonEmptyString2("XDG_STATE_HOME").pipe(withDefault2(join8(homedir3(), ".local", "state")));
41093
41135
  var subscriptionDirConfig = stateBaseConfig3.pipe(map14((base) => join8(base, STATE_SEGMENT3, "subscriptions")));
41094
- var FileSubscriptionStoreLive = effect(SubscriptionStoreTag, all6([FileSystem, orDie2(subscriptionDirConfig), SessionId]).pipe(map18(([fs, dir, session]) => createFileSubscriptionStore({ dir, fs, session }))));
41136
+ var FileSubscriptionStoreLive = effect(SubscriptionStoreTag, all6([
41137
+ FileSystem,
41138
+ orDie2(subscriptionDirConfig),
41139
+ SessionId,
41140
+ orElseSucceed2(botNameConfig, () => none2())
41141
+ ]).pipe(map18(([fs, dir, session, botName]) => createFileSubscriptionStore({
41142
+ dir,
41143
+ fs,
41144
+ owner: match2(botName, {
41145
+ onNone: () => _await(session),
41146
+ onSome: (name) => succeed8(`bot-${name}`)
41147
+ })
41148
+ }))));
41095
41149
 
41096
41150
  // packages/mcp/tools.ts
41097
41151
  var emptyObjectSchema = {
@@ -41584,7 +41638,7 @@ var buildToolDefs = (deps, cache) => {
41584
41638
  },
41585
41639
  {
41586
41640
  name: "unsubscribe",
41587
- description: "Unsubscribe the bot from a substrate target. Same token syntax as subscribe.",
41641
+ description: "Unsubscribe the bot from a substrate target. Same token syntax as subscribe. Unsubscribing a whole channel also drops every topic and new-topics narrow held in it, including topics joined by posting or reacting there.",
41588
41642
  inputSchema: {
41589
41643
  type: "object",
41590
41644
  properties: {
@@ -42014,20 +42068,19 @@ var makeProgram = (params = {}) => scoped2(gen2(function* () {
42014
42068
  const mcp = buildMcpServer();
42015
42069
  const notifier = params.notifier ?? channelNotifier(mcp);
42016
42070
  const registerType2Defaults = parsed.botName === undefined ? createType2DefaultsOnAcquire(narrowSet, adapter.inbox) : undefined;
42017
- const persistedTopicIntents = parsed.botName === undefined ? subscriptionStore.read() : succeedNone2;
42071
+ const persistedTopicIntents = subscriptionStore.read();
42072
+ const narrowSetRebuilt = yield* make25();
42018
42073
  const rebuildNarrowSet = (parsed.botName === undefined ? _await(sessionIdDeferred).pipe(asSome2) : succeedNone2).pipe(flatMap10((sessionId) => withSessionContext(restoreSubscriptions({
42019
42074
  persisted: persistedTopicIntents,
42020
42075
  isBound: () => identityCache.boundIdentityIds().size > 0,
42021
42076
  narrowSet,
42022
42077
  inbox: adapter.inbox
42023
- }), { sessionId: getOrUndefined2(sessionId), project: parsed.project })), catchAll2((err) => logError2(`commy plugin: could not rebuild the subscription set: ${isError(err) ? err.message : String(err)}`).pipe(zipRight3(sync4(() => narrowSet.load(none2()))))), provide2(loggerLayer));
42078
+ }), { sessionId: getOrUndefined2(sessionId), project: parsed.project })), catchAll2((err) => logError2(`commy plugin: could not rebuild the subscription set: ${isError(err) ? err.message : String(err)}`).pipe(zipRight3(sync4(() => narrowSet.load(none2()))))), provide2(loggerLayer), ensuring2(succeed3(narrowSetRebuilt, undefined)));
42024
42079
  const seedDefaults = (project) => registerType2Defaults !== undefined ? registerType2Defaults(project) : _void;
42025
42080
  const seedIfFresh = (sessionId, project) => parsed.botName !== undefined ? _void : seedDefaultsIfFresh({ subscriptionStore, registerDefaults: seedDefaults }, project).pipe(catchAll2((err) => logError2(`commy plugin: subscription seed check failed for ${sessionId}: ${isError(err) ? err.message : String(err)}`).pipe(zipRight3(seedDefaults(project)))), provide2(loggerLayer));
42026
42081
  const ensureSessionSubscriptions = (sessionId, project) => seedIfFresh(sessionId, project);
42027
- const persistSessionSubscriptions = parsed.botName !== undefined ? _void : poll(sessionIdDeferred).pipe(flatMap10(match2({
42028
- onNone: () => _void,
42029
- onSome: () => persistSubscriptions(subscriptionStore, narrowSet).pipe(catchAll2((err) => logError2(`commy plugin: subscription persist failed: ${isError(err) ? err.message : String(err)}`)), provide2(loggerLayer))
42030
- })));
42082
+ const writeTopicRecord = _await(narrowSetRebuilt).pipe(zipRight3(persistSubscriptions(subscriptionStore, narrowSet)), catchAll2((err) => logError2(`commy plugin: subscription persist failed: ${isError(err) ? err.message : String(err)}`)), provide2(loggerLayer));
42083
+ const persistSessionSubscriptions = parsed.botName !== undefined ? writeTopicRecord : poll(sessionIdDeferred).pipe(flatMap10(match2({ onNone: () => _void, onSome: () => writeTopicRecord })));
42031
42084
  const seedDeps = {
42032
42085
  inbox: adapter.inbox,
42033
42086
  narrowSet,