claude-threads 1.39.2 → 1.39.4

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,20 @@ 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.4] - 2026-10-30
9
+
10
+ ### Security
11
+ - `proxy-addr` 2.0.7 → 2.0.8 (critical, IP spoofing), `@modelcontextprotocol/sdk` 1.30.1 → 1.32.2 (three high advisories), `body-parser` 2.3.0 → 2.4.0, `fast-uri` 4.2.1 → 4.2.3, `shell-quote` 1.10.0 → 1.12.0 and `hono` 4.13.9 → 4.13.13, all bundled into what users run (#650). Supersedes the separate Dependabot PRs, none of which could go green on its own.
12
+
13
+ ### Changed
14
+ - `@hono/node-server` 2.1.5, `express-rate-limit` 8.7.1, `@redactpii/node` 1.0.19, `react` 19.3.1, `zod` 4.6.7, plus dev tooling (`eslint`, `knip`, `prettier`, `typescript-eslint`, `@types/*`). `ink` stays on 6.x; the 8.0 major is left for a separate change.
15
+
16
+ ## [1.39.3] - 2026-10-01
17
+
18
+ ### Fixed
19
+ - **Mattermost: a skin-toned 👍 no longer denies** (#629). Mattermost names an emoji picked with a skin tone `+1_medium_skin_tone` (verified against Mattermost v11), and the permission prompt treats any emoji it does not recognize as a denial. 1.39.2 fixed Slack's `+1::skin-tone-3` form; the Mattermost form is now matched on its base name too, for permission prompts, plan approvals and the other reaction controls.
20
+ - **One failed update no longer turns a code block into a post per flush** (#620). When the platform refused an update of the post Claude was writing a code block into, the bot gave up on that post at once. The continuation started in the middle of the block without its opening fence, so the rest rendered as prose, the height check split it every few lines (a new post per flush, close to 200 posts for a 2000-line file), and the closing fence opened a new block. A failed update is now retried on the next flush, which recovers the post completely for a passing error; only after three failures in a row is the post given up, with its code block reopened at the start of the next one.
21
+
8
22
  ## [1.39.2] - 2026-10-01
9
23
 
10
24
  ### Fixed
package/dist/index.js CHANGED
@@ -6033,7 +6033,7 @@ var init_usage = __esm(() => {
6033
6033
 
6034
6034
  // src/utils/emoji.ts
6035
6035
  function withoutSkinTone(emoji) {
6036
- return emoji.replace(/::skin-tone-\d$/, "");
6036
+ return emoji.replace(/::skin-tone-\d$/, "").replace(/(?<=.)_(?:light|medium_light|medium|medium_dark|dark)_skin_tone$/, "");
6037
6037
  }
6038
6038
  function isApprovalEmoji(emoji) {
6039
6039
  return APPROVAL_EMOJIS.includes(withoutSkinTone(emoji));
@@ -19189,7 +19189,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
19189
19189
  exports.useTransition = function() {
19190
19190
  return resolveDispatcher().useTransition();
19191
19191
  };
19192
- exports.version = "19.3.0";
19192
+ exports.version = "19.3.1";
19193
19193
  typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === "function" && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
19194
19194
  })();
19195
19195
  });
@@ -57094,15 +57094,15 @@ class Redactor {
57094
57094
  name: "CREDIT_CARD"
57095
57095
  },
57096
57096
  EMAIL: {
57097
- pattern: /\b[a-z0-9][a-z0-9._-]*@[a-z0-9][\w.-]*\.[a-z]{2,}/gi,
57097
+ pattern: /(?<![\p{L}\p{N}._%+-]|[\p{L}\p{N}]')[\p{L}\p{N}][\p{L}\p{N}._%+'-]*@[\p{L}\p{N}][\p{L}\p{N}_.-]*\.\p{L}{2,}/giu,
57098
57098
  name: "EMAIL"
57099
57099
  },
57100
57100
  NAME: {
57101
- pattern: /(?:^|\.\s+)(?:dear|hi|hello|greetings|hey|hey there)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)/gi,
57101
+ pattern: /(?=[DdHhGg])(?<=(?:^|[.!?\n])\s*)(?:[Dd]ear|[Hh]i|[Hh]ello|[Gg]reetings|[Hh]ey(?:\s+[Tt]here)?)\s+\p{Lu}[\p{L}'’-]*\p{Ll}(?:[ \t]+\p{Lu}[\p{L}'’-]*\p{Ll})+/gu,
57102
57102
  name: "PERSON_NAME"
57103
57103
  },
57104
57104
  PHONE: {
57105
- pattern: /\b(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}\b/g,
57105
+ pattern: /(?<!\w)(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}\b/g,
57106
57106
  name: "PHONE_NUMBER"
57107
57107
  },
57108
57108
  SSN: {
@@ -57116,15 +57116,15 @@ class Redactor {
57116
57116
  name: "CREDIT_CARD"
57117
57117
  },
57118
57118
  EMAIL: {
57119
- pattern: /\b[a-z0-9][a-z0-9._-]*\s*(?:@|\[at\]|\(at\))\s*[a-z0-9][\w.-]*\s*(?:\.|\[dot\]|\(dot\))\s*[a-z]{2,}\b/gi,
57119
+ pattern: /(?<![\p{L}\p{N}._%+-]|[\p{L}\p{N}]')[\p{L}\p{N}][\p{L}\p{N}._%+'-]*\s*(?:@|\[at\]|\(at\))\s*[\p{L}\p{N}][\p{L}\p{N}_.-]*\s*(?:\.|\[dot\]|\(dot\))\s*\p{L}{2,}(?![\p{L}\p{N}_])/giu,
57120
57120
  name: "EMAIL"
57121
57121
  },
57122
57122
  NAME: {
57123
- pattern: /(?:^|\.\s+|,\s*)(?:dear|hi|hello|greetings|hey|hey there|mr|mrs|ms|dr|prof)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/gi,
57123
+ pattern: /(?=[DdHhGgMmPp])(?<=(?:^|[.!?\n,])\s*)(?:[Dd]ear|[Hh]i|[Hh]ello|[Gg]reetings|[Hh]ey(?:\s+[Tt]here)?|[Mm][Rr][Ss]?\.?|[Mm][Ss]\.?|[Dd][Rr]\.?|[Pp]rof\.?)\s+\p{Lu}[\p{L}'’-]*\p{Ll}(?:[ \t]+\p{Lu}[\p{L}'’-]*\p{Ll})*/gu,
57124
57124
  name: "PERSON_NAME"
57125
57125
  },
57126
57126
  PHONE: {
57127
- pattern: /\b(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}\b|\b\d{3}[-.\s]\d{4}\b/g,
57127
+ pattern: /(?<!\w)(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}\b|\b\d{3}[-.\s]\d{4}\b/g,
57128
57128
  name: "PHONE_NUMBER"
57129
57129
  },
57130
57130
  SSN: {
@@ -57145,7 +57145,8 @@ class Redactor {
57145
57145
  return activeRules;
57146
57146
  }
57147
57147
  _clonePattern(pattern) {
57148
- return new RegExp(pattern.source, pattern.flags);
57148
+ const flags = pattern.flags.replace("y", "");
57149
+ return new RegExp(pattern.source, flags.includes("g") ? flags : `${flags}g`);
57149
57150
  }
57150
57151
  hasPII(text) {
57151
57152
  for (const { pattern } of this.activeRules) {
@@ -57185,48 +57186,42 @@ class Redactor {
57185
57186
  }
57186
57187
  _redactString(text, resetState) {
57187
57188
  const events = [];
57188
- let redactedText = text;
57189
57189
  if (resetState && this.anonymize) {
57190
57190
  this.anonymizationMap.clear();
57191
57191
  this.anonymizationCounters.clear();
57192
57192
  }
57193
+ const spans = [];
57193
57194
  for (const { pattern, name } of this.activeRules) {
57194
- const clonedPattern = this._clonePattern(pattern);
57195
- redactedText = redactedText.replace(clonedPattern, (match) => {
57196
- const piiType = name;
57197
- events.push({ pii_type: piiType, action: "REDACTED" });
57198
- if (this.anonymize) {
57199
- const normalizedMatch = match.toLowerCase();
57200
- const key = `${piiType}:${normalizedMatch}`;
57201
- if (this.anonymizationMap.has(key)) {
57202
- return this.anonymizationMap.get(key) ?? match;
57203
- }
57204
- const counter = (this.anonymizationCounters.get(piiType) ?? 0) + 1;
57205
- this.anonymizationCounters.set(piiType, counter);
57206
- const tokenPrefix = this._getTokenPrefix(piiType);
57207
- const token = `${tokenPrefix}_${counter}`;
57208
- this.anonymizationMap.set(key, token);
57209
- return token;
57210
- }
57211
- if (this.globalReplaceWith !== undefined) {
57212
- return this.globalReplaceWith;
57213
- }
57214
- switch (piiType) {
57215
- case "CREDIT_CARD":
57216
- return "CREDIT_CARD_NUMBER";
57217
- case "EMAIL":
57218
- return "EMAIL_ADDRESS";
57219
- case "PERSON_NAME":
57220
- return "PERSON_NAME";
57221
- case "PHONE_NUMBER":
57222
- return "PHONE_NUMBER";
57223
- case "US_SOCIAL_SECURITY_NUMBER":
57224
- return "US_SOCIAL_SECURITY_NUMBER";
57225
- default:
57226
- return "DIGITS";
57195
+ for (const match of text.matchAll(this._clonePattern(pattern))) {
57196
+ if (match[0] === "") {
57197
+ continue;
57227
57198
  }
57228
- });
57199
+ const start = match.index;
57200
+ spans.push({ start, end: start + match[0].length, name, length: match[0].length });
57201
+ }
57202
+ }
57203
+ spans.sort((a, b) => a.start === b.start ? b.end - a.end : a.start - b.start);
57204
+ const merged = [];
57205
+ for (const span of spans) {
57206
+ const last = merged[merged.length - 1];
57207
+ if (last !== undefined && span.start < last.end) {
57208
+ last.end = Math.max(last.end, span.end);
57209
+ if (span.length > last.length) {
57210
+ last.name = span.name;
57211
+ last.length = span.length;
57212
+ }
57213
+ } else {
57214
+ merged.push({ ...span });
57215
+ }
57229
57216
  }
57217
+ let redactedText = "";
57218
+ let cursor = 0;
57219
+ for (const { start, end, name } of merged) {
57220
+ events.push({ pii_type: name, action: "REDACTED" });
57221
+ redactedText += text.slice(cursor, start) + this._replacementFor(name, text.slice(start, end));
57222
+ cursor = end;
57223
+ }
57224
+ redactedText += text.slice(cursor);
57230
57225
  if (resetState) {
57231
57226
  const hasValidApiKey = this.apiKey !== null && this.apiKey !== "";
57232
57227
  if (hasValidApiKey && events.length > 0) {
@@ -57239,6 +57234,38 @@ class Redactor {
57239
57234
  }
57240
57235
  return redactedText;
57241
57236
  }
57237
+ _replacementFor(piiType, match) {
57238
+ if (this.anonymize) {
57239
+ const normalizedMatch = match.toLowerCase();
57240
+ const key = `${piiType}:${normalizedMatch}`;
57241
+ if (this.anonymizationMap.has(key)) {
57242
+ return this.anonymizationMap.get(key) ?? match;
57243
+ }
57244
+ const counter = (this.anonymizationCounters.get(piiType) ?? 0) + 1;
57245
+ this.anonymizationCounters.set(piiType, counter);
57246
+ const tokenPrefix = this._getTokenPrefix(piiType);
57247
+ const token = `${tokenPrefix}_${counter}`;
57248
+ this.anonymizationMap.set(key, token);
57249
+ return token;
57250
+ }
57251
+ if (this.globalReplaceWith !== undefined) {
57252
+ return this.globalReplaceWith;
57253
+ }
57254
+ switch (piiType) {
57255
+ case "CREDIT_CARD":
57256
+ return "CREDIT_CARD_NUMBER";
57257
+ case "EMAIL":
57258
+ return "EMAIL_ADDRESS";
57259
+ case "PERSON_NAME":
57260
+ return "PERSON_NAME";
57261
+ case "PHONE_NUMBER":
57262
+ return "PHONE_NUMBER";
57263
+ case "US_SOCIAL_SECURITY_NUMBER":
57264
+ return "US_SOCIAL_SECURITY_NUMBER";
57265
+ default:
57266
+ return "DIGITS";
57267
+ }
57268
+ }
57242
57269
  redact(text) {
57243
57270
  return this._redactString(text, true);
57244
57271
  }
@@ -60297,7 +60324,8 @@ class ContentExecutor extends BaseExecutor {
60297
60324
  headerPostId: null,
60298
60325
  headerBody: "",
60299
60326
  turnOpen: false,
60300
- paragraphBreak: false
60327
+ paragraphBreak: false,
60328
+ updateFailures: 0
60301
60329
  };
60302
60330
  }
60303
60331
  setHeader(line) {
@@ -60318,6 +60346,7 @@ class ContentExecutor extends BaseExecutor {
60318
60346
  this.state.headerPostId = null;
60319
60347
  this.state.headerBody = "";
60320
60348
  this.state.turnOpen = false;
60349
+ this.state.updateFailures = 0;
60321
60350
  }
60322
60351
  getRenderedCurrentPost() {
60323
60352
  const postId = this.state.currentPostId;
@@ -60344,7 +60373,9 @@ ${body}` : this.state.header;
60344
60373
  this.state.headerDirty = false;
60345
60374
  if (this.state.headerPostId) {
60346
60375
  const postId = this.state.headerPostId;
60376
+ const failures = this.state.updateFailures;
60347
60377
  await this.tryUpdatePost(ctx, postId, this.state.headerBody, "header", { reason: "header_update", headerLength: this.state.header?.length ?? 0 }, { reason: "header_update_failed" }, () => {}, () => {});
60378
+ this.state.updateFailures = failures;
60348
60379
  return;
60349
60380
  }
60350
60381
  if (this.state.header && this.state.turnOpen) {
@@ -60367,6 +60398,7 @@ ${body}` : this.state.header;
60367
60398
  this.state.headerBody = content;
60368
60399
  this.state.headerDirty = false;
60369
60400
  }
60401
+ this.state.updateFailures = 0;
60370
60402
  onSuccess();
60371
60403
  ctx.threadLogger?.logExecutor("content", "update", postId, successDetails, logTag);
60372
60404
  } catch (err) {
@@ -60379,8 +60411,11 @@ ${body}` : this.state.header;
60379
60411
  closeCurrentPost(ctx) {
60380
60412
  const oldPostId = this.state.currentPostId;
60381
60413
  const contentLength = this.state.currentPostContent.length;
60414
+ if (this.state.updateFailures > 0 && this.state.pendingContent.trim())
60415
+ this.giveUpCurrentPost();
60382
60416
  this.state.currentPostId = null;
60383
60417
  this.state.currentPostContent = "";
60418
+ this.state.updateFailures = 0;
60384
60419
  if (ctx?.threadLogger && oldPostId) {
60385
60420
  ctx.threadLogger.logExecutor("content", "close", oldPostId, {
60386
60421
  contentLength,
@@ -60436,13 +60471,22 @@ ${body}` : this.state.header;
60436
60471
  this.state.turnOpen = false;
60437
60472
  return;
60438
60473
  }
60439
- await this.flushPending(ctx);
60440
- if (this.state.headerDirty)
60474
+ const postBefore = this.state.currentPostId;
60475
+ await this.flushPending(ctx, reason);
60476
+ if (reason === "result") {
60477
+ if (this.state.updateFailures > 0 && this.state.currentPostId !== null && this.state.pendingContent.trim()) {
60478
+ await this.flushPending(ctx, reason);
60479
+ }
60480
+ if (postBefore !== null && this.state.currentPostId === null && this.state.pendingContent.trim()) {
60481
+ await this.flushPending(ctx, reason);
60482
+ }
60483
+ }
60484
+ if (this.state.headerDirty && this.state.updateFailures === 0)
60441
60485
  await this.renderHeaderOnly(ctx);
60442
60486
  if (reason === "result")
60443
60487
  this.state.turnOpen = false;
60444
60488
  }
60445
- async flushPending(ctx) {
60489
+ async flushPending(ctx, reason) {
60446
60490
  const pendingAtFlushStart = this.state.pendingContent;
60447
60491
  let content = ctx.formatter.formatMarkdown(pendingAtFlushStart).trim();
60448
60492
  const { maxLength: MAX_POST_LENGTH, hardThreshold: HARD_CONTINUATION_THRESHOLD } = ctx.platform.getMessageLimits();
@@ -60460,7 +60504,7 @@ ${body}` : this.state.header;
60460
60504
  const shouldBreakEarly = this.state.currentPostId && combinedContent.length > MIN_BREAK_THRESHOLD && ctx.contentBreaker.shouldFlushEarly(combinedContent);
60461
60505
  const reserve = this.headerReserve(this.state.currentPostId);
60462
60506
  if (this.state.currentPostId && (combinedContent.length + reserve > HARD_CONTINUATION_THRESHOLD || shouldBreakEarly)) {
60463
- await this.handleSplit(ctx, combinedContent, pendingAtFlushStart, HARD_CONTINUATION_THRESHOLD);
60507
+ await this.handleSplit(ctx, combinedContent, pendingAtFlushStart, HARD_CONTINUATION_THRESHOLD, reason);
60464
60508
  return;
60465
60509
  }
60466
60510
  if (this.state.currentPostId && content.length + reserve > MAX_POST_LENGTH) {
@@ -60495,10 +60539,7 @@ ${body}` : this.state.header;
60495
60539
  await this.tryUpdatePost(ctx, postId, combinedContent, "flush", { newContentLength: content.length, combinedLength: combinedContent.length }, (err) => ({ failedOp: "updatePost", error: String(err) }), () => {
60496
60540
  this.state.currentPostContent = combinedContent;
60497
60541
  this.clearFlushedContent(pendingAtFlushStart);
60498
- }, () => {
60499
- this.state.currentPostId = null;
60500
- this.state.currentPostContent = "";
60501
- });
60542
+ }, () => this.onInPlaceUpdateFailed(reason));
60502
60543
  } else {
60503
60544
  const chunks = splitContentForHeight(content, ctx.contentBreaker).flatMap((chunk) => chunk.length + reserve > MAX_POST_LENGTH ? splitByLength(chunk, HARD_CONTINUATION_THRESHOLD - reserve) : [chunk]);
60504
60545
  ctx.threadLogger?.logExecutor("content", "create_start", "none", {
@@ -60509,7 +60550,7 @@ ${body}` : this.state.header;
60509
60550
  await this.postChunks(ctx, chunks, pendingAtFlushStart, false);
60510
60551
  }
60511
60552
  }
60512
- async splitOversized(ctx, content, pendingAtFlushStart, hardThreshold) {
60553
+ async splitOversized(ctx, content, pendingAtFlushStart, hardThreshold, reason) {
60513
60554
  const postId = this.state.currentPostId;
60514
60555
  if (!postId)
60515
60556
  return false;
@@ -60520,17 +60561,35 @@ ${body}` : this.state.header;
60520
60561
  let landed = false;
60521
60562
  await this.tryUpdatePost(ctx, postId, firstPiece, "handleSplit", { reason: "oversized_code_block", contentLength: content.length, pieces: rest.length + 1 }, { reason: "oversized_code_block_failed" }, () => {
60522
60563
  landed = true;
60523
- }, () => {
60524
- if (postId === this.state.headerPostId)
60525
- this.state.headerBody = firstPiece;
60526
- });
60564
+ }, () => {});
60565
+ if (!landed) {
60566
+ this.onInPlaceUpdateFailed(reason);
60567
+ return true;
60568
+ }
60527
60569
  this.state.currentPostId = null;
60528
60570
  this.state.currentPostContent = "";
60529
- if (landed)
60530
- this.clearFlushedContent(pendingAtFlushStart);
60531
- await this.postChunks(ctx, rest, pendingAtFlushStart, landed);
60571
+ this.clearFlushedContent(pendingAtFlushStart);
60572
+ await this.postChunks(ctx, rest, pendingAtFlushStart, true);
60532
60573
  return true;
60533
60574
  }
60575
+ onInPlaceUpdateFailed(reason) {
60576
+ this.state.updateFailures++;
60577
+ const limit = reason === "result" ? 2 : MAX_IN_PLACE_UPDATE_FAILURES;
60578
+ if (this.state.updateFailures < limit)
60579
+ return;
60580
+ this.giveUpCurrentPost();
60581
+ }
60582
+ giveUpCurrentPost() {
60583
+ const opener = openFenceOf(this.state.currentPostContent);
60584
+ this.state.currentPostId = null;
60585
+ this.state.currentPostContent = "";
60586
+ this.state.updateFailures = 0;
60587
+ if (!opener)
60588
+ return;
60589
+ const rest = this.state.pendingContent.replace(/^\n+/, "");
60590
+ this.state.pendingContent = /^```[ \t]*(\n|$)/.test(rest) ? rest.replace(/^```[ \t]*\n?/, "") : `${opener}
60591
+ ${rest}`;
60592
+ }
60534
60593
  async postChunks(ctx, chunks, pendingAtFlushStart, flushCleared) {
60535
60594
  let cleared = flushCleared;
60536
60595
  for (let i = 0;i < chunks.length; i++) {
@@ -60554,7 +60613,7 @@ ${this.state.pendingContent}` : "");
60554
60613
  }
60555
60614
  }
60556
60615
  }
60557
- async handleSplit(ctx, content, pendingAtFlushStart, hardThreshold) {
60616
+ async handleSplit(ctx, content, pendingAtFlushStart, hardThreshold, reason) {
60558
60617
  let breakPoint;
60559
60618
  let codeBlockOpenPosition;
60560
60619
  if (content.length > hardThreshold) {
@@ -60602,26 +60661,21 @@ ${this.state.pendingContent}` : "");
60602
60661
  await this.tryUpdatePost(ctx, postId, content, "handleSplit", { reason: "soft_break_no_breakpoint", contentLength: content.length }, { reason: "soft_break_no_breakpoint_failed" }, () => {
60603
60662
  this.state.currentPostContent = content;
60604
60663
  this.clearFlushedContent(pendingAtFlushStart);
60605
- }, () => {
60606
- this.state.currentPostId = null;
60607
- });
60664
+ }, () => this.onInPlaceUpdateFailed(reason));
60608
60665
  }
60609
60666
  return;
60610
60667
  }
60611
60668
  }
60612
60669
  if (codeBlockOpenPosition !== undefined) {
60613
60670
  if (codeBlockOpenPosition === 0) {
60614
- if (await this.splitOversized(ctx, content, pendingAtFlushStart, hardThreshold))
60671
+ if (await this.splitOversized(ctx, content, pendingAtFlushStart, hardThreshold, reason))
60615
60672
  return;
60616
60673
  if (this.state.currentPostId) {
60617
60674
  const postId = this.state.currentPostId;
60618
60675
  await this.tryUpdatePost(ctx, postId, content, "handleSplit", { reason: "code_block_at_start", contentLength: content.length }, { reason: "code_block_at_start_failed" }, () => {
60619
60676
  this.state.currentPostContent = content;
60620
60677
  this.clearFlushedContent(pendingAtFlushStart);
60621
- }, () => {
60622
- this.state.currentPostId = null;
60623
- this.state.currentPostContent = "";
60624
- });
60678
+ }, () => this.onInPlaceUpdateFailed(reason));
60625
60679
  }
60626
60680
  return;
60627
60681
  }
@@ -60630,17 +60684,14 @@ ${this.state.pendingContent}` : "");
60630
60684
  if (breakBeforeCodeBlock > 0) {
60631
60685
  breakPoint = breakBeforeCodeBlock;
60632
60686
  } else {
60633
- if (await this.splitOversized(ctx, content, pendingAtFlushStart, hardThreshold))
60687
+ if (await this.splitOversized(ctx, content, pendingAtFlushStart, hardThreshold, reason))
60634
60688
  return;
60635
60689
  if (this.state.currentPostId) {
60636
60690
  const postId = this.state.currentPostId;
60637
60691
  await this.tryUpdatePost(ctx, postId, content, "handleSplit", { reason: "no_break_before_code_block", contentLength: content.length }, { reason: "no_break_before_code_block_failed" }, () => {
60638
60692
  this.state.currentPostContent = content;
60639
60693
  this.clearFlushedContent(pendingAtFlushStart);
60640
- }, () => {
60641
- this.state.currentPostId = null;
60642
- this.state.currentPostContent = "";
60643
- });
60694
+ }, () => this.onInPlaceUpdateFailed(reason));
60644
60695
  }
60645
60696
  return;
60646
60697
  }
@@ -60652,11 +60703,11 @@ ${this.state.pendingContent}` : "");
60652
60703
  const postId = this.state.currentPostId;
60653
60704
  await this.tryUpdatePost(ctx, postId, firstPart, "handleSplit", { reason: "split_first_part", firstPartLength: firstPart.length, remainderLength: remainder.length }, { reason: "split_first_part_failed" }, () => {
60654
60705
  firstPartLanded = true;
60655
- }, () => {
60656
- if (postId === this.state.headerPostId) {
60657
- this.state.headerBody = firstPart;
60658
- }
60659
- });
60706
+ }, () => {});
60707
+ if (!firstPartLanded) {
60708
+ this.onInPlaceUpdateFailed(reason);
60709
+ return;
60710
+ }
60660
60711
  }
60661
60712
  this.state.currentPostId = null;
60662
60713
  this.state.currentPostContent = "";
@@ -60689,6 +60740,7 @@ ${content}` : header : content;
60689
60740
  if (bumpedPostId) {
60690
60741
  adoptAsHeaderPost(bumpedPostId);
60691
60742
  this.state.currentPostId = bumpedPostId;
60743
+ this.state.updateFailures = 0;
60692
60744
  this.state.currentPostContent = content;
60693
60745
  this.clearFlushedContent(pendingAtFlushStart);
60694
60746
  ctx.threadLogger?.logExecutor("content", "create", bumpedPostId, {
@@ -60705,6 +60757,7 @@ ${content}` : header : content;
60705
60757
  const post = await ctx.createPost(rendered, { type: "content" });
60706
60758
  adoptAsHeaderPost(post.id);
60707
60759
  this.state.currentPostId = post.id;
60760
+ this.state.updateFailures = 0;
60708
60761
  this.state.currentPostContent = content;
60709
60762
  this.clearFlushedContent(pendingAtFlushStart);
60710
60763
  ctx.logger.debug(`Created post ${formatShortId(post.id)}`);
@@ -60730,6 +60783,11 @@ ${content}` : header : content;
60730
60783
  }
60731
60784
  }
60732
60785
  var FENCE_LINE = /^```.*$/gm;
60786
+ var MAX_IN_PLACE_UPDATE_FAILURES = 3;
60787
+ function openFenceOf(text) {
60788
+ const fences = text.match(FENCE_LINE) ?? [];
60789
+ return fences.length % 2 === 1 ? fences[fences.length - 1] : null;
60790
+ }
60733
60791
  var FENCE_CLOSE = "\n```";
60734
60792
  function splitByLength(text, max) {
60735
60793
  const room = max - FENCE_CLOSE.length;
@@ -71905,7 +71963,6 @@ class SlackClient extends BasePlatformClient {
71905
71963
  token: this.botToken,
71906
71964
  channelId: this.channelId,
71907
71965
  allowedUsers: this.allowedUsers,
71908
- appToken: this.appToken,
71909
71966
  apiUrl: this.apiUrl,
71910
71967
  outboundFiles: this.outboundFiles,
71911
71968
  mcpServers: this.mcpServers,