claude-threads 1.39.0 → 1.39.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.39.2] - 2026-10-01
9
+
10
+ ### Fixed
11
+ - **Slack: reacting to a permission prompt works again** (#622, thanks @elganfd). With interactive permissions on Slack every 👍 / ✅ / 👎 on a tool permission prompt was ignored, so the prompt sat until it timed out and denied. Two causes, each enough on its own: the permission server sent its `users.info` lookup as a JSON body, which Slack ignores on read methods, so every reacting user came back `user_not_found` and counted as unauthorized; and it opened a Socket Mode connection of its own, while Slack spreads an app's events over all its connections, so it saw only part of the reactions (and took that part of the bot's own events while a prompt waited). Read methods now go out as GET requests, and the prompt is polled for reactions instead of watched over a second connection. The same JSON-body problem affected `read_post`, `read_thread`, `read_channel_history`, the channel lookups and DM recipient resolution on Slack; those are fixed too. Plan approvals and questions were never affected.
12
+ - **A skin-toned 👍 no longer denies** (#628). Slack names an emoji picked with a skin tone `+1::skin-tone-3`, and the permission prompt treats any emoji it does not recognize as a denial. Emoji are now matched on their base name, which also covers plan approvals and the other reaction controls.
13
+
14
+ ### Security
15
+ - `fast-uri` 4.1.4 → 4.2.1 (#625) and `ip-address` 10.4.0 → 10.7.2 (#626), both bundled into what users run.
16
+
17
+ ### Changed
18
+ - `@modelcontextprotocol/sdk` 1.30.1, `hono` 4.13.9, `ws` 8.22.0 (#624).
19
+
20
+ ## [1.39.1] - 2026-09-25
21
+
22
+ ### Fixed
23
+ - **A long burst or a long code block no longer overflows one post** (#617, #619). When a lot of text reached a reply that already had a post, the part that did not fit went into one new post of any size, and a code block Claude was still writing kept growing the last post without limit. Slack cut such a post off; Mattermost refused it, and the text was posted twice later. Both now split over posts that fit, with code blocks closed and reopened with their language. Normal replies below the limit are unchanged.
24
+
8
25
  ## [1.39.0] - 2026-09-25
9
26
 
10
27
  ### Added
package/dist/index.js CHANGED
@@ -5290,8 +5290,8 @@ function buildPermissionArgs(opts) {
5290
5290
  mcpEnv[AGENT_FEATURES_ENV.DCM] = "1";
5291
5291
  }
5292
5292
  }
5293
- if (opts.platformConfig.appToken) {
5294
- mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
5293
+ if (opts.platformConfig.apiUrl) {
5294
+ mcpEnv.PLATFORM_API_URL = opts.platformConfig.apiUrl;
5295
5295
  }
5296
5296
  if (opts.workingDir) {
5297
5297
  mcpEnv[OUTBOUND_ENV.SESSION_WORKING_DIR] = opts.workingDir;
@@ -6032,23 +6032,26 @@ var init_usage = __esm(() => {
6032
6032
  });
6033
6033
 
6034
6034
  // src/utils/emoji.ts
6035
+ function withoutSkinTone(emoji) {
6036
+ return emoji.replace(/::skin-tone-\d$/, "");
6037
+ }
6035
6038
  function isApprovalEmoji(emoji) {
6036
- return APPROVAL_EMOJIS.includes(emoji);
6039
+ return APPROVAL_EMOJIS.includes(withoutSkinTone(emoji));
6037
6040
  }
6038
6041
  function isDenialEmoji(emoji) {
6039
- return DENIAL_EMOJIS.includes(emoji);
6042
+ return DENIAL_EMOJIS.includes(withoutSkinTone(emoji));
6040
6043
  }
6041
6044
  function isAllowAllEmoji(emoji) {
6042
- return ALLOW_ALL_EMOJIS.includes(emoji);
6045
+ return ALLOW_ALL_EMOJIS.includes(withoutSkinTone(emoji));
6043
6046
  }
6044
6047
  function isCancelEmoji(emoji) {
6045
- return CANCEL_EMOJIS.includes(emoji);
6048
+ return CANCEL_EMOJIS.includes(withoutSkinTone(emoji));
6046
6049
  }
6047
6050
  function isEscapeEmoji(emoji) {
6048
- return ESCAPE_EMOJIS.includes(emoji);
6051
+ return ESCAPE_EMOJIS.includes(withoutSkinTone(emoji));
6049
6052
  }
6050
6053
  function isResumeEmoji(emoji) {
6051
- return RESUME_EMOJIS.includes(emoji);
6054
+ return RESUME_EMOJIS.includes(withoutSkinTone(emoji));
6052
6055
  }
6053
6056
  function isMinimizeToggleEmoji(emoji) {
6054
6057
  return MINIMIZE_TOGGLE_EMOJIS.includes(emoji);
@@ -17137,11 +17140,23 @@ var require_websocket = __commonJS(function(exports, module) {
17137
17140
  this._isServer = false;
17138
17141
  this._redirects = 0;
17139
17142
  if (protocols === undefined) {
17140
- protocols = [];
17143
+ if (!options || options.protocols === undefined) {
17144
+ protocols = [];
17145
+ } else if (Array.isArray(options.protocols)) {
17146
+ protocols = options.protocols;
17147
+ } else {
17148
+ protocols = [options.protocols];
17149
+ }
17141
17150
  } else if (!Array.isArray(protocols)) {
17142
17151
  if (typeof protocols === "object" && protocols !== null) {
17143
17152
  options = protocols;
17144
- protocols = [];
17153
+ if (options.protocols === undefined) {
17154
+ protocols = [];
17155
+ } else if (Array.isArray(options.protocols)) {
17156
+ protocols = options.protocols;
17157
+ } else {
17158
+ protocols = [options.protocols];
17159
+ }
17145
17160
  } else {
17146
17161
  protocols = [protocols];
17147
17162
  }
@@ -17260,7 +17275,6 @@ var require_websocket = __commonJS(function(exports, module) {
17260
17275
  }
17261
17276
  return;
17262
17277
  }
17263
- this._readyState = WebSocket.CLOSING;
17264
17278
  this._sender.close(code, data, !this._isServer, (err) => {
17265
17279
  if (err)
17266
17280
  return;
@@ -17269,6 +17283,7 @@ var require_websocket = __commonJS(function(exports, module) {
17269
17283
  this._socket.end();
17270
17284
  }
17271
17285
  });
17286
+ this._readyState = WebSocket.CLOSING;
17272
17287
  setCloseTimer(this);
17273
17288
  }
17274
17289
  pause() {
@@ -17456,6 +17471,7 @@ var require_websocket = __commonJS(function(exports, module) {
17456
17471
  socketPath: undefined,
17457
17472
  hostname: undefined,
17458
17473
  protocol: undefined,
17474
+ protocols: undefined,
17459
17475
  timeout: undefined,
17460
17476
  method: "GET",
17461
17477
  host: undefined,
@@ -30090,7 +30106,7 @@ var require_react_reconciler = __commonJS(function(exports, module) {
30090
30106
  });
30091
30107
 
30092
30108
  // node_modules/ws/wrapper.mjs
30093
- var import_stream, import_extension, import_permessage_deflate, import_receiver, import_sender, import_subprotocol, import_websocket5, import_websocket_server, wrapper_default;
30109
+ var import_stream, import_extension, import_permessage_deflate, import_receiver, import_sender, import_subprotocol, import_websocket4, import_websocket_server, wrapper_default;
30094
30110
  var init_wrapper = __esm(() => {
30095
30111
  import_stream = __toESM(require_stream(), 1);
30096
30112
  import_extension = __toESM(require_extension(), 1);
@@ -30098,9 +30114,9 @@ var init_wrapper = __esm(() => {
30098
30114
  import_receiver = __toESM(require_receiver(), 1);
30099
30115
  import_sender = __toESM(require_sender(), 1);
30100
30116
  import_subprotocol = __toESM(require_subprotocol(), 1);
30101
- import_websocket5 = __toESM(require_websocket(), 1);
30117
+ import_websocket4 = __toESM(require_websocket(), 1);
30102
30118
  import_websocket_server = __toESM(require_websocket_server(), 1);
30103
- wrapper_default = import_websocket5.default;
30119
+ wrapper_default = import_websocket4.default;
30104
30120
  });
30105
30121
 
30106
30122
  // node_modules/ink/build/devtools-window-polyfill.js
@@ -60490,26 +60506,51 @@ ${body}` : this.state.header;
60490
60506
  chunkCount: chunks.length,
60491
60507
  reason: "no_currentPostId"
60492
60508
  }, "flush");
60493
- let lastPosted = null;
60494
- for (let i = 0;i < chunks.length; i++) {
60495
- const created = await this.createNewPost(ctx, chunks[i], lastPosted ? "" : pendingAtFlushStart);
60496
- if (!created) {
60497
- if (lastPosted) {
60498
- this.state.pendingContent = chunks.slice(i).join(`
60509
+ await this.postChunks(ctx, chunks, pendingAtFlushStart, false);
60510
+ }
60511
+ }
60512
+ async splitOversized(ctx, content, pendingAtFlushStart, hardThreshold) {
60513
+ const postId = this.state.currentPostId;
60514
+ if (!postId)
60515
+ return false;
60516
+ const reserve = this.headerReserve(postId);
60517
+ if (content.length + reserve <= ctx.platform.getMessageLimits().maxLength)
60518
+ return false;
60519
+ const [firstPiece, ...rest] = splitByLength(content, hardThreshold - reserve);
60520
+ let landed = false;
60521
+ await this.tryUpdatePost(ctx, postId, firstPiece, "handleSplit", { reason: "oversized_code_block", contentLength: content.length, pieces: rest.length + 1 }, { reason: "oversized_code_block_failed" }, () => {
60522
+ landed = true;
60523
+ }, () => {
60524
+ if (postId === this.state.headerPostId)
60525
+ this.state.headerBody = firstPiece;
60526
+ });
60527
+ this.state.currentPostId = null;
60528
+ this.state.currentPostContent = "";
60529
+ if (landed)
60530
+ this.clearFlushedContent(pendingAtFlushStart);
60531
+ await this.postChunks(ctx, rest, pendingAtFlushStart, landed);
60532
+ return true;
60533
+ }
60534
+ async postChunks(ctx, chunks, pendingAtFlushStart, flushCleared) {
60535
+ let cleared = flushCleared;
60536
+ for (let i = 0;i < chunks.length; i++) {
60537
+ const created = await this.createNewPost(ctx, chunks[i], cleared ? "" : pendingAtFlushStart);
60538
+ if (!created) {
60539
+ if (cleared) {
60540
+ this.state.pendingContent = chunks.slice(i).join(`
60499
60541
 
60500
60542
  `) + (this.state.pendingContent ? `
60501
60543
 
60502
60544
  ${this.state.pendingContent}` : "");
60503
- }
60504
- this.state.currentPostId = null;
60505
- this.state.currentPostContent = "";
60506
- break;
60507
- }
60508
- lastPosted = { id: this.state.currentPostId, content: this.state.currentPostContent };
60509
- if (i < chunks.length - 1) {
60510
- this.state.currentPostId = null;
60511
- this.state.currentPostContent = "";
60512
60545
  }
60546
+ this.state.currentPostId = null;
60547
+ this.state.currentPostContent = "";
60548
+ return;
60549
+ }
60550
+ cleared = true;
60551
+ if (i < chunks.length - 1) {
60552
+ this.state.currentPostId = null;
60553
+ this.state.currentPostContent = "";
60513
60554
  }
60514
60555
  }
60515
60556
  }
@@ -60570,6 +60611,8 @@ ${this.state.pendingContent}` : "");
60570
60611
  }
60571
60612
  if (codeBlockOpenPosition !== undefined) {
60572
60613
  if (codeBlockOpenPosition === 0) {
60614
+ if (await this.splitOversized(ctx, content, pendingAtFlushStart, hardThreshold))
60615
+ return;
60573
60616
  if (this.state.currentPostId) {
60574
60617
  const postId = this.state.currentPostId;
60575
60618
  await this.tryUpdatePost(ctx, postId, content, "handleSplit", { reason: "code_block_at_start", contentLength: content.length }, { reason: "code_block_at_start_failed" }, () => {
@@ -60587,6 +60630,8 @@ ${this.state.pendingContent}` : "");
60587
60630
  if (breakBeforeCodeBlock > 0) {
60588
60631
  breakPoint = breakBeforeCodeBlock;
60589
60632
  } else {
60633
+ if (await this.splitOversized(ctx, content, pendingAtFlushStart, hardThreshold))
60634
+ return;
60590
60635
  if (this.state.currentPostId) {
60591
60636
  const postId = this.state.currentPostId;
60592
60637
  await this.tryUpdatePost(ctx, postId, content, "handleSplit", { reason: "no_break_before_code_block", contentLength: content.length }, { reason: "no_break_before_code_block_failed" }, () => {
@@ -60602,9 +60647,12 @@ ${this.state.pendingContent}` : "");
60602
60647
  }
60603
60648
  const firstPart = content.substring(0, breakPoint).trim();
60604
60649
  const remainder = content.substring(breakPoint).trim();
60650
+ let firstPartLanded = false;
60605
60651
  if (this.state.currentPostId) {
60606
60652
  const postId = this.state.currentPostId;
60607
- await this.tryUpdatePost(ctx, postId, firstPart, "handleSplit", { reason: "split_first_part", firstPartLength: firstPart.length, remainderLength: remainder.length }, { reason: "split_first_part_failed" }, () => {}, () => {
60653
+ await this.tryUpdatePost(ctx, postId, firstPart, "handleSplit", { reason: "split_first_part", firstPartLength: firstPart.length, remainderLength: remainder.length }, { reason: "split_first_part_failed" }, () => {
60654
+ firstPartLanded = true;
60655
+ }, () => {
60608
60656
  if (postId === this.state.headerPostId) {
60609
60657
  this.state.headerBody = firstPart;
60610
60658
  }
@@ -60613,7 +60661,14 @@ ${this.state.pendingContent}` : "");
60613
60661
  this.state.currentPostId = null;
60614
60662
  this.state.currentPostContent = "";
60615
60663
  if (remainder) {
60616
- await this.createNewPost(ctx, remainder, pendingAtFlushStart);
60664
+ const MAX_POST_LENGTH = ctx.platform.getMessageLimits().maxLength;
60665
+ const reserve = this.headerReserve(null);
60666
+ const chunks = remainder.length + reserve > MAX_POST_LENGTH ? splitByLength(remainder, hardThreshold - reserve) : [remainder];
60667
+ if (firstPartLanded)
60668
+ this.clearFlushedContent(pendingAtFlushStart);
60669
+ await this.postChunks(ctx, chunks, pendingAtFlushStart, firstPartLanded);
60670
+ } else if (firstPartLanded) {
60671
+ this.clearFlushedContent(pendingAtFlushStart);
60617
60672
  }
60618
60673
  }
60619
60674
  async createNewPost(ctx, content, pendingAtFlushStart) {
@@ -71851,6 +71906,7 @@ class SlackClient extends BasePlatformClient {
71851
71906
  channelId: this.channelId,
71852
71907
  allowedUsers: this.allowedUsers,
71853
71908
  appToken: this.appToken,
71909
+ apiUrl: this.apiUrl,
71854
71910
  outboundFiles: this.outboundFiles,
71855
71911
  mcpServers: this.mcpServers,
71856
71912
  strictMcpConfig: this.strictMcpConfig,
@@ -72158,6 +72214,14 @@ var apiLog = createLogger("mm-api");
72158
72214
 
72159
72215
  // src/platform/slack/mcp-platform-api.ts
72160
72216
  init_logger();
72217
+ var READ_METHODS = new Set([
72218
+ "users.info",
72219
+ "conversations.history",
72220
+ "conversations.replies",
72221
+ "conversations.info",
72222
+ "conversations.members",
72223
+ "reactions.get"
72224
+ ]);
72161
72225
  // src/session/manager.ts
72162
72226
  import { EventEmitter as EventEmitter4 } from "events";
72163
72227
 
@@ -3547,6 +3547,22 @@ var require_utils = __commonJS(function(exports, module) {
3547
3547
  }
3548
3548
  return output;
3549
3549
  }
3550
+ function recomposeZonedIPv6Host(component) {
3551
+ const zone = component.ipv6Zone;
3552
+ if (zone === undefined)
3553
+ return;
3554
+ const host = component.host;
3555
+ const separator = host.indexOf("%");
3556
+ if (separator === -1)
3557
+ return;
3558
+ const address = host.slice(0, separator);
3559
+ const escaped = address + "%25" + zone;
3560
+ const derived = normalizeIPv6("[" + escaped + "]");
3561
+ if (derived.isIPV6 === true && derived.host === host) {
3562
+ return "[" + escaped + "]";
3563
+ }
3564
+ return;
3565
+ }
3550
3566
  function recomposeAuthority(component) {
3551
3567
  const uriTokens = [];
3552
3568
  if (component.userinfo !== undefined) {
@@ -3556,15 +3572,20 @@ var require_utils = __commonJS(function(exports, module) {
3556
3572
  if (component.host !== undefined) {
3557
3573
  let host = component.host;
3558
3574
  if (!isIPv4(host)) {
3559
- let ipV6res = normalizeIPv6(host);
3560
- if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3561
- host = normalizePercentEncoding(host, true);
3562
- ipV6res = normalizeIPv6(host);
3563
- }
3564
- if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3565
- host = `[${ipV6res.escapedHost}]`;
3575
+ const zonedHost = recomposeZonedIPv6Host(component);
3576
+ if (zonedHost !== undefined) {
3577
+ host = zonedHost;
3566
3578
  } else {
3567
- host = reescapeHostDelimiters(host, false);
3579
+ let ipV6res = normalizeIPv6(host);
3580
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3581
+ host = normalizePercentEncoding(host, true);
3582
+ ipV6res = normalizeIPv6(host);
3583
+ }
3584
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3585
+ host = `[${ipV6res.escapedHost}]`;
3586
+ } else {
3587
+ host = reescapeHostDelimiters(host, false);
3588
+ }
3568
3589
  }
3569
3590
  }
3570
3591
  uriTokens.push(host);
@@ -3777,6 +3798,7 @@ var require_schemes = __commonJS(function(exports, module) {
3777
3798
  var HEX_PAIR = /^[\da-f]{2}$/iu;
3778
3799
  var MAILTO_DOMAIN_LITERAL = /^\[[\x21-\x5A\x5E-\x7E]*\]$/u;
3779
3800
  var MAILTO_DOMAIN_ERROR = "URI mailto has an invalid recipient domain.";
3801
+ var MAILTO_AUTHORITY_ERROR = "URI mailto must not have an authority component.";
3780
3802
  var HAS_SURROGATE = /[\uD800-\uDFFF]/u;
3781
3803
  function decodeHex(str) {
3782
3804
  if (typeof str !== "string" || str.indexOf("%") === -1) {
@@ -3891,30 +3913,37 @@ var require_schemes = __commonJS(function(exports, module) {
3891
3913
  mailtoComponent.error = mailtoComponent.error || "URI mailto has malformed header fields.";
3892
3914
  continue;
3893
3915
  }
3894
- const name = eqIdx === -1 ? token : token.slice(0, eqIdx);
3916
+ const name = decodeHex(eqIdx === -1 ? token : token.slice(0, eqIdx));
3917
+ const normalizedName = name.toLowerCase();
3895
3918
  const value = eqIdx === -1 ? "" : token.slice(eqIdx + 1);
3896
- if (name === "to") {
3919
+ if (normalizedName === "to") {
3897
3920
  const addrs = value.split(",");
3898
3921
  for (let j = 0;j < addrs.length; j++)
3899
3922
  to.push(addrs[j]);
3900
3923
  continue;
3901
3924
  }
3902
- if (name === "subject") {
3925
+ if (normalizedName === "subject") {
3903
3926
  mailtoComponent.subject = decodeHex(value);
3904
3927
  continue;
3905
3928
  }
3906
- if (name === "body") {
3929
+ if (normalizedName === "body") {
3907
3930
  mailtoComponent.body = decodeHex(value);
3908
3931
  continue;
3909
3932
  }
3910
3933
  if (headers === null)
3911
3934
  headers = Object.create(null);
3912
- headers[decodeHex(name)] = decodeHex(value);
3935
+ headers[name] = decodeHex(value);
3913
3936
  }
3914
3937
  if (headers !== null)
3915
3938
  mailtoComponent.headers = headers;
3916
3939
  }
3917
3940
  mailtoComponent.query = undefined;
3941
+ if (to.length > 0 && (mailtoComponent.userinfo !== undefined || mailtoComponent.host !== undefined || mailtoComponent.port !== undefined)) {
3942
+ mailtoComponent.userinfo = undefined;
3943
+ mailtoComponent.host = undefined;
3944
+ mailtoComponent.port = undefined;
3945
+ mailtoComponent.error = mailtoComponent.error || MAILTO_AUTHORITY_ERROR;
3946
+ }
3918
3947
  for (let i = 0;i < to.length; i++) {
3919
3948
  const rawAddr = to[i];
3920
3949
  const atIdx = rawAddr.lastIndexOf("@");
@@ -3931,10 +3960,35 @@ var require_schemes = __commonJS(function(exports, module) {
3931
3960
  mailtoComponent.to = to;
3932
3961
  return mailtoComponent;
3933
3962
  }
3963
+ function mailtoEncodeHeaderName(name) {
3964
+ return encodeWithAllow(name.replace(/%/gu, "%25"), HFNAME);
3965
+ }
3934
3966
  function mailtoSerialize(component, options) {
3935
3967
  const mailtoComponent = component;
3936
3968
  const to = Array.isArray(mailtoComponent.to) ? mailtoComponent.to.slice() : [];
3969
+ const sourceHeaders = mailtoComponent.headers && typeof mailtoComponent.headers === "object" ? Object.assign(Object.create(null), mailtoComponent.headers) : Object.create(null);
3970
+ const headers = Object.create(null);
3971
+ for (const name in sourceHeaders) {
3972
+ const normalizedName = name.toLowerCase();
3973
+ if (normalizedName === "to") {
3974
+ const addrs = String(sourceHeaders[name]).split(",");
3975
+ for (let i = 0;i < addrs.length; i++)
3976
+ to.push(addrs[i]);
3977
+ } else if (normalizedName === "subject" || normalizedName === "body") {
3978
+ headers[normalizedName] = sourceHeaders[name];
3979
+ } else {
3980
+ headers[name] = sourceHeaders[name];
3981
+ }
3982
+ }
3983
+ if (mailtoComponent.subject !== undefined)
3984
+ headers.subject = mailtoComponent.subject;
3985
+ if (mailtoComponent.body !== undefined)
3986
+ headers.body = mailtoComponent.body;
3987
+ mailtoComponent.headers = headers;
3937
3988
  if (to.length) {
3989
+ mailtoComponent.userinfo = undefined;
3990
+ mailtoComponent.host = undefined;
3991
+ mailtoComponent.port = undefined;
3938
3992
  for (let i = 0;i < to.length; i++) {
3939
3993
  const addr = String(to[i]);
3940
3994
  const atIdx = addr.lastIndexOf("@");
@@ -3950,18 +4004,12 @@ var require_schemes = __commonJS(function(exports, module) {
3950
4004
  } else {
3951
4005
  mailtoComponent.path = undefined;
3952
4006
  }
3953
- const headers = mailtoComponent.headers && typeof mailtoComponent.headers === "object" ? Object.assign(Object.create(null), mailtoComponent.headers) : Object.create(null);
3954
- if (mailtoComponent.subject)
3955
- headers.subject = mailtoComponent.subject;
3956
- if (mailtoComponent.body)
3957
- headers.body = mailtoComponent.body;
3958
- mailtoComponent.headers = headers;
3959
4007
  let query = "";
3960
4008
  let count = 0;
3961
4009
  for (const name in headers) {
3962
4010
  if (count++ !== 0)
3963
4011
  query += "&";
3964
- query += encodeWithAllow(name, HFNAME) + "=" + encodeWithAllow(String(headers[name]), HFNAME);
4012
+ query += mailtoEncodeHeaderName(name) + "=" + encodeWithAllow(String(headers[name]), HFNAME);
3965
4013
  }
3966
4014
  if (count !== 0) {
3967
4015
  mailtoComponent.query = query;
@@ -4054,6 +4102,12 @@ var require_fast_uri = __commonJS(function(exports, module) {
4054
4102
  schemelessOptions.skipEscape = true;
4055
4103
  return serialize(resolved, schemelessOptions);
4056
4104
  }
4105
+ function copyHost(target, source) {
4106
+ target.host = source.host;
4107
+ if (source.ipv6Zone !== undefined) {
4108
+ target.ipv6Zone = source.ipv6Zone;
4109
+ }
4110
+ }
4057
4111
  function resolveComponent(base, relative, options, skipNormalization) {
4058
4112
  const target = {};
4059
4113
  if (!skipNormalization) {
@@ -4064,14 +4118,14 @@ var require_fast_uri = __commonJS(function(exports, module) {
4064
4118
  if (!options.tolerant && relative.scheme) {
4065
4119
  target.scheme = relative.scheme;
4066
4120
  target.userinfo = relative.userinfo;
4067
- target.host = relative.host;
4121
+ copyHost(target, relative);
4068
4122
  target.port = relative.port;
4069
4123
  target.path = removeDotSegments(relative.path || "");
4070
4124
  target.query = relative.query;
4071
4125
  } else {
4072
4126
  if (relative.userinfo !== undefined || relative.host !== undefined || relative.port !== undefined) {
4073
4127
  target.userinfo = relative.userinfo;
4074
- target.host = relative.host;
4128
+ copyHost(target, relative);
4075
4129
  target.port = relative.port;
4076
4130
  target.path = removeDotSegments(relative.path || "");
4077
4131
  target.query = relative.query;
@@ -4099,7 +4153,7 @@ var require_fast_uri = __commonJS(function(exports, module) {
4099
4153
  target.query = relative.query;
4100
4154
  }
4101
4155
  target.userinfo = base.userinfo;
4102
- target.host = base.host;
4156
+ copyHost(target, base);
4103
4157
  target.port = base.port;
4104
4158
  }
4105
4159
  target.scheme = base.scheme;
@@ -4115,6 +4169,7 @@ var require_fast_uri = __commonJS(function(exports, module) {
4115
4169
  function serialize(cmpts, opts) {
4116
4170
  const component = {
4117
4171
  host: cmpts.host,
4172
+ ipv6Zone: cmpts.ipv6Zone,
4118
4173
  scheme: cmpts.scheme,
4119
4174
  userinfo: cmpts.userinfo,
4120
4175
  port: cmpts.port,
@@ -4311,6 +4366,9 @@ var require_fast_uri = __commonJS(function(exports, module) {
4311
4366
  isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4312
4367
  malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4313
4368
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4369
+ if (isIP && ipv6result.isIPV6 === true && parsed.host.indexOf("%") !== -1) {
4370
+ parsed.ipv6Zone = parsed.host.slice(parsed.host.indexOf("%") + 1);
4371
+ }
4314
4372
  if (malformedIPLiteral) {
4315
4373
  parsed.error = parsed.error || "URI host is malformed.";
4316
4374
  malformedAuthorityOrPort = true;
@@ -4335,13 +4393,14 @@ var require_fast_uri = __commonJS(function(exports, module) {
4335
4393
  if (!malformedIPLiteral) {
4336
4394
  malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
4337
4395
  }
4338
- if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4339
- if (uri.indexOf("%") !== -1) {
4340
- if (parsed.host !== undefined && !malformedIPLiteral) {
4341
- const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4342
- parsed.host = reescapeHostDelimiters(host, isIP);
4343
- }
4396
+ if (uri.indexOf("%") !== -1 && parsed.host !== undefined && !malformedIPLiteral) {
4397
+ let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4398
+ if (!isIP) {
4399
+ host = normalizePercentEncoding(host.toLowerCase());
4344
4400
  }
4401
+ parsed.host = reescapeHostDelimiters(host, isIP);
4402
+ }
4403
+ if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4345
4404
  if (parsed.path) {
4346
4405
  parsed.path = normalizePathEncoding(parsed.path);
4347
4406
  }
@@ -12663,11 +12722,14 @@ var require_dist = __commonJS(function(exports, module) {
12663
12722
  });
12664
12723
 
12665
12724
  // src/utils/emoji.ts
12725
+ function withoutSkinTone(emoji) {
12726
+ return emoji.replace(/::skin-tone-\d$/, "");
12727
+ }
12666
12728
  function isApprovalEmoji(emoji) {
12667
- return APPROVAL_EMOJIS.includes(emoji);
12729
+ return APPROVAL_EMOJIS.includes(withoutSkinTone(emoji));
12668
12730
  }
12669
12731
  function isAllowAllEmoji(emoji) {
12670
- return ALLOW_ALL_EMOJIS.includes(emoji);
12732
+ return ALLOW_ALL_EMOJIS.includes(withoutSkinTone(emoji));
12671
12733
  }
12672
12734
  var APPROVAL_EMOJIS, DENIAL_EMOJIS, ALLOW_ALL_EMOJIS;
12673
12735
  var init_emoji = __esm(() => {
@@ -17234,11 +17296,23 @@ var require_websocket = __commonJS(function(exports, module) {
17234
17296
  this._isServer = false;
17235
17297
  this._redirects = 0;
17236
17298
  if (protocols === undefined) {
17237
- protocols = [];
17299
+ if (!options || options.protocols === undefined) {
17300
+ protocols = [];
17301
+ } else if (Array.isArray(options.protocols)) {
17302
+ protocols = options.protocols;
17303
+ } else {
17304
+ protocols = [options.protocols];
17305
+ }
17238
17306
  } else if (!Array.isArray(protocols)) {
17239
17307
  if (typeof protocols === "object" && protocols !== null) {
17240
17308
  options = protocols;
17241
- protocols = [];
17309
+ if (options.protocols === undefined) {
17310
+ protocols = [];
17311
+ } else if (Array.isArray(options.protocols)) {
17312
+ protocols = options.protocols;
17313
+ } else {
17314
+ protocols = [options.protocols];
17315
+ }
17242
17316
  } else {
17243
17317
  protocols = [protocols];
17244
17318
  }
@@ -17357,7 +17431,6 @@ var require_websocket = __commonJS(function(exports, module) {
17357
17431
  }
17358
17432
  return;
17359
17433
  }
17360
- this._readyState = WebSocket.CLOSING;
17361
17434
  this._sender.close(code, data, !this._isServer, (err) => {
17362
17435
  if (err)
17363
17436
  return;
@@ -17366,6 +17439,7 @@ var require_websocket = __commonJS(function(exports, module) {
17366
17439
  this._socket.end();
17367
17440
  }
17368
17441
  });
17442
+ this._readyState = WebSocket.CLOSING;
17369
17443
  setCloseTimer(this);
17370
17444
  }
17371
17445
  pause() {
@@ -17553,6 +17627,7 @@ var require_websocket = __commonJS(function(exports, module) {
17553
17627
  socketPath: undefined,
17554
17628
  hostname: undefined,
17555
17629
  protocol: undefined,
17630
+ protocols: undefined,
17556
17631
  timeout: undefined,
17557
17632
  method: "GET",
17558
17633
  host: undefined,
@@ -40251,16 +40326,49 @@ async function uploadFileSlack(args) {
40251
40326
 
40252
40327
  // src/platform/slack/mcp-platform-api.ts
40253
40328
  var SLACK_API_BASE = "https://slack.com/api";
40254
- async function slackApi(method, token, body) {
40255
- const url = `${SLACK_API_BASE}/${method}`;
40256
- const response = await fetch(url, {
40257
- method: "POST",
40258
- headers: {
40259
- Authorization: `Bearer ${token}`,
40260
- "Content-Type": "application/json; charset=utf-8"
40261
- },
40262
- body: body ? JSON.stringify(body) : undefined
40263
- });
40329
+ var READ_METHODS = new Set([
40330
+ "users.info",
40331
+ "conversations.history",
40332
+ "conversations.replies",
40333
+ "conversations.info",
40334
+ "conversations.members",
40335
+ "reactions.get"
40336
+ ]);
40337
+
40338
+ class SlackRateLimitError extends Error {
40339
+ retryAfterMs;
40340
+ constructor(retryAfterMs) {
40341
+ super(`Slack API error: 429 rate limited, retry after ${retryAfterMs} ms`);
40342
+ this.retryAfterMs = retryAfterMs;
40343
+ }
40344
+ }
40345
+ async function slackApi(method, token, args, apiBase = SLACK_API_BASE) {
40346
+ let response;
40347
+ if (READ_METHODS.has(method)) {
40348
+ const query = new URLSearchParams;
40349
+ for (const [key, value] of Object.entries(args ?? {})) {
40350
+ if (value !== undefined && value !== null)
40351
+ query.set(key, String(value));
40352
+ }
40353
+ const qs = query.toString();
40354
+ response = await fetch(`${apiBase}/${method}${qs ? `?${qs}` : ""}`, {
40355
+ method: "GET",
40356
+ headers: { Authorization: `Bearer ${token}` }
40357
+ });
40358
+ } else {
40359
+ response = await fetch(`${apiBase}/${method}`, {
40360
+ method: "POST",
40361
+ headers: {
40362
+ Authorization: `Bearer ${token}`,
40363
+ "Content-Type": "application/json; charset=utf-8"
40364
+ },
40365
+ body: args ? JSON.stringify(args) : undefined
40366
+ });
40367
+ }
40368
+ if (response.status === 429) {
40369
+ const seconds = Number(response.headers.get("retry-after"));
40370
+ throw new SlackRateLimitError(Number.isFinite(seconds) && seconds > 0 ? seconds * 1000 : 5000);
40371
+ }
40264
40372
  if (!response.ok) {
40265
40373
  throw new Error(`Slack API error: ${response.status} ${response.statusText}`);
40266
40374
  }
@@ -40272,6 +40380,9 @@ async function slackApi(method, token, body) {
40272
40380
  }
40273
40381
 
40274
40382
  class SlackMcpPlatformApi {
40383
+ slackApi(method, token, args) {
40384
+ return slackApi(method, token, args, this.config.apiUrl || SLACK_API_BASE);
40385
+ }
40275
40386
  config;
40276
40387
  formatter = new SlackFormatter;
40277
40388
  botUserIdCache = null;
@@ -40287,7 +40398,7 @@ class SlackMcpPlatformApi {
40287
40398
  return this.botUserIdCache;
40288
40399
  }
40289
40400
  mcpLogger.debug("Fetching bot user ID via auth.test...");
40290
- const response = await slackApi("auth.test", this.config.botToken);
40401
+ const response = await this.slackApi("auth.test", this.config.botToken);
40291
40402
  this.botUserIdCache = response.user_id;
40292
40403
  mcpLogger.debug(`Bot user ID: ${response.user_id}`);
40293
40404
  return response.user_id;
@@ -40295,7 +40406,7 @@ class SlackMcpPlatformApi {
40295
40406
  async getUsername(userId) {
40296
40407
  try {
40297
40408
  mcpLogger.debug(`Looking up username for user ${userId}`);
40298
- const response = await slackApi("users.info", this.config.botToken, { user: userId });
40409
+ const response = await this.slackApi("users.info", this.config.botToken, { user: userId });
40299
40410
  const username = response.user?.name;
40300
40411
  if (username) {
40301
40412
  mcpLogger.debug(`User ${userId} is @${username}`);
@@ -40317,7 +40428,7 @@ class SlackMcpPlatformApi {
40317
40428
  }
40318
40429
  async createInteractivePost(message, reactions, threadTs) {
40319
40430
  mcpLogger.debug(`Creating interactive post with ${reactions.length} reaction options`);
40320
- const response = await slackApi("chat.postMessage", this.config.botToken, {
40431
+ const response = await this.slackApi("chat.postMessage", this.config.botToken, {
40321
40432
  channel: this.config.channelId,
40322
40433
  text: message,
40323
40434
  thread_ts: resolvePostThreadId(threadTs || this.config.threadTs),
@@ -40328,7 +40439,7 @@ class SlackMcpPlatformApi {
40328
40439
  for (const emoji of reactions) {
40329
40440
  try {
40330
40441
  const emojiName = getEmojiName(emoji);
40331
- await slackApi("reactions.add", this.config.botToken, {
40442
+ await this.slackApi("reactions.add", this.config.botToken, {
40332
40443
  channel: this.config.channelId,
40333
40444
  timestamp: messageTs,
40334
40445
  name: emojiName
@@ -40342,120 +40453,54 @@ class SlackMcpPlatformApi {
40342
40453
  }
40343
40454
  async updatePost(postId, message) {
40344
40455
  mcpLogger.debug(`Updating post ${postId}`);
40345
- await slackApi("chat.update", this.config.botToken, {
40456
+ await this.slackApi("chat.update", this.config.botToken, {
40346
40457
  channel: this.config.channelId,
40347
40458
  ts: postId,
40348
40459
  text: message,
40349
40460
  mrkdwn: true
40350
40461
  });
40351
40462
  }
40463
+ reactionPollMs = 2000;
40464
+ reactionSlowAfterMs = 30000;
40465
+ reactionSlowPollMs = 5000;
40466
+ seenTtlMs = 30000;
40467
+ seenReactions = new Map;
40352
40468
  async waitForReaction(postId, botUserId, timeoutMs) {
40353
- return new Promise((resolve) => {
40354
- let resolved = false;
40355
- let ws = null;
40356
- const cleanup = () => {
40357
- if (ws && ws.readyState === WS.OPEN) {
40358
- ws.close();
40359
- }
40360
- };
40361
- const timeout = setTimeout(() => {
40362
- if (!resolved) {
40363
- mcpLogger.debug(`Reaction wait timed out after ${timeoutMs}ms`);
40364
- resolved = true;
40365
- cleanup();
40366
- resolve(null);
40367
- }
40368
- }, timeoutMs);
40369
- this.getSocketModeUrl().then((wsUrl) => {
40370
- if (resolved)
40371
- return;
40372
- mcpLogger.debug(`Connecting to Socket Mode: ${wsUrl.substring(0, 50)}...`);
40373
- ws = new WS(wsUrl);
40374
- ws.onopen = () => {
40375
- mcpLogger.debug("Socket Mode WebSocket connected");
40376
- };
40377
- ws.onmessage = (event) => {
40378
- if (resolved)
40379
- return;
40380
- try {
40381
- const data = typeof event.data === "string" ? event.data : event.data.toString();
40382
- const socketEvent = JSON.parse(data);
40383
- if (socketEvent.envelope_id) {
40384
- ws?.send(JSON.stringify({ envelope_id: socketEvent.envelope_id }));
40385
- }
40386
- mcpLogger.debug(`Socket Mode event type: ${socketEvent.type}`);
40387
- if (socketEvent.type === "events_api" && socketEvent.payload?.event) {
40388
- const slackEvent = socketEvent.payload.event;
40389
- if (slackEvent.type === "reaction_added") {
40390
- const item = slackEvent.item;
40391
- if (item?.type !== "message" || item.ts !== postId || item.channel !== this.config.channelId) {
40392
- return;
40393
- }
40394
- if (slackEvent.user === botUserId) {
40395
- mcpLogger.debug("Ignoring bot's own reaction");
40396
- return;
40397
- }
40398
- const emojiName = slackEvent.reaction || "";
40399
- const userId = slackEvent.user || "";
40400
- mcpLogger.debug(`Reaction received: :${emojiName}: from user: ${userId}`);
40401
- resolved = true;
40402
- clearTimeout(timeout);
40403
- cleanup();
40404
- resolve({
40405
- postId: item.ts,
40406
- userId,
40407
- emojiName
40408
- });
40409
- }
40410
- }
40411
- if (socketEvent.type === "hello") {
40412
- mcpLogger.debug("Socket Mode hello received, connection established");
40413
- }
40414
- if (socketEvent.type === "disconnect") {
40415
- mcpLogger.debug("Socket Mode disconnect requested");
40416
- if (!resolved) {
40417
- resolved = true;
40418
- clearTimeout(timeout);
40419
- cleanup();
40420
- resolve(null);
40421
- }
40422
- }
40423
- } catch (err) {
40424
- mcpLogger.debug(`Error parsing Socket Mode message: ${err}`);
40425
- }
40426
- };
40427
- ws.onerror = (event) => {
40428
- mcpLogger.error(`Socket Mode WebSocket error: ${formatWebSocketError(event)}`);
40429
- if (!resolved) {
40430
- resolved = true;
40431
- clearTimeout(timeout);
40432
- cleanup();
40433
- resolve(null);
40434
- }
40435
- };
40436
- ws.onclose = () => {
40437
- mcpLogger.debug("Socket Mode WebSocket closed");
40438
- if (!resolved) {
40439
- resolved = true;
40440
- clearTimeout(timeout);
40441
- resolve(null);
40469
+ const started = Date.now();
40470
+ const deadline = started + timeoutMs;
40471
+ let warned = false;
40472
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Math.min(ms, deadline - Date.now()))));
40473
+ while (Date.now() < deadline) {
40474
+ let wait = Date.now() - started >= this.reactionSlowAfterMs ? this.reactionSlowPollMs : this.reactionPollMs;
40475
+ try {
40476
+ const response = await this.slackApi("reactions.get", this.config.botToken, { channel: this.config.channelId, timestamp: postId, full: true });
40477
+ for (const reaction of response.message?.reactions ?? []) {
40478
+ for (const userId of reaction.users ?? []) {
40479
+ if (userId === botUserId)
40480
+ continue;
40481
+ const key = `${postId}|${userId}|${reaction.name}`;
40482
+ const seenAt = this.seenReactions.get(key);
40483
+ if (seenAt !== undefined && Date.now() - seenAt < this.seenTtlMs)
40484
+ continue;
40485
+ this.seenReactions.set(key, Date.now());
40486
+ mcpLogger.debug(`Reaction received: :${reaction.name}: from user: ${userId}`);
40487
+ return { postId, userId, emojiName: reaction.name };
40442
40488
  }
40443
- };
40444
- }).catch((err) => {
40445
- mcpLogger.error(`Failed to get Socket Mode URL: ${err}`);
40446
- if (!resolved) {
40447
- resolved = true;
40448
- clearTimeout(timeout);
40449
- resolve(null);
40450
40489
  }
40451
- });
40452
- });
40453
- }
40454
- async getSocketModeUrl() {
40455
- mcpLogger.debug("Getting Socket Mode WebSocket URL...");
40456
- const response = await slackApi("apps.connections.open", this.config.appToken);
40457
- mcpLogger.debug("Got Socket Mode URL");
40458
- return response.url;
40490
+ } catch (err) {
40491
+ if (err instanceof SlackRateLimitError) {
40492
+ wait = err.retryAfterMs;
40493
+ } else if (!warned) {
40494
+ warned = true;
40495
+ mcpLogger.warn(`reactions.get failed, will keep retrying: ${err}`);
40496
+ } else {
40497
+ mcpLogger.debug(`reactions.get failed, retrying: ${err}`);
40498
+ }
40499
+ }
40500
+ await sleep(wait);
40501
+ }
40502
+ mcpLogger.debug(`Reaction wait timed out after ${timeoutMs}ms`);
40503
+ return null;
40459
40504
  }
40460
40505
  async uploadFile(filePath, threadId, options) {
40461
40506
  const filename = sanitizeFilename(options?.filename ?? filePath);
@@ -40466,14 +40511,15 @@ class SlackMcpPlatformApi {
40466
40511
  threadTs: threadId,
40467
40512
  filePath,
40468
40513
  filename,
40469
- caption: options?.caption
40514
+ caption: options?.caption,
40515
+ apiUrl: this.config.apiUrl
40470
40516
  });
40471
40517
  return { postId: result.postId };
40472
40518
  }
40473
40519
  async readPost(postId) {
40474
40520
  mcpLogger.debug(`readPost: ts ${postId}`);
40475
40521
  try {
40476
- const response = await slackApi("conversations.history", this.config.botToken, {
40522
+ const response = await this.slackApi("conversations.history", this.config.botToken, {
40477
40523
  channel: this.config.channelId,
40478
40524
  latest: postId,
40479
40525
  oldest: postId,
@@ -40493,7 +40539,7 @@ class SlackMcpPlatformApi {
40493
40539
  async addReaction(postId, emojiName) {
40494
40540
  const name = getEmojiName(emojiName);
40495
40541
  mcpLogger.debug(`addReaction: :${name}: on ts ${postId}`);
40496
- await slackApi("reactions.add", this.config.botToken, {
40542
+ await this.slackApi("reactions.add", this.config.botToken, {
40497
40543
  channel: this.config.channelId,
40498
40544
  timestamp: postId,
40499
40545
  name
@@ -40502,7 +40548,7 @@ class SlackMcpPlatformApi {
40502
40548
  async readThread(threadRootId, options) {
40503
40549
  mcpLogger.debug(`readThread: ts ${threadRootId}`);
40504
40550
  try {
40505
- const response = await slackApi("conversations.replies", this.config.botToken, {
40551
+ const response = await this.slackApi("conversations.replies", this.config.botToken, {
40506
40552
  channel: this.config.channelId,
40507
40553
  ts: threadRootId,
40508
40554
  limit: options?.limit ?? 100
@@ -40525,7 +40571,7 @@ class SlackMcpPlatformApi {
40525
40571
  const limit = options?.limit ?? 20;
40526
40572
  mcpLogger.debug(`readChannelHistory: ${channelId} (limit=${limit})`);
40527
40573
  try {
40528
- const response = await slackApi("conversations.history", this.config.botToken, {
40574
+ const response = await this.slackApi("conversations.history", this.config.botToken, {
40529
40575
  channel: channelId,
40530
40576
  limit
40531
40577
  });
@@ -40545,7 +40591,7 @@ class SlackMcpPlatformApi {
40545
40591
  async getChannelInfo(channelId) {
40546
40592
  mcpLogger.debug(`getChannelInfo: ${channelId}`);
40547
40593
  try {
40548
- const response = await slackApi("conversations.info", this.config.botToken, { channel: channelId });
40594
+ const response = await this.slackApi("conversations.info", this.config.botToken, { channel: channelId });
40549
40595
  const ch = response.channel;
40550
40596
  const isPrivate = ch.is_private || ch.is_im || ch.is_mpim || false;
40551
40597
  return {
@@ -40570,7 +40616,7 @@ class SlackMcpPlatformApi {
40570
40616
  };
40571
40617
  if (cursor)
40572
40618
  params.cursor = cursor;
40573
- const response = await slackApi("conversations.members", this.config.botToken, params);
40619
+ const response = await this.slackApi("conversations.members", this.config.botToken, params);
40574
40620
  all.push(...response.members ?? []);
40575
40621
  cursor = response.response_metadata?.next_cursor;
40576
40622
  if (!cursor)
@@ -40589,7 +40635,7 @@ class SlackMcpPlatformApi {
40589
40635
  return null;
40590
40636
  }
40591
40637
  try {
40592
- const response = await slackApi("users.info", this.config.botToken, { user: id });
40638
+ const response = await this.slackApi("users.info", this.config.botToken, { user: id });
40593
40639
  return { id: response.user.id, username: response.user.name ?? null };
40594
40640
  } catch (err) {
40595
40641
  mcpLogger.debug(`resolveRecipient ${id} failed: ${err}`);
@@ -40597,9 +40643,9 @@ class SlackMcpPlatformApi {
40597
40643
  }
40598
40644
  }
40599
40645
  async sendDirectMessage(recipientUserId, message) {
40600
- const opened = await slackApi("conversations.open", this.config.botToken, { users: recipientUserId });
40646
+ const opened = await this.slackApi("conversations.open", this.config.botToken, { users: recipientUserId });
40601
40647
  const dmChannelId = opened.channel.id;
40602
- const post = await slackApi("chat.postMessage", this.config.botToken, {
40648
+ const post = await this.slackApi("chat.postMessage", this.config.botToken, {
40603
40649
  channel: dmChannelId,
40604
40650
  text: message,
40605
40651
  mrkdwn: true
@@ -41046,7 +41092,8 @@ var SKIP_STANDARD_PERMISSION_PROMPT = new Set([
41046
41092
  var apiConfig = PLATFORM_TYPE === "slack" ? {
41047
41093
  platformType: "slack",
41048
41094
  botToken: PLATFORM_TOKEN,
41049
- appToken: process.env.PLATFORM_APP_TOKEN || "",
41095
+ appToken: "",
41096
+ apiUrl: process.env.PLATFORM_API_URL || undefined,
41050
41097
  channelId: PLATFORM_CHANNEL_ID,
41051
41098
  threadTs: PLATFORM_THREAD_ID || undefined,
41052
41099
  allowedUsers: ALLOWED_USERS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.39.0",
3
+ "version": "1.39.2",
4
4
  "description": "Run Claude Code from Slack or Mattermost. Sessions stream live into threads where your whole team can watch and steer.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "test:integration:slack:setup": "bun run tests/integration/fixtures/slack/mock-server.ts &",
25
25
  "test:integration:slack:run": "TEST_PLATFORMS=slack INTEGRATION_TEST=1 bun test tests/integration/suites --timeout 120000",
26
26
  "test:integration:slack:teardown": "pkill -f 'mock-server.ts' || true",
27
- "test:integration:slack": "bun run test:integration:slack:teardown; bun run tests/integration/fixtures/slack/mock-server.ts & sleep 2 && TEST_PLATFORMS=slack INTEGRATION_TEST=1 bun test tests/integration/suites --timeout 120000; bun run test:integration:slack:teardown",
27
+ "test:integration:slack": "bun run test:integration:slack:teardown; bun run tests/integration/fixtures/slack/mock-server.ts & sleep 2 && TEST_PLATFORMS=slack INTEGRATION_TEST=1 bun test tests/integration/suites --timeout 120000; status=$?; bun run test:integration:slack:teardown; exit $status",
28
28
  "test:integration:all": "bun run test:integration:setup && bun run tests/integration/fixtures/slack/mock-server.ts & sleep 2 && TEST_PLATFORMS=mattermost,slack INTEGRATION_TEST=1 bun test tests/integration/suites --timeout 120000; bun run test:integration:slack:teardown",
29
29
  "lint": "eslint src/",
30
30
  "lint:fix": "eslint src/ --fix",
@@ -78,7 +78,7 @@
78
78
  "cross-spawn": "^7.0.6",
79
79
  "diff": "^8.0.3",
80
80
  "express-rate-limit": "^8.3.0",
81
- "hono": "4.13.8",
81
+ "hono": "4.13.9",
82
82
  "ink": "^6.6.0",
83
83
  "ink-scroll-view": "^0.3.5",
84
84
  "js-yaml": "^4.3.1",
@@ -86,7 +86,7 @@
86
86
  "react": "^19.2.3",
87
87
  "semver": "7.8.5",
88
88
  "update-notifier": "^7.3.1",
89
- "ws": "8.21.3",
89
+ "ws": "8.22.0",
90
90
  "zod": "^4.3.6"
91
91
  },
92
92
  "devDependencies": {
@@ -102,7 +102,7 @@
102
102
  "@types/ws": "^8.18.0",
103
103
  "eslint": "^10.7.0",
104
104
  "husky": "^9.1.7",
105
- "knip": "6.37.0",
105
+ "knip": "6.38.0",
106
106
  "lint-staged": "^17.0.4",
107
107
  "prettier": "^3.9.5",
108
108
  "react-devtools-core": "^8.0.0",
@@ -126,7 +126,7 @@
126
126
  "flatted": ">=3.4.0",
127
127
  "picomatch": ">=2.3.2",
128
128
  "path-to-regexp": ">=8.4.0",
129
- "fast-uri": ">=4.1.4",
129
+ "fast-uri": ">=4.2.1",
130
130
  "brace-expansion": ">=5.0.9",
131
131
  "ip-address": ">=10.3.1",
132
132
  "ws": "$ws",
@@ -139,7 +139,7 @@
139
139
  "flatted": ">=3.4.0",
140
140
  "picomatch": ">=2.3.2",
141
141
  "path-to-regexp": ">=8.4.0",
142
- "fast-uri": ">=4.1.4",
142
+ "fast-uri": ">=4.2.1",
143
143
  "brace-expansion": ">=5.0.9",
144
144
  "ip-address": ">=10.3.1",
145
145
  "ws": "$ws",