@codeforbreakfast/commy-mcp 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server.js +153 -35
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -47088,6 +47088,36 @@ var wrapBotHttp = (inner, resolveDirectory, selfUserId) => ({
|
|
|
47088
47088
|
delete: (path, schema, body) => inner.delete(path, schema, body)
|
|
47089
47089
|
});
|
|
47090
47090
|
|
|
47091
|
+
// packages/zulip/permalink.ts
|
|
47092
|
+
var HASH_REPLACEMENTS = {
|
|
47093
|
+
"%": ".",
|
|
47094
|
+
"!": ".21",
|
|
47095
|
+
"'": ".27",
|
|
47096
|
+
"(": ".28",
|
|
47097
|
+
")": ".29",
|
|
47098
|
+
"*": ".2A",
|
|
47099
|
+
".": ".2E"
|
|
47100
|
+
};
|
|
47101
|
+
var encodeHashComponent = (value5) => encodeURIComponent(value5).replace(/[%!'()*.]/g, (matched) => HASH_REPLACEMENTS[matched] ?? matched);
|
|
47102
|
+
var permalinkBase = (config2) => config2.hostHeader === undefined ? config2.realmUrl.replace(/\/+$/, "") : `https://${config2.hostHeader}`;
|
|
47103
|
+
var channelSlug = (channel) => `${channel.id}-${encodeHashComponent(channel.name.replaceAll(" ", "-"))}`;
|
|
47104
|
+
var channelPermalink = (base, channel) => `${base}/#narrow/channel/${channelSlug(channel)}`;
|
|
47105
|
+
var topicPermalink = (base, channel, topic) => `${channelPermalink(base, channel)}/topic/${encodeHashComponent(topic)}`;
|
|
47106
|
+
var messagePermalink = (base, channel, id3, topic) => topic === undefined ? `${channelPermalink(base, channel)}/near/${id3}` : `${topicPermalink(base, channel, topic)}/near/${id3}`;
|
|
47107
|
+
var withChannelPermalink = (base, channel) => ({ ...channel, permalink: channelPermalink(base, channel) });
|
|
47108
|
+
var buildMessageRef = (base, id3, channel, threadName3) => {
|
|
47109
|
+
const decoratedChannel = withChannelPermalink(base, channel);
|
|
47110
|
+
return threadName3 === undefined ? { id: id3, channel: decoratedChannel, permalink: messagePermalink(base, decoratedChannel, id3) } : {
|
|
47111
|
+
id: id3,
|
|
47112
|
+
channel: decoratedChannel,
|
|
47113
|
+
thread: {
|
|
47114
|
+
name: threadName3,
|
|
47115
|
+
permalink: topicPermalink(base, decoratedChannel, threadName3)
|
|
47116
|
+
},
|
|
47117
|
+
permalink: messagePermalink(base, decoratedChannel, id3, threadName3)
|
|
47118
|
+
};
|
|
47119
|
+
};
|
|
47120
|
+
|
|
47091
47121
|
// packages/zulip/events.ts
|
|
47092
47122
|
var createMessageRefCache = (maxSize = 1e4) => {
|
|
47093
47123
|
const map32 = new Map;
|
|
@@ -47177,16 +47207,12 @@ var extractMentions = (content, byName) => {
|
|
|
47177
47207
|
}
|
|
47178
47208
|
return results;
|
|
47179
47209
|
};
|
|
47180
|
-
var decodeMessageRef = (message) => exports_Effect.gen(function* () {
|
|
47210
|
+
var decodeMessageRef = (message, base) => exports_Effect.gen(function* () {
|
|
47181
47211
|
const id3 = yield* decodeMessageId(String(message.id));
|
|
47182
47212
|
const channelId = yield* decodeChannelId(String(message.stream_id));
|
|
47183
47213
|
const channelName = yield* decodeChannelName(message.display_recipient);
|
|
47184
47214
|
const threadName3 = yield* decodeThreadName(message.subject);
|
|
47185
|
-
return {
|
|
47186
|
-
id: id3,
|
|
47187
|
-
channel: { id: channelId, name: channelName },
|
|
47188
|
-
thread: { name: threadName3 }
|
|
47189
|
-
};
|
|
47215
|
+
return buildMessageRef(base, id3, { id: channelId, name: channelName }, threadName3);
|
|
47190
47216
|
});
|
|
47191
47217
|
var decodeSenderIdentity = (message, directory) => {
|
|
47192
47218
|
const cached4 = directory.byId.get(message.sender_id);
|
|
@@ -47198,9 +47224,9 @@ var decodeSenderIdentity = (message, directory) => {
|
|
|
47198
47224
|
return { id: id3, name, kind: "human" };
|
|
47199
47225
|
});
|
|
47200
47226
|
};
|
|
47201
|
-
var messageToInboundEvents = (message, directory, boundIdentity) => exports_Effect.gen(function* () {
|
|
47227
|
+
var messageToInboundEvents = (message, directory, boundIdentity, base) => exports_Effect.gen(function* () {
|
|
47202
47228
|
const sender = yield* decodeSenderIdentity(message, directory);
|
|
47203
|
-
const ref = yield* decodeMessageRef(message);
|
|
47229
|
+
const ref = yield* decodeMessageRef(message, base);
|
|
47204
47230
|
const body = yield* decodeMessageBody(message.content);
|
|
47205
47231
|
const ts = yield* decodeTimestamp(message.timestamp);
|
|
47206
47232
|
const portMessage = {
|
|
@@ -47223,12 +47249,12 @@ var messageToInboundEvents = (message, directory, boundIdentity) => exports_Effe
|
|
|
47223
47249
|
});
|
|
47224
47250
|
var channelShapeSchema = exports_Schema.Struct({ stream_id: exports_Schema.Number });
|
|
47225
47251
|
var decodeChannelShape = exports_Schema.decodeUnknownEither(channelShapeSchema);
|
|
47226
|
-
var mapMessageEvent = (raw3, directory, boundIdentity) => {
|
|
47252
|
+
var mapMessageEvent = (raw3, directory, boundIdentity, base) => {
|
|
47227
47253
|
const message = raw3["message"];
|
|
47228
47254
|
if (exports_Either.isLeft(decodeChannelShape(message))) {
|
|
47229
47255
|
return exports_Effect.succeed([]);
|
|
47230
47256
|
}
|
|
47231
|
-
return decodeZulipMessageContent(message).pipe(exports_Effect.flatMap((parsed) => messageToInboundEvents(parsed, directory, boundIdentity)));
|
|
47257
|
+
return decodeZulipMessageContent(message).pipe(exports_Effect.flatMap((parsed) => messageToInboundEvents(parsed, directory, boundIdentity, base)));
|
|
47232
47258
|
};
|
|
47233
47259
|
var reactionKind = (op) => op === "add" ? "reaction-added" : "reaction-removed";
|
|
47234
47260
|
var decodeReactionIdentity = (reaction, directory) => {
|
|
@@ -47267,7 +47293,7 @@ var singleMessageResponseSchema = exports_Schema.Struct({
|
|
|
47267
47293
|
result: exports_Schema.Literal("success"),
|
|
47268
47294
|
messages: exports_Schema.Array(zulipMessageContentSchema)
|
|
47269
47295
|
});
|
|
47270
|
-
var fetchMessageRef = (http, messageId) => http.get("/messages", singleMessageResponseSchema, {
|
|
47296
|
+
var fetchMessageRef = (http, messageId, base) => http.get("/messages", singleMessageResponseSchema, {
|
|
47271
47297
|
anchor: messageId,
|
|
47272
47298
|
num_before: 0,
|
|
47273
47299
|
num_after: 0,
|
|
@@ -47277,7 +47303,7 @@ var fetchMessageRef = (http, messageId) => http.get("/messages", singleMessageRe
|
|
|
47277
47303
|
const message = res.messages[0];
|
|
47278
47304
|
if (message === undefined || message.id !== messageId)
|
|
47279
47305
|
return exports_Effect.succeed(exports_Option.none());
|
|
47280
|
-
return decodeMessageRef(message).pipe(exports_Effect.map(exports_Option.some));
|
|
47306
|
+
return decodeMessageRef(message, base).pipe(exports_Effect.map(exports_Option.some));
|
|
47281
47307
|
}));
|
|
47282
47308
|
var describeError = (err) => messageOf(err);
|
|
47283
47309
|
var markReplayed = (events) => events.map((ev) => {
|
|
@@ -47290,7 +47316,7 @@ var defaultRetrySchedule = exports_Schedule.exponential(exports_Duration.seconds
|
|
|
47290
47316
|
var skipWithLog = (line) => exports_Effect.logInfo(line).pipe(exports_Effect.as([]));
|
|
47291
47317
|
var processSingleEvent = (evt, directory, config2) => {
|
|
47292
47318
|
if (evt.type === "message") {
|
|
47293
|
-
return mapMessageEvent(evt, directory, config2.boundIdentity).pipe(exports_Effect.tap((events) => exports_Effect.sync(() => {
|
|
47319
|
+
return mapMessageEvent(evt, directory, config2.boundIdentity, config2.permalinkBase).pipe(exports_Effect.tap((events) => exports_Effect.sync(() => {
|
|
47294
47320
|
if (config2.messageRefCache !== undefined) {
|
|
47295
47321
|
populateCacheFromInboundEvents(config2.messageRefCache, events);
|
|
47296
47322
|
}
|
|
@@ -47308,7 +47334,7 @@ var processSingleEvent = (evt, directory, config2) => {
|
|
|
47308
47334
|
if (cachedTarget !== undefined) {
|
|
47309
47335
|
return eventsForTarget(cachedTarget).pipe(exports_Effect.catchTag("ParseError", (err) => skipWithLog(`commy zulip events: skipping reaction event id=${evt.id}: ${describeError(err)}`)));
|
|
47310
47336
|
}
|
|
47311
|
-
return fetchMessageRef(config2.http, reaction.message_id).pipe(exports_Effect.flatMap(exports_Option.match({
|
|
47337
|
+
return fetchMessageRef(config2.http, reaction.message_id, config2.permalinkBase).pipe(exports_Effect.flatMap(exports_Option.match({
|
|
47312
47338
|
onNone: () => exports_Effect.succeed([]),
|
|
47313
47339
|
onSome: (target) => {
|
|
47314
47340
|
cache.set(target.id, target);
|
|
@@ -47554,6 +47580,12 @@ class ReactivateForbidden extends exports_Data.TaggedError("ReactivateForbidden"
|
|
|
47554
47580
|
return `cannot reactivate deactivated bot ${this.userId}: the minter is not an ` + `organization administrator, and POST /users/{id}/reactivate is admin-only. ` + `Pin this identity with COMMY_BOT_NAME so it is never deactivated on ` + `release, or grant the minter admin rights. Underlying realm ` + `error: ${this.cause.message}`;
|
|
47555
47581
|
}
|
|
47556
47582
|
}
|
|
47583
|
+
|
|
47584
|
+
class AttachIdentityNotFound extends exports_Data.TaggedError("AttachIdentityNotFound") {
|
|
47585
|
+
get message() {
|
|
47586
|
+
return `attach mode: no provisioned bot named '${this.name}' on the realm. ` + `Provision the persona once and supply its stable api key before attaching.`;
|
|
47587
|
+
}
|
|
47588
|
+
}
|
|
47557
47589
|
var SHORT_NAME_PATTERN = /[^a-z0-9._-]/g;
|
|
47558
47590
|
var sanitiseShortName = (name) => {
|
|
47559
47591
|
const collapsed = name.toLowerCase().replace(SHORT_NAME_PATTERN, "-");
|
|
@@ -47741,7 +47773,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
47741
47773
|
const body = yield* decodeMessageBody(m.content);
|
|
47742
47774
|
const reactions = yield* mapHistoricalReactions(m.reactions, directory2);
|
|
47743
47775
|
return {
|
|
47744
|
-
ref:
|
|
47776
|
+
ref: decorateMessageRef(m.id, channel, m.subject),
|
|
47745
47777
|
sender,
|
|
47746
47778
|
body,
|
|
47747
47779
|
ts: m.ts,
|
|
@@ -47760,6 +47792,9 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
47760
47792
|
})
|
|
47761
47793
|
], { concurrency: 2 }).pipe(exports_Effect.flatMap(([directory2, res]) => exports_Effect.forEach(res.messages, toHistoricalMessage).pipe(exports_Effect.map((historical) => historical.filter(inRange(range5))), exports_Effect.flatMap((inRangeMessages) => exports_Effect.forEach(inRangeMessages, (m) => mapHistoricalMessage(m, channel, directory2))))));
|
|
47762
47794
|
const realmHost = new URL(config2.realmUrl).hostname;
|
|
47795
|
+
const base = permalinkBase(config2);
|
|
47796
|
+
const decorateChannel = (channel) => withChannelPermalink(base, channel);
|
|
47797
|
+
const decorateMessageRef = (id3, channel, threadName3) => buildMessageRef(base, id3, channel, threadName3);
|
|
47763
47798
|
const buildBotEmail = (shortName) => BotEmail(`${shortName}-bot@${realmHost}`);
|
|
47764
47799
|
const findAnyBotByName = (name) => minterHttp.get("/users", usersSchema).pipe(exports_Effect.map((res) => exports_Option.fromNullable(res.members.find((u) => u.full_name === name && u.is_bot))));
|
|
47765
47800
|
const mintBot = (name) => {
|
|
@@ -47793,6 +47828,18 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
47793
47828
|
return reactivate.pipe(exports_Effect.flatMap(() => regenerateBotKey(existing)));
|
|
47794
47829
|
}
|
|
47795
47830
|
})));
|
|
47831
|
+
const attachBot = (name, apiKey) => findAnyBotByName(name).pipe(exports_Effect.flatMap(exports_Option.match({
|
|
47832
|
+
onNone: () => exports_Effect.fail(new AttachIdentityNotFound({ name })),
|
|
47833
|
+
onSome: (existing) => BotEmail(existing.email).pipe(exports_Effect.map((email) => ({
|
|
47834
|
+
userId: ZulipUserRef(existing.user_id),
|
|
47835
|
+
apiKey,
|
|
47836
|
+
email
|
|
47837
|
+
})))
|
|
47838
|
+
})));
|
|
47839
|
+
const provideMintedFor = (name) => {
|
|
47840
|
+
const attach = config2.attachIdentity;
|
|
47841
|
+
return attach !== undefined && attach.name === name ? attachBot(name, exports_Redacted.value(attach.apiKey)) : acquireBot(name);
|
|
47842
|
+
};
|
|
47796
47843
|
const identity6 = {
|
|
47797
47844
|
currentIdentity: () => requireBound().pipe(exports_Effect.map((b) => b.identity)),
|
|
47798
47845
|
acquire: (name) => exports_SynchronizedRef.modifyEffect(boundRef, (current2) => exports_Option.match(current2, {
|
|
@@ -47805,7 +47852,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
47805
47852
|
}
|
|
47806
47853
|
return exports_Effect.die(new Error(`zulipAdapter: already bound to ${existing.acquiredName} — release() before acquiring ${name}`));
|
|
47807
47854
|
},
|
|
47808
|
-
onNone: () =>
|
|
47855
|
+
onNone: () => provideMintedFor(name).pipe(exports_Effect.flatMap((minted) => exports_Effect.gen(function* () {
|
|
47809
47856
|
const innerHttp = yield* makeZulipHttp(buildHttpConfig(config2.realmUrl, minted.email, minted.apiKey, config2.hostHeader)).pipe(exports_Effect.provideService(exports_HttpClient.HttpClient, httpClient));
|
|
47810
47857
|
const http = wrapBotHttp(innerHttp, recipientDirectory, minted.userId);
|
|
47811
47858
|
const ident = {
|
|
@@ -47891,10 +47938,10 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
47891
47938
|
}).pipe(exports_Effect.flatMap((res) => exports_Effect.gen(function* () {
|
|
47892
47939
|
const map32 = new Map;
|
|
47893
47940
|
for (const s of res.streams) {
|
|
47894
|
-
map32.set(s.name, {
|
|
47941
|
+
map32.set(s.name, decorateChannel({
|
|
47895
47942
|
id: yield* decodeChannelId(String(s.stream_id)),
|
|
47896
47943
|
name: yield* decodeChannelName(s.name)
|
|
47897
|
-
});
|
|
47944
|
+
}));
|
|
47898
47945
|
}
|
|
47899
47946
|
return map32;
|
|
47900
47947
|
})));
|
|
@@ -47916,7 +47963,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
47916
47963
|
to: effective.name,
|
|
47917
47964
|
content: body,
|
|
47918
47965
|
topic: thread === undefined ? "(no topic)" : thread.name
|
|
47919
|
-
}).pipe(exports_Effect.flatMap((sent) => decodeMessageId(String(sent.id)).pipe(exports_Effect.map((id3) =>
|
|
47966
|
+
}).pipe(exports_Effect.flatMap((sent) => decodeMessageId(String(sent.id)).pipe(exports_Effect.map((id3) => decorateMessageRef(id3, effective, thread?.name)))))))), exports_Effect.mapError((cause3) => cause3 instanceof UnknownChannel ? cause3 : new PublisherError({ operation: "post", cause: cause3 })));
|
|
47920
47967
|
},
|
|
47921
47968
|
edit: (message, body) => boundHttp().pipe(exports_Effect.flatMap((http) => http.patch(`/messages/${encodeURIComponent(message.id)}`, successSchema2, {
|
|
47922
47969
|
content: body
|
|
@@ -48018,6 +48065,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
48018
48065
|
}).pipe(exports_Effect.mapError((cause3) => new InboxError({ operation: "unsubscribe", cause: cause3 }))),
|
|
48019
48066
|
events: () => exports_Stream.unwrap(exports_Effect.all([exports_SynchronizedRef.get(boundRef), exports_Ref.get(inboxRef)]).pipe(exports_Effect.map(([current2, state]) => inboxEvents({
|
|
48020
48067
|
http: minterHttp,
|
|
48068
|
+
permalinkBase: base,
|
|
48021
48069
|
resolveDirectory: buildDirectoryLookup,
|
|
48022
48070
|
mode: currentMode(state),
|
|
48023
48071
|
messageRefCache,
|
|
@@ -48046,7 +48094,7 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
48046
48094
|
exports_SynchronizedRef.get(boundRef)
|
|
48047
48095
|
], { concurrency: 2 }).pipe(exports_Effect.flatMap(([directory2, res, current2]) => exports_Effect.forEach(res.messages.filter((raw3) => raw3.timestamp >= since), (raw3) => {
|
|
48048
48096
|
const { flags: _flags, ...message } = raw3;
|
|
48049
|
-
return messageToInboundEvents(message, directory2, exports_Option.getOrUndefined(current2)?.identity);
|
|
48097
|
+
return messageToInboundEvents(message, directory2, exports_Option.getOrUndefined(current2)?.identity, base);
|
|
48050
48098
|
}).pipe(exports_Effect.map((perMessage) => {
|
|
48051
48099
|
const out = [];
|
|
48052
48100
|
for (const mapped of perMessage) {
|
|
@@ -48102,7 +48150,8 @@ var zulipAdapter = (config2) => exports_Effect.gen(function* () {
|
|
|
48102
48150
|
return exports_Array.sort(threads, exports_Order.reverse(exports_Order.mapInput(exports_Order.number, (t) => t.lastPostTs)));
|
|
48103
48151
|
})));
|
|
48104
48152
|
}), exports_Effect.mapError((cause3) => new HistoryError({ operation: "recentThreads", cause: cause3 })));
|
|
48105
|
-
}
|
|
48153
|
+
},
|
|
48154
|
+
messagePermalink: (id3, hint) => (hint === undefined ? fetchMessageRef(minterHttp, Number(id3), base).pipe(exports_Effect.map(exports_Option.flatMap((ref) => exports_Option.fromNullable(ref.permalink)))) : lookupChannel(hint.channel).pipe(exports_Effect.map(exports_Option.flatMap((channel) => exports_Option.fromNullable(buildMessageRef(base, id3, channel, hint.thread).permalink))))).pipe(exports_Effect.mapError((cause3) => new HistoryError({ operation: "messagePermalink", cause: cause3 })))
|
|
48106
48155
|
};
|
|
48107
48156
|
const reconcileMinter = () => reconcileMinterSubscriptions({
|
|
48108
48157
|
listUnsubscribedPublicStreams: () => minterHttp.get("/streams", streamsListResponseSchema, {
|
|
@@ -55031,22 +55080,37 @@ var optionalBotName = (key) => optionalNonEmpty(key).pipe(exports_Config.mapOrFa
|
|
|
55031
55080
|
onSome: (botName) => exports_Either.right(exports_Option.some(botName))
|
|
55032
55081
|
})
|
|
55033
55082
|
})));
|
|
55083
|
+
var optionalApiKey = (key) => {
|
|
55084
|
+
const decode7 = exports_ParseResult.decodeUnknownEither(apiKeySchema);
|
|
55085
|
+
return optionalNonEmpty(key).pipe(exports_Config.mapOrFail((option7) => exports_Option.match(option7, {
|
|
55086
|
+
onNone: () => exports_Either.right(exports_Option.none()),
|
|
55087
|
+
onSome: (raw3) => decode7(raw3).pipe(exports_Either.mapBoth({
|
|
55088
|
+
onLeft: (issue2) => exports_ConfigError.InvalidData([key], exports_ParseResult.TreeFormatter.formatIssueSync(issue2)),
|
|
55089
|
+
onRight: (value5) => exports_Option.some(exports_Redacted.make(value5))
|
|
55090
|
+
}))
|
|
55091
|
+
})));
|
|
55092
|
+
};
|
|
55034
55093
|
var envConfig = exports_Config.all({
|
|
55035
55094
|
realmUrl: requiredBrand("ZULIP_SITE", realmUrlSchema, (v) => v),
|
|
55036
55095
|
minterEmail: requiredBrand("ZULIP_MINTER_EMAIL", botEmailSchema, (v) => v),
|
|
55037
55096
|
minterApiKey: exports_Config.redacted(requiredBrand("ZULIP_MINTER_API_KEY", apiKeySchema, (v) => v)),
|
|
55038
55097
|
botName: optionalBotName("COMMY_BOT_NAME"),
|
|
55098
|
+
botApiKey: optionalApiKey("COMMY_BOT_API_KEY"),
|
|
55039
55099
|
subscribe: optionalUserConfig("COMMY_SUBSCRIBE"),
|
|
55040
55100
|
project: optionalNonEmpty("COMMY_PROJECT"),
|
|
55041
55101
|
catchupWindowSeconds: optionalNonNegativeInt("COMMY_CATCHUP_WINDOW_SECONDS"),
|
|
55042
55102
|
sessionId: optionalSessionId("CLAUDE_CODE_SESSION_ID")
|
|
55043
|
-
}).pipe(exports_Config.
|
|
55103
|
+
}).pipe(exports_Config.mapOrFail((raw3) => {
|
|
55104
|
+
if (exports_Option.isSome(raw3.botApiKey) && exports_Option.isNone(raw3.botName)) {
|
|
55105
|
+
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"));
|
|
55106
|
+
}
|
|
55044
55107
|
const project3 = exports_Option.flatMap(raw3.project, sanitiseProjectSlug);
|
|
55045
|
-
return {
|
|
55108
|
+
return exports_Either.right({
|
|
55046
55109
|
realmUrl: raw3.realmUrl,
|
|
55047
55110
|
minterEmail: raw3.minterEmail,
|
|
55048
55111
|
minterApiKey: raw3.minterApiKey,
|
|
55049
55112
|
...exports_Option.match(raw3.botName, { onNone: () => ({}), onSome: (botName) => ({ botName }) }),
|
|
55113
|
+
...exports_Option.isSome(raw3.botName) && exports_Option.isSome(raw3.botApiKey) ? { attachIdentity: { name: raw3.botName.value, apiKey: raw3.botApiKey.value } } : {},
|
|
55050
55114
|
...exports_Option.match(raw3.subscribe, {
|
|
55051
55115
|
onNone: () => ({}),
|
|
55052
55116
|
onSome: (subscribe4) => ({ subscribe: subscribe4 })
|
|
@@ -55060,7 +55124,7 @@ var envConfig = exports_Config.all({
|
|
|
55060
55124
|
onNone: () => ({}),
|
|
55061
55125
|
onSome: (catchupWindowSeconds) => ({ catchupWindowSeconds })
|
|
55062
55126
|
})
|
|
55063
|
-
};
|
|
55127
|
+
});
|
|
55064
55128
|
}));
|
|
55065
55129
|
var parseEnv = envConfig.pipe(exports_Effect.mapError((error2) => envConfigError(renderConfigError(error2))));
|
|
55066
55130
|
var sanitiseProjectSlug = (raw3) => {
|
|
@@ -55121,7 +55185,8 @@ var ZulipAdapterLive = substrateAdapterLayer(exports_Effect.gen(function* () {
|
|
|
55121
55185
|
return yield* zulipAdapter({
|
|
55122
55186
|
realmUrl: parsed.realmUrl,
|
|
55123
55187
|
minterEmail: parsed.minterEmail,
|
|
55124
|
-
minterApiKey: parsed.minterApiKey
|
|
55188
|
+
minterApiKey: parsed.minterApiKey,
|
|
55189
|
+
...parsed.attachIdentity === undefined ? {} : { attachIdentity: parsed.attachIdentity }
|
|
55125
55190
|
});
|
|
55126
55191
|
}));
|
|
55127
55192
|
var subscribeFromEnv = (inbox, narrowSet, parsed) => {
|
|
@@ -55146,6 +55211,9 @@ var formatMessage = (event, botIdentityId) => {
|
|
|
55146
55211
|
["channel_name", ref.channel.name],
|
|
55147
55212
|
["thread", ref.thread?.name],
|
|
55148
55213
|
["message_id", ref.id],
|
|
55214
|
+
["permalink", ref.permalink],
|
|
55215
|
+
["channel_permalink", ref.channel.permalink],
|
|
55216
|
+
["thread_permalink", ref.thread?.permalink],
|
|
55149
55217
|
["sender_id", sender.id],
|
|
55150
55218
|
["sender_name", sender.name],
|
|
55151
55219
|
["sender_kind", sender.kind],
|
|
@@ -55164,6 +55232,7 @@ var formatReaction = (event, observedAt) => {
|
|
|
55164
55232
|
["target_message_id", event.target.id],
|
|
55165
55233
|
["target_channel_name", event.target.channel.name],
|
|
55166
55234
|
["target_thread", event.target.thread?.name],
|
|
55235
|
+
["target_permalink", event.target.permalink],
|
|
55167
55236
|
["reaction_emoji", event.emoji],
|
|
55168
55237
|
["reaction_action", action],
|
|
55169
55238
|
["by_id", event.by.id],
|
|
@@ -56898,7 +56967,7 @@ class Server extends Protocol {
|
|
|
56898
56967
|
}
|
|
56899
56968
|
|
|
56900
56969
|
// packages/mcp/mcp-server.ts
|
|
56901
|
-
var PLUGIN_VERSION = "0.
|
|
56970
|
+
var PLUGIN_VERSION = "0.14.0";
|
|
56902
56971
|
var COMMY_INSTRUCTIONS = `**Canonical substrate.** commy is the inter-agent channel on this workstation. Where you also see \`claude-peers\`, \`agent-mail\`, or Discord MCP tools, prefer commy — claude-peers and Discord are retiring (comms-sxf); agent-mail is a mail-shaped substrate kept for specific flows. Don't fan the same message across substrates.
|
|
56903
56972
|
|
|
56904
56973
|
**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.
|
|
@@ -56907,7 +56976,9 @@ var COMMY_INSTRUCTIONS = `**Canonical substrate.** commy is the inter-agent chan
|
|
|
56907
56976
|
|
|
56908
56977
|
**Subscriptions.** Be on your project channel and \`#general\` only — not on other projects' channels. The workstation's boot-time defaults come from the plugin's \`COMMY_SUBSCRIBE\` userConfig (tokens: \`channel:<name>\`, \`thread:<channel>/<thread>\`, \`new-topics:<channel>\`, \`mentions\`); adjust the live set via \`subscribe\`/\`unsubscribe\` at runtime. Inbound matches arrive as \`<channel source="commy" ...>\` blocks. If one arrives from a channel you aren't subscribed to, treat it as background context and don't reply — only post into your project channel, into \`#general\`, or into threads you've explicitly joined. **Refer to peers by name** — the \`sender_name\` (and reaction \`by_name\`) in those blocks — never by a bare number; reserve numbers for message and bead ids, which otherwise collide with identity in a thread.
|
|
56909
56978
|
|
|
56910
|
-
**
|
|
56979
|
+
**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}\`.
|
|
56980
|
+
|
|
56981
|
+
**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).
|
|
56911
56982
|
|
|
56912
56983
|
**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 (comms-uqf). 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.`;
|
|
56913
56984
|
var buildMcpServer = () => new Server({ name: "commy", version: PLUGIN_VERSION }, {
|
|
@@ -57001,7 +57072,8 @@ var identityShape = (identity6) => ({
|
|
|
57001
57072
|
});
|
|
57002
57073
|
var channelShape = (channel) => ({
|
|
57003
57074
|
id: channel.id,
|
|
57004
|
-
name: channel.name
|
|
57075
|
+
name: channel.name,
|
|
57076
|
+
permalink: channel.permalink ?? null
|
|
57005
57077
|
});
|
|
57006
57078
|
var createCache = () => {
|
|
57007
57079
|
const identityById = new Map;
|
|
@@ -57058,8 +57130,13 @@ var resolveMentions = (cache, mentionIds) => exports_Effect.forEach(mentionIds,
|
|
|
57058
57130
|
}));
|
|
57059
57131
|
var messageShape = (m) => ({
|
|
57060
57132
|
id: m.ref.id,
|
|
57061
|
-
channel: {
|
|
57062
|
-
|
|
57133
|
+
channel: {
|
|
57134
|
+
id: m.ref.channel.id,
|
|
57135
|
+
name: m.ref.channel.name,
|
|
57136
|
+
permalink: m.ref.channel.permalink ?? null
|
|
57137
|
+
},
|
|
57138
|
+
thread: m.ref.thread === undefined ? null : { name: m.ref.thread.name, permalink: m.ref.thread.permalink ?? null },
|
|
57139
|
+
permalink: m.ref.permalink ?? null,
|
|
57063
57140
|
sender: identityShape(m.sender),
|
|
57064
57141
|
body: m.body,
|
|
57065
57142
|
ts: m.ts,
|
|
@@ -57124,6 +57201,11 @@ var ReadThreadArgs = exports_Schema.Struct({
|
|
|
57124
57201
|
thread: exports_Schema.String,
|
|
57125
57202
|
...RangeArgs
|
|
57126
57203
|
});
|
|
57204
|
+
var MessageLinkArgs = exports_Schema.Struct({
|
|
57205
|
+
message_id: exports_Schema.String,
|
|
57206
|
+
channel_name: exports_Schema.optional(exports_Schema.String),
|
|
57207
|
+
thread: exports_Schema.optional(exports_Schema.String)
|
|
57208
|
+
});
|
|
57127
57209
|
var PresenceArgs = exports_Schema.Struct({ identity_id: exports_Schema.String });
|
|
57128
57210
|
var DownloadFileArgs = exports_Schema.Struct({ url_path: exports_Schema.String });
|
|
57129
57211
|
var UploadFileArgs = exports_Schema.Struct({ path: exports_Schema.String });
|
|
@@ -57235,7 +57317,7 @@ var buildToolDefs = (deps, cache) => {
|
|
|
57235
57317
|
},
|
|
57236
57318
|
{
|
|
57237
57319
|
name: "list_channels",
|
|
57238
|
-
description: "List every channel the substrate is aware of, returning {id, name} for each. Use this for discovery instead of guessing channel names — posting to a non-existent channel throws UnknownChannel.",
|
|
57320
|
+
description: "List every channel the substrate is aware of, returning {id, name, permalink} for each. Use this for discovery instead of guessing channel names — posting to a non-existent channel throws UnknownChannel. When you show a human a channel, link it by its permalink rather than a bare name.",
|
|
57239
57321
|
inputSchema: emptyObjectSchema,
|
|
57240
57322
|
handler: async () => {
|
|
57241
57323
|
const channels = await runEdge(adapter4.directory.listChannels());
|
|
@@ -57246,7 +57328,7 @@ var buildToolDefs = (deps, cache) => {
|
|
|
57246
57328
|
},
|
|
57247
57329
|
{
|
|
57248
57330
|
name: "post",
|
|
57249
|
-
description: "Post a message to a channel by name. Optional thread (topic), mentions (identity ids the bot has seen), and reply_to (message id). To ping someone on Zulip, write the @**Name** markup inline in body where you want it rendered — the mentions array is notification metadata only and does NOT modify body. Returns {message_id, channel_id, channel_name, thread}.",
|
|
57331
|
+
description: "Post a message to a channel by name. Optional thread (topic), mentions (identity ids the bot has seen), and reply_to (message id). To ping someone on Zulip, write the @**Name** markup inline in body where you want it rendered — the mentions array is notification metadata only and does NOT modify body. Returns {message_id, channel_id, channel_name, thread, permalink}. When you show a human this message afterwards, link it by the returned permalink — never a bare name or message id.",
|
|
57250
57332
|
inputSchema: {
|
|
57251
57333
|
type: "object",
|
|
57252
57334
|
properties: {
|
|
@@ -57296,7 +57378,8 @@ var buildToolDefs = (deps, cache) => {
|
|
|
57296
57378
|
message_id: ref.id,
|
|
57297
57379
|
channel_id: ref.channel.id,
|
|
57298
57380
|
channel_name: ref.channel.name,
|
|
57299
|
-
thread: ref.thread === undefined ? null : { name: ref.thread.name }
|
|
57381
|
+
thread: ref.thread === undefined ? null : { name: ref.thread.name },
|
|
57382
|
+
permalink: ref.permalink ?? null
|
|
57300
57383
|
};
|
|
57301
57384
|
}
|
|
57302
57385
|
},
|
|
@@ -57450,7 +57533,7 @@ var buildToolDefs = (deps, cache) => {
|
|
|
57450
57533
|
},
|
|
57451
57534
|
{
|
|
57452
57535
|
name: "read_channel",
|
|
57453
|
-
description: "Read recent messages from a channel by name. Returns {messages: Message[]} bounded by optional since/until/limit.",
|
|
57536
|
+
description: "Read recent messages from a channel by name. Returns {messages: Message[]} bounded by optional since/until/limit. Each message carries a clickable permalink (and channel.permalink / thread.permalink) — when you cite one of these to a human, render it as that permalink, not a bare name or id.",
|
|
57454
57537
|
inputSchema: {
|
|
57455
57538
|
type: "object",
|
|
57456
57539
|
properties: {
|
|
@@ -57475,7 +57558,7 @@ var buildToolDefs = (deps, cache) => {
|
|
|
57475
57558
|
},
|
|
57476
57559
|
{
|
|
57477
57560
|
name: "read_thread",
|
|
57478
|
-
description: "Read recent messages from a thread (topic) within a channel. Returns {messages: Message[]} bounded by optional since/until/limit.",
|
|
57561
|
+
description: "Read recent messages from a thread (topic) within a channel. Returns {messages: Message[]} bounded by optional since/until/limit. Each message carries a clickable permalink (and channel.permalink / thread.permalink) — when you cite one of these to a human, render it as that permalink, not a bare name or id.",
|
|
57479
57562
|
inputSchema: {
|
|
57480
57563
|
type: "object",
|
|
57481
57564
|
properties: {
|
|
@@ -57499,6 +57582,41 @@ var buildToolDefs = (deps, cache) => {
|
|
|
57499
57582
|
return { messages: messages.map(messageShape) };
|
|
57500
57583
|
}
|
|
57501
57584
|
},
|
|
57585
|
+
{
|
|
57586
|
+
name: "message_link",
|
|
57587
|
+
description: "Return the canonical clickable permalink for a message id: {permalink}. Use when you hold only an id (e.g. one cited elsewhere) and want the URL without re-deriving the narrow format — messages from post/read already carry a permalink. A message just posted or read is resolved from cache; otherwise pass channel_name (and thread) to build the link directly, or omit them to look the message up by id. Returns {permalink: null} when the message cannot be resolved.",
|
|
57588
|
+
inputSchema: {
|
|
57589
|
+
type: "object",
|
|
57590
|
+
properties: {
|
|
57591
|
+
message_id: { type: "string", description: "Message id to link to" },
|
|
57592
|
+
channel_name: {
|
|
57593
|
+
type: "string",
|
|
57594
|
+
description: "Channel the message lives in (lets the link be built without a lookup)"
|
|
57595
|
+
},
|
|
57596
|
+
thread: {
|
|
57597
|
+
type: "string",
|
|
57598
|
+
description: "Thread / topic the message lives in (sharpens the link to the topic)"
|
|
57599
|
+
}
|
|
57600
|
+
},
|
|
57601
|
+
required: ["message_id"],
|
|
57602
|
+
additionalProperties: false
|
|
57603
|
+
},
|
|
57604
|
+
handler: async (args2) => {
|
|
57605
|
+
const permalink = await runEdge(exports_Effect.gen(function* () {
|
|
57606
|
+
const parsed = yield* exports_Schema.decodeUnknown(MessageLinkArgs)(args2);
|
|
57607
|
+
const id3 = yield* decodeMessageId(parsed.message_id);
|
|
57608
|
+
const cached5 = cache.messageById.get(id3);
|
|
57609
|
+
if (cached5?.permalink !== undefined)
|
|
57610
|
+
return cached5.permalink;
|
|
57611
|
+
const hint = parsed.channel_name === undefined ? undefined : {
|
|
57612
|
+
channel: yield* decodeChannelName(parsed.channel_name),
|
|
57613
|
+
...parsed.thread === undefined ? {} : { thread: yield* decodeThreadName(parsed.thread) }
|
|
57614
|
+
};
|
|
57615
|
+
return exports_Option.getOrNull(yield* adapter4.history.messagePermalink(id3, hint));
|
|
57616
|
+
}));
|
|
57617
|
+
return { permalink };
|
|
57618
|
+
}
|
|
57619
|
+
},
|
|
57502
57620
|
{
|
|
57503
57621
|
name: "presence",
|
|
57504
57622
|
description: "Return presence for the given identity id: online/idle/offline for humans, or 'unknown' for agents (bots have no presence concept — Zulip presence is human-only). Identity must already be cached via list_agents, list_humans, resolve, or current_identity — call one of those first when given a name.",
|