@codeforbreakfast/commy-mcp 0.18.0 → 0.19.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 +171 -94
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeforbreakfast/commy-mcp",
3
- "version": "0.18.0",
3
+ "version": "0.19.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
@@ -47638,9 +47638,11 @@ var reactionToInboundEvent = (reaction, directory, target) => exports_Effect.gen
47638
47638
  by
47639
47639
  };
47640
47640
  });
47641
- var registerQueue = (http, mode) => {
47641
+ var MAX_QUEUE_TIMEOUT_SECS = 604800;
47642
+ var registerQueue = (http, mode, idleTimeoutSecs) => {
47642
47643
  const baseParams = { event_types: JSON.stringify(["message", "reaction"]) };
47643
- const params = mode === "mentions" ? { ...baseParams, narrow: JSON.stringify([["is", "mentioned"]]) } : baseParams;
47644
+ const modeParams = mode === "mentions" ? { ...baseParams, narrow: JSON.stringify([["is", "mentioned"]]) } : baseParams;
47645
+ const params = idleTimeoutSecs === undefined ? modeParams : { ...modeParams, idle_queue_timeout: idleTimeoutSecs };
47644
47646
  return http.post("/register", registerResponseSchema, params).pipe(exports_Effect.map((res) => ({ queueId: res.queue_id, lastEventId: res.last_event_id })));
47645
47647
  };
47646
47648
  var populateCacheFromInboundEvents = (cache, events) => {
@@ -47711,6 +47713,13 @@ var inboxEvents = (config2) => exports_Stream.unwrap(exports_Effect.gen(function
47711
47713
  consecutiveFailures: 0,
47712
47714
  needsBreadcrumb: false
47713
47715
  });
47716
+ const resumeReported = yield* exports_Ref.make(false);
47717
+ const reportResume = (queueReplayed) => {
47718
+ const onResumeOutcome = config2.onResumeOutcome;
47719
+ if (onResumeOutcome === undefined)
47720
+ return exports_Effect.void;
47721
+ return exports_Ref.getAndSet(resumeReported, true).pipe(exports_Effect.flatMap((already) => already ? exports_Effect.void : onResumeOutcome(queueReplayed)));
47722
+ };
47714
47723
  const recordTransientFailure = (cause3) => exports_Ref.modify(breadcrumb, (s) => {
47715
47724
  const consecutiveFailures = s.consecutiveFailures + 1;
47716
47725
  return [consecutiveFailures, { consecutiveFailures, needsBreadcrumb: true }];
@@ -47723,6 +47732,7 @@ var inboxEvents = (config2) => exports_Stream.unwrap(exports_Effect.gen(function
47723
47732
  }
47724
47733
  })));
47725
47734
  const handleBadQueue = exports_Effect.gen(function* () {
47735
+ yield* reportResume(false);
47726
47736
  const since = yield* watermark.get();
47727
47737
  if (config2.replay === undefined || exports_Option.isNone(since)) {
47728
47738
  return [[], undefined];
@@ -47736,11 +47746,12 @@ var inboxEvents = (config2) => exports_Stream.unwrap(exports_Effect.gen(function
47736
47746
  return [markReplayed(gapEither.right), undefined];
47737
47747
  });
47738
47748
  const step4 = (queue) => exports_Effect.gen(function* () {
47739
- const currentQueue = queue ?? (yield* registerQueue(config2.http, config2.mode));
47749
+ const currentQueue = queue ?? (yield* registerQueue(config2.http, config2.mode, config2.queueIdleTimeoutSecs).pipe(exports_Effect.tap((q) => config2.onQueueRegister?.(q) ?? exports_Effect.void)));
47740
47750
  const res = yield* config2.http.get("/events", eventsResponseSchema, {
47741
47751
  queue_id: currentQueue.queueId,
47742
47752
  last_event_id: currentQueue.lastEventId
47743
47753
  });
47754
+ yield* reportResume(true);
47744
47755
  const directory = res.events.length > 0 ? yield* config2.resolveDirectory() : undefined;
47745
47756
  let maxId = currentQueue.lastEventId;
47746
47757
  const mapped = [];
@@ -47763,6 +47774,9 @@ var inboxEvents = (config2) => exports_Stream.unwrap(exports_Effect.gen(function
47763
47774
  yield* watermark.advance(ev.message.ts);
47764
47775
  }
47765
47776
  }
47777
+ if (config2.onQueueAdvance !== undefined && maxId > currentQueue.lastEventId) {
47778
+ yield* config2.onQueueAdvance(maxId);
47779
+ }
47766
47780
  return [mapped, { queueId: currentQueue.queueId, lastEventId: maxId }];
47767
47781
  }).pipe(exports_Effect.catchTag("ZulipApiError", (e) => {
47768
47782
  if (e.code === "BAD_EVENT_QUEUE_ID")
@@ -48406,7 +48420,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48406
48420
  if (exports_Option.exists(state.registration, (r) => r.mode === mode)) {
48407
48421
  return exports_Effect.succeed([undefined, state]);
48408
48422
  }
48409
- return registerQueue(minterHttp, mode).pipe(exports_Effect.map((q) => [
48423
+ return registerQueue(minterHttp, mode, config2.queueIdleTimeoutSecs).pipe(exports_Effect.tap((q) => config2.onQueueRegister?.(q) ?? exports_Effect.void), exports_Effect.map((q) => [
48410
48424
  undefined,
48411
48425
  { ...state, registration: exports_Option.some(exports_Data.struct({ queue: q, mode })) }
48412
48426
  ]));
@@ -48463,23 +48477,35 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
48463
48477
  subscriptions: JSON.stringify([channel])
48464
48478
  }).pipe(exports_Effect.asVoid)));
48465
48479
  }).pipe(exports_Effect.mapError((cause3) => new InboxError({ operation: "unsubscribe", cause: cause3 }))),
48466
- events: () => exports_Stream.unwrap(exports_Effect.all([exports_SynchronizedRef.get(boundRef), exports_SynchronizedRef.get(inboxRef)]).pipe(exports_Effect.map(([current2, state]) => inboxEvents({
48467
- http: minterHttp,
48468
- permalinkBase: base,
48469
- resolveDirectory: buildDirectoryLookup,
48470
- mode: currentMode(state),
48471
- messageRefCache,
48472
- watermarkStore,
48473
- replay: (since) => inbox.replay(since),
48474
- ...exports_Option.match(current2, {
48475
- onNone: () => ({}),
48476
- onSome: (b) => ({ boundIdentity: b.identity })
48477
- }),
48478
- ...exports_Option.match(state.registration, {
48479
- onNone: () => ({}),
48480
- onSome: (r) => ({ initialQueue: r.queue })
48481
- })
48482
- })))).pipe(exports_Stream.filterEffect(shouldDeliver)),
48480
+ events: () => exports_Stream.unwrap(exports_Effect.all([
48481
+ exports_SynchronizedRef.get(boundRef),
48482
+ exports_SynchronizedRef.get(inboxRef),
48483
+ config2.resumeQueue?.() ?? exports_Effect.succeedNone
48484
+ ]).pipe(exports_Effect.flatMap(([current2, state, resumed]) => {
48485
+ const initialQueue = exports_Option.orElse(resumed, () => exports_Option.map(state.registration, (r) => r.queue));
48486
+ const reportAbsentResume = exports_Option.isNone(resumed) ? config2.onResumeOutcome?.(false) ?? exports_Effect.void : exports_Effect.void;
48487
+ return reportAbsentResume.pipe(exports_Effect.as(inboxEvents({
48488
+ http: minterHttp,
48489
+ permalinkBase: base,
48490
+ resolveDirectory: buildDirectoryLookup,
48491
+ mode: currentMode(state),
48492
+ messageRefCache,
48493
+ watermarkStore,
48494
+ ...config2.queueIdleTimeoutSecs === undefined ? {} : { queueIdleTimeoutSecs: config2.queueIdleTimeoutSecs },
48495
+ ...config2.onQueueRegister === undefined ? {} : { onQueueRegister: config2.onQueueRegister },
48496
+ ...config2.onQueueAdvance === undefined ? {} : { onQueueAdvance: config2.onQueueAdvance },
48497
+ replay: (since) => inbox.replay(since),
48498
+ ...exports_Option.match(current2, {
48499
+ onNone: () => ({}),
48500
+ onSome: (b) => ({ boundIdentity: b.identity })
48501
+ }),
48502
+ ...exports_Option.match(initialQueue, {
48503
+ onNone: () => ({}),
48504
+ onSome: (q) => ({ initialQueue: q })
48505
+ }),
48506
+ ...exports_Option.isSome(resumed) && config2.onResumeOutcome !== undefined ? { onResumeOutcome: config2.onResumeOutcome } : {}
48507
+ })));
48508
+ }))).pipe(exports_Stream.filterEffect(shouldDeliver)),
48483
48509
  replay: (since) => {
48484
48510
  const replayNarrow = JSON.stringify([{ negated: true, operator: "is", operand: "dm" }]);
48485
48511
  return exports_Effect.all([
@@ -56742,6 +56768,70 @@ class StdioServerTransport {
56742
56768
  }
56743
56769
  }
56744
56770
 
56771
+ // packages/mcp/queue-state-hooks.ts
56772
+ var buildQueueStateHooks = (deps) => {
56773
+ const { store, session, idleTimeoutSecs, resumeOutcome } = deps;
56774
+ const withSession = (use) => exports_Deferred.poll(session).pipe(exports_Effect.flatMap(exports_Option.match({
56775
+ onNone: () => exports_Effect.void,
56776
+ onSome: (awaitId) => exports_Effect.flatMap(awaitId, use)
56777
+ })));
56778
+ return {
56779
+ queueIdleTimeoutSecs: idleTimeoutSecs,
56780
+ onQueueRegister: (queue) => withSession((id3) => exports_Deferred.poll(resumeOutcome).pipe(exports_Effect.flatMap(exports_Option.match({
56781
+ onSome: () => store.write(id3, queue),
56782
+ onNone: () => store.read(id3).pipe(exports_Effect.map(exports_Option.isNone), exports_Effect.catchAll(() => exports_Effect.succeed(true)), exports_Effect.flatMap((isFresh3) => isFresh3 ? store.write(id3, queue) : exports_Effect.void))
56783
+ })), exports_Effect.catchAllCause(() => exports_Effect.void))),
56784
+ onQueueAdvance: (lastEventId) => withSession((id3) => store.advance(id3, lastEventId).pipe(exports_Effect.catchAllCause(() => exports_Effect.void))),
56785
+ resumeQueue: () => exports_Deferred.poll(session).pipe(exports_Effect.flatMap(exports_Option.match({
56786
+ onNone: () => exports_Effect.succeedNone,
56787
+ onSome: (awaitId) => exports_Effect.flatMap(awaitId, (id3) => store.read(id3).pipe(exports_Effect.catchAllCause(() => exports_Effect.succeedNone)))
56788
+ }))),
56789
+ onResumeOutcome: (queueReplayed) => exports_Deferred.succeed(resumeOutcome, queueReplayed).pipe(exports_Effect.asVoid)
56790
+ };
56791
+ };
56792
+
56793
+ // packages/mcp/queue-state-store.ts
56794
+ import { homedir } from "node:os";
56795
+ import { join as join7 } from "node:path";
56796
+ class QueueStateStoreTag extends exports_Context.Tag("QueueStateStore")() {
56797
+ }
56798
+ var FILENAME_SAFE = /[^a-zA-Z0-9._-]/g;
56799
+ var stateFilename = (id3) => `${id3.replace(FILENAME_SAFE, "_")}.json`;
56800
+ var QueueStateFileSchema = exports_Schema.parseJson(exports_Schema.Struct({ queueId: exports_Schema.String, lastEventId: exports_Schema.Int }));
56801
+ var decodeQueueStateFile = exports_Schema.decodeUnknown(QueueStateFileSchema);
56802
+ var encodeQueueStateFile = exports_Schema.encode(QueueStateFileSchema);
56803
+ var isNotFound = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
56804
+ var readState = (fs, path) => fs.readFileString(path).pipe(exports_Effect.flatMap(decodeQueueStateFile), exports_Effect.map(exports_Option.some), exports_Effect.catchIf(isNotFound, () => exports_Effect.succeed(exports_Option.none())));
56805
+ var createFileQueueStateStore = (deps) => {
56806
+ const { dir, fs } = deps;
56807
+ const pathFor = (id3) => join7(dir, stateFilename(id3));
56808
+ const persist = (path, state) => encodeQueueStateFile(state).pipe(exports_Effect.orDie, exports_Effect.flatMap((json4) => fs.makeDirectory(dir, { recursive: true }).pipe(exports_Effect.zipRight(fs.writeFileString(path, json4)))));
56809
+ const read3 = (id3) => readState(fs, pathFor(id3));
56810
+ const write4 = (id3, state) => persist(pathFor(id3), state);
56811
+ const advance = (id3, lastEventId) => {
56812
+ const path = pathFor(id3);
56813
+ return readState(fs, path).pipe(exports_Effect.catchTag("ParseError", () => exports_Effect.succeed(exports_Option.none())), exports_Effect.flatMap(exports_Option.match({
56814
+ onNone: () => exports_Effect.void,
56815
+ onSome: (prior) => lastEventId > prior.lastEventId ? persist(path, { queueId: prior.queueId, lastEventId }) : exports_Effect.void
56816
+ })));
56817
+ };
56818
+ return { read: read3, write: write4, advance };
56819
+ };
56820
+ var STATE_SEGMENT = "commy";
56821
+ var stateBaseConfig = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join7(homedir(), ".local", "state")));
56822
+ var queueStateDirConfig = stateBaseConfig.pipe(exports_Config.map((base) => join7(base, STATE_SEGMENT, "queue-state")));
56823
+ var FileQueueStateStoreLive = exports_Layer.effect(QueueStateStoreTag, exports_Effect.all([exports_FileSystem.FileSystem, exports_Effect.orDie(queueStateDirConfig)]).pipe(exports_Effect.map(([fs, dir]) => createFileQueueStateStore({ dir, fs }))));
56824
+
56825
+ // packages/mcp/resume-outcome.ts
56826
+ class ResumeOutcome extends exports_Context.Tag("commy/mcp/ResumeOutcome")() {
56827
+ }
56828
+ var ResumeOutcomeLive = exports_Layer.effect(ResumeOutcome, exports_Deferred.make());
56829
+
56830
+ // packages/mcp/session-id.ts
56831
+ class SessionId extends exports_Context.Tag("commy/mcp/SessionId")() {
56832
+ }
56833
+ var SessionIdLive = exports_Layer.effect(SessionId, exports_Deferred.make());
56834
+
56745
56835
  // packages/mcp/subscribe-parser.ts
56746
56836
  class SubscribeTokenError extends exports_Data.TaggedError("SubscribeTokenError") {
56747
56837
  get message() {
@@ -56867,6 +56957,14 @@ var userConfigString = (key) => exports_Config.string(key).pipe(exports_Config.m
56867
56957
  return exports_Either.right(value5);
56868
56958
  }));
56869
56959
  var optionalUserConfig = (key) => exports_Config.option(userConfigString(key));
56960
+ var QUEUE_IDLE_TIMEOUT_DEFAULT_SECS = 86400;
56961
+ var queueIdleTimeoutSecs = (key) => optionalUserConfig(key).pipe(exports_Config.mapOrFail((option7) => exports_Option.match(option7, {
56962
+ onNone: () => exports_Either.right(QUEUE_IDLE_TIMEOUT_DEFAULT_SECS),
56963
+ onSome: (raw3) => {
56964
+ const parsed = Number(raw3);
56965
+ return Number.isInteger(parsed) && parsed > 0 ? exports_Either.right(Math.min(parsed, MAX_QUEUE_TIMEOUT_SECS)) : exports_Either.left(exports_ConfigError.InvalidData([key], `${key} must be a positive integer — received: ${raw3}`));
56966
+ }
56967
+ })));
56870
56968
  var optionalNonNegativeInt = (key) => optionalUserConfig(key).pipe(exports_Config.mapOrFail((option7) => exports_Option.match(option7, {
56871
56969
  onNone: () => exports_Either.right(exports_Option.none()),
56872
56970
  onSome: (raw3) => {
@@ -56897,6 +56995,7 @@ var envConfig = exports_Config.all({
56897
56995
  subscribe: optionalUserConfig("COMMY_SUBSCRIBE"),
56898
56996
  project: optionalNonEmpty("COMMY_PROJECT"),
56899
56997
  catchupWindowSeconds: optionalNonNegativeInt("COMMY_CATCHUP_WINDOW_SECONDS"),
56998
+ queueIdleTimeoutSecs: queueIdleTimeoutSecs("COMMY_QUEUE_IDLE_TIMEOUT_SECS"),
56900
56999
  downloadDir: optionalNonEmpty("COMMY_DOWNLOAD_DIR")
56901
57000
  }).pipe(exports_Config.mapOrFail((raw3) => {
56902
57001
  if (exports_Option.isSome(raw3.botApiKey) && exports_Option.isNone(raw3.botName)) {
@@ -56907,6 +57006,7 @@ var envConfig = exports_Config.all({
56907
57006
  realmUrl: raw3.realmUrl,
56908
57007
  minterEmail: raw3.minterEmail,
56909
57008
  minterApiKey: raw3.minterApiKey,
57009
+ queueIdleTimeoutSecs: raw3.queueIdleTimeoutSecs,
56910
57010
  ...exports_Option.match(raw3.botName, { onNone: () => ({}), onSome: (botName) => ({ botName }) }),
56911
57011
  ...exports_Option.isSome(raw3.botName) && exports_Option.isSome(raw3.botApiKey) ? { attachIdentity: { name: raw3.botName.value, apiKey: raw3.botApiKey.value } } : {},
56912
57012
  ...exports_Option.match(raw3.subscribe, {
@@ -56980,11 +57080,24 @@ class SubstrateAdapter extends exports_Context.Tag("SubstrateAdapter")() {
56980
57080
  var substrateAdapterLayer = (make78) => exports_Layer.scoped(SubstrateAdapter, exports_Effect.tap(make78, (adapter4) => exports_Effect.addFinalizer(() => exports_Effect.promise(() => adapter4.close()))));
56981
57081
  var ZulipAdapterLive = substrateAdapterLayer(exports_Effect.gen(function* () {
56982
57082
  const parsed = yield* parseEnv;
57083
+ const queueHooks = parsed.botName === undefined ? buildQueueStateHooks({
57084
+ store: yield* QueueStateStoreTag,
57085
+ session: yield* SessionId,
57086
+ idleTimeoutSecs: parsed.queueIdleTimeoutSecs,
57087
+ resumeOutcome: yield* ResumeOutcome
57088
+ }) : undefined;
56983
57089
  return yield* zulipAdapter({
56984
57090
  realmUrl: parsed.realmUrl,
56985
57091
  minterEmail: parsed.minterEmail,
56986
57092
  minterApiKey: parsed.minterApiKey,
56987
- ...parsed.attachIdentity === undefined ? {} : { attachIdentity: parsed.attachIdentity }
57093
+ ...parsed.attachIdentity === undefined ? {} : { attachIdentity: parsed.attachIdentity },
57094
+ ...queueHooks === undefined ? {} : {
57095
+ queueIdleTimeoutSecs: queueHooks.queueIdleTimeoutSecs,
57096
+ onQueueRegister: queueHooks.onQueueRegister,
57097
+ onQueueAdvance: queueHooks.onQueueAdvance,
57098
+ resumeQueue: queueHooks.resumeQueue,
57099
+ onResumeOutcome: queueHooks.onResumeOutcome
57100
+ }
56988
57101
  });
56989
57102
  }));
56990
57103
  var subscribeFromEnv = (inbox, narrowSet, parsed) => {
@@ -57111,29 +57224,29 @@ var catchUpChannels = (deps) => exports_Effect.gen(function* () {
57111
57224
  });
57112
57225
 
57113
57226
  // packages/mcp/cursor-store.ts
57114
- import { homedir } from "node:os";
57115
- import { join as join7 } from "node:path";
57227
+ import { homedir as homedir2 } from "node:os";
57228
+ import { join as join8 } from "node:path";
57116
57229
  class CursorStoreTag extends exports_Context.Tag("CursorStore")() {
57117
57230
  }
57118
- var FILENAME_SAFE = /[^a-zA-Z0-9._-]/g;
57231
+ var FILENAME_SAFE2 = /[^a-zA-Z0-9._-]/g;
57119
57232
  var cursorFilename = (id3) => {
57120
- const safe = id3.replace(FILENAME_SAFE, "_");
57233
+ const safe = id3.replace(FILENAME_SAFE2, "_");
57121
57234
  return `${safe}.json`;
57122
57235
  };
57123
57236
  var CursorFileSchema = exports_Schema.parseJson(exports_Schema.Struct({ lastSeenTs: TimestampSchema }));
57124
57237
  var decodeCursorFile = exports_Schema.decodeUnknown(CursorFileSchema);
57125
- var isNotFound = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
57126
- var readCursor = (fs, path) => fs.readFileString(path).pipe(exports_Effect.flatMap(decodeCursorFile), exports_Effect.map((file3) => exports_Option.some(file3.lastSeenTs)), exports_Effect.catchIf(isNotFound, () => exports_Effect.succeed(exports_Option.none())));
57238
+ var isNotFound2 = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
57239
+ var readCursor = (fs, path) => fs.readFileString(path).pipe(exports_Effect.flatMap(decodeCursorFile), exports_Effect.map((file3) => exports_Option.some(file3.lastSeenTs)), exports_Effect.catchIf(isNotFound2, () => exports_Effect.succeed(exports_Option.none())));
57127
57240
  var createFileCursorStore = (deps) => {
57128
57241
  const { dir, fs } = deps;
57129
- const pathFor = (id3) => join7(dir, cursorFilename(id3));
57242
+ const pathFor = (id3) => join8(dir, cursorFilename(id3));
57130
57243
  const read3 = (id3) => readCursor(fs, pathFor(id3));
57131
57244
  const write4 = (id3, ts) => readCursor(fs, pathFor(id3)).pipe(exports_Effect.catchTag("ParseError", () => exports_Effect.succeed(exports_Option.none())), exports_Effect.flatMap((prior) => exports_Option.isSome(prior) && prior.value >= ts ? exports_Effect.void : fs.makeDirectory(dir, { recursive: true }).pipe(exports_Effect.zipRight(fs.writeFileString(pathFor(id3), JSON.stringify({ lastSeenTs: ts }))))));
57132
57245
  return { read: read3, write: write4 };
57133
57246
  };
57134
- var STATE_SEGMENT = "commy";
57135
- var stateBaseConfig = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join7(homedir(), ".local", "state")));
57136
- var cursorDirConfig = stateBaseConfig.pipe(exports_Config.map((base) => join7(base, STATE_SEGMENT, "cursors")));
57247
+ var STATE_SEGMENT2 = "commy";
57248
+ var stateBaseConfig2 = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join8(homedir2(), ".local", "state")));
57249
+ var cursorDirConfig = stateBaseConfig2.pipe(exports_Config.map((base) => join8(base, STATE_SEGMENT2, "cursors")));
57137
57250
  var FileCursorStoreLive = exports_Layer.effect(CursorStoreTag, exports_Effect.all([exports_FileSystem.FileSystem, exports_Effect.orDie(cursorDirConfig)]).pipe(exports_Effect.map(([fs, dir]) => createFileCursorStore({ dir, fs }))));
57138
57251
 
57139
57252
  // packages/mcp/ensure-bound.ts
@@ -57169,35 +57282,6 @@ var createEnsureBound = (deps) => exports_Effect.map(exports_Ref.make({ kind: "i
57169
57282
  return ensureBound;
57170
57283
  });
57171
57284
 
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
-
57201
57285
  // packages/mcp/event-pump.ts
57202
57286
  var originatorId = (event) => exports_Match.value(event).pipe(exports_Match.discriminatorsExhaustive("kind")({
57203
57287
  "message-posted": (message) => message.message.sender.id,
@@ -57252,9 +57336,7 @@ var startEventPump = (deps) => exports_Effect.gen(function* () {
57252
57336
  for (const identity6 of identitiesIn(event))
57253
57337
  deps.rememberIdentity(identity6);
57254
57338
  }
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);
57339
+ const advance = deps.onMention !== undefined && event.kind === "mention-received" ? deps.onMention(event.message.ts) : exports_Effect.void;
57258
57340
  if (recordMessageDelivery(event, seenDelivered) === "duplicate")
57259
57341
  return advance;
57260
57342
  return advance.pipe(exports_Effect.zipRight(renderEvent(event, botIdentityId)), exports_Effect.flatMap((payload) => exports_Effect.tryPromise({
@@ -58767,7 +58849,7 @@ class Server extends Protocol {
58767
58849
  }
58768
58850
 
58769
58851
  // packages/mcp/mcp-server.ts
58770
- var PLUGIN_VERSION = "0.18.0";
58852
+ var PLUGIN_VERSION = "0.19.0";
58771
58853
  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.
58772
58854
 
58773
58855
  **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.
@@ -58884,11 +58966,6 @@ var createNarrowSet = () => {
58884
58966
  var DEFAULT_TIMEOUT_MS = 5000;
58885
58967
  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));
58886
58968
 
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
-
58892
58969
  // packages/mcp/subscription-restore.ts
58893
58970
  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)), {
58894
58971
  discard: true
@@ -58923,25 +59000,16 @@ var subscriptionFilename = (id3) => {
58923
59000
  var isNotFound3 = (error2) => error2._tag === "SystemError" && error2.reason === "NotFound";
58924
59001
  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())));
58925
59002
  var createFileSubscriptionStore = (deps) => {
58926
- const { dir, fs, cursorDir, session } = deps;
59003
+ const { dir, fs, session } = deps;
58927
59004
  const pathFor = (id3) => join9(dir, subscriptionFilename(id3));
58928
59005
  const read3 = () => exports_Effect.flatMap(exports_Deferred.await(session), (id3) => readSubscriptions(fs, pathFor(id3)));
58929
59006
  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 };
59007
+ return { read: read3, write: write4 };
58935
59008
  };
58936
59009
  var STATE_SEGMENT3 = "commy";
58937
59010
  var stateBaseConfig3 = exports_Config.nonEmptyString("XDG_STATE_HOME").pipe(exports_Config.withDefault(join9(homedir3(), ".local", "state")));
58938
59011
  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 }))));
59012
+ var FileSubscriptionStoreLive = exports_Layer.effect(SubscriptionStoreTag, exports_Effect.all([exports_FileSystem.FileSystem, exports_Effect.orDie(subscriptionDirConfig), SessionId]).pipe(exports_Effect.map(([fs, dir, session]) => createFileSubscriptionStore({ dir, fs, session }))));
58945
59013
 
58946
59014
  // packages/mcp/tools.ts
58947
59015
  var emptyObjectSchema = {
@@ -59744,6 +59812,7 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59744
59812
  const cursorStore = yield* CursorStoreTag;
59745
59813
  const subscriptionStore = yield* SubscriptionStoreTag;
59746
59814
  const sessionIdDeferred = yield* SessionId;
59815
+ const resumeOutcome = yield* ResumeOutcome;
59747
59816
  yield* exports_Effect.flatMap(readBootSessionId, exports_Option.match({
59748
59817
  onNone: () => exports_Effect.void,
59749
59818
  onSome: (sessionId) => exports_Deferred.succeed(sessionIdDeferred, sessionId).pipe(exports_Effect.asVoid)
@@ -59791,12 +59860,25 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59791
59860
  onNone: () => exports_Effect.void,
59792
59861
  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
59862
  })));
59794
- const ephemeralOnAcquire = parsed.botName === undefined ? (acquired, project3, sessionId) => ensureSessionSubscriptions(sessionId, project3).pipe(exports_Effect.zipRight(catchUpMentions({
59795
- cursorStore,
59796
- inbox: adapter4.inbox,
59797
- identityId: acquired.identity.id,
59798
- notifier
59799
- }).pipe(exports_Effect.catchAllCause(logCatchUpFailure("ephemeral mentions")))), exports_Effect.provide(loggerLayer)) : undefined;
59863
+ const ephemeralOnAcquire = parsed.botName === undefined ? (acquired, project3, sessionId) => ensureSessionSubscriptions(sessionId, project3).pipe(exports_Effect.zipRight(exports_Deferred.await(resumeOutcome)), exports_Effect.flatMap((queueReplayed) => {
59864
+ if (queueReplayed)
59865
+ return exports_Effect.void;
59866
+ const identityId = acquired.identity.id;
59867
+ const windowSeconds = parsed.catchupWindowSeconds ?? DEFAULT_CATCHUP_WINDOW_SECONDS;
59868
+ const catchUpIntents = narrowSet.intents();
59869
+ return catchUpMentions({
59870
+ cursorStore,
59871
+ inbox: adapter4.inbox,
59872
+ identityId,
59873
+ notifier
59874
+ }).pipe(exports_Effect.catchAllCause(logCatchUpFailure("ephemeral mentions")), exports_Effect.zipRight(windowSeconds > 0 && catchUpIntents.length > 0 ? catchUpChannels({
59875
+ intents: catchUpIntents,
59876
+ history: adapter4.history,
59877
+ notifier,
59878
+ botIdentityId: identityId,
59879
+ windowSeconds
59880
+ }).pipe(exports_Effect.catchAllCause(logCatchUpFailure("ephemeral channels"))) : exports_Effect.void));
59881
+ }), exports_Effect.provide(loggerLayer)) : undefined;
59800
59882
  const identityCache = yield* buildIdentityCache(adapter4, parsed.botName, ephemeralOnAcquire);
59801
59883
  let type1Intents = [];
59802
59884
  if (parsed.botName !== undefined) {
@@ -59881,11 +59963,6 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59881
59963
  if (id3 === undefined)
59882
59964
  return exports_Effect.void;
59883
59965
  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));
59889
59966
  }
59890
59967
  });
59891
59968
  yield* exports_Effect.addFinalizer(() => pump.cancel);
@@ -59895,7 +59972,7 @@ var makeProgram = (params = {}) => exports_Effect.scoped(exports_Effect.gen(func
59895
59972
  yield* exports_Effect.forkScoped(restoreOnResume);
59896
59973
  yield* exports_Effect.raceFirst(pump.done, params.shutdownSignal ?? exports_Effect.never);
59897
59974
  }));
59898
- var AppLayer = exports_Layer.mergeAll(ZulipAdapterLive, FileCursorStoreLive, exports_Layer.provideMerge(FileSubscriptionStoreLive, SessionIdLive), stderrLoggerLayer);
59975
+ var AppLayer = exports_Layer.mergeAll(ZulipAdapterLive, FileCursorStoreLive, FileSubscriptionStoreLive, stderrLoggerLayer).pipe(exports_Layer.provideMerge(FileQueueStateStoreLive), exports_Layer.provideMerge(SessionIdLive), exports_Layer.provideMerge(ResumeOutcomeLive));
59899
59976
  var PlatformLive = exports_Layer.mergeAll(exports_Layer.setConfigProvider(exports_ConfigProvider.fromEnv()), exports_FetchHttpClient.layer, exports_NodeContext.layer);
59900
59977
  var MainLive = exports_Layer.provideMerge(AppLayer, PlatformLive);
59901
59978
  var clientDisconnect = (stdin3) => exports_Effect.async((resume2) => {