claude-threads 1.6.0 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ 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.6.3] - 2026-04-21
9
+
10
+ ### Fixed
11
+ - **Skipped file attachments are now surfaced on session start** - When a user starts a session with an unsupported file (e.g. `.xlsx`), the bot posts the `⚠️ Some files could not be processed` warning instead of silently dropping it. The same warning now fires on the mid-thread context-prompt and worktree paths. (#325, thanks @shaders)
12
+ - **Recognize `.har` and `.log` as text** - Both extensions come through as `application/octet-stream` via Mattermost/Slack and were previously dropped as unsupported. (#325)
13
+
14
+ ## [1.6.2] - 2026-04-20
15
+
16
+ ### Fixed
17
+ - **MCP server path resolution in bundled builds** - Fixes `__dirname` resolution when the project is bundled with `bun build` into a single `dist/index.js` (#316)
18
+ - **CLAUDE_THREADS_INTERACTIVE forwarding to daemon subprocess** - Parent no longer falsely advertises a TTY to the piped-stdio child in daemon mode (#312, #317)
19
+ - **Sticky message test regex** - Updated to match the actual header format (#319)
20
+ - **Flaky permissions integration test** - Uses pattern-based waits instead of fixed post counts to tolerate intermittent CI 500s (#320)
21
+
22
+ ### Security
23
+ - **Override path-to-regexp to >=8.4.0** - Fixes CVE-2026-4926 (HIGH severity DoS) pulled in transitively via @modelcontextprotocol/sdk → express → router (#318)
24
+ - **Bump hono to 4.12.14** - Fixes GHSA-458j-xx4x-4375 (improper JSX attribute name handling in hono/jsx SSR) (#324)
25
+
26
+ ### Dependencies
27
+ - **Bump production dependencies** - @hono/node-server, hono, @redactpii/node, react (#311, #326)
28
+ - **Bump dev dependencies** - @types/yazl, @types/bun, @types/node, prettier, typescript-eslint (#310, #322)
29
+ - **Bump CI actions** - actions/upload-pages-artifact 4 to 5 (#321)
30
+
31
+ ## [1.6.1] - 2026-04-07
32
+
33
+ ### Security
34
+ - **Bump path-to-regexp to 8.4.0** - Fixes CVE-2026-4926 (DoS via malicious route patterns) (#303)
35
+
36
+ ### Dependencies
37
+ - **Bump production dependencies** - @hono/node-server, @modelcontextprotocol/sdk, express-rate-limit, yauzl (#308)
38
+ - **Bump dev dependencies** - TypeScript 5.9 to 6.0, @types/bun, @types/node, @types/react, lint-staged, typescript-eslint (#309)
39
+ - **Bump CI actions** - actions/configure-pages 5 to 6, actions/deploy-pages 4 to 5, schneegans/dynamic-badges-action 1.7 to 1.8 (#304, #305, #306)
40
+
8
41
  ## [1.6.0] - 2026-03-27
9
42
 
10
43
  ### Added
package/dist/index.js CHANGED
@@ -54372,11 +54372,19 @@ class ClaudeCli extends EventEmitter2 {
54372
54372
  getMcpServerPath() {
54373
54373
  const __filename2 = fileURLToPath3(import.meta.url);
54374
54374
  const __dirname4 = dirname6(__filename2);
54375
+ const bundledPath = resolve3(__dirname4, "mcp", "permission-server.js");
54376
+ if (existsSync8(bundledPath)) {
54377
+ return bundledPath;
54378
+ }
54375
54379
  return resolve3(__dirname4, "..", "mcp", "permission-server.js");
54376
54380
  }
54377
54381
  getStatusLineWriterPath() {
54378
54382
  const __filename2 = fileURLToPath3(import.meta.url);
54379
54383
  const __dirname4 = dirname6(__filename2);
54384
+ const bundledPath = resolve3(__dirname4, "statusline", "writer.js");
54385
+ if (existsSync8(bundledPath)) {
54386
+ return bundledPath;
54387
+ }
54380
54388
  return resolve3(__dirname4, "..", "statusline", "writer.js");
54381
54389
  }
54382
54390
  }
@@ -59412,6 +59420,8 @@ var TEXT_FILE_EXTENSIONS = [
59412
59420
  ".xml",
59413
59421
  ".yaml",
59414
59422
  ".yml",
59423
+ ".har",
59424
+ ".log",
59415
59425
  ".js",
59416
59426
  ".ts",
59417
59427
  ".jsx",
@@ -59982,7 +59992,7 @@ function getUnsupportedFileSuggestion(file) {
59982
59992
  async function buildMessageContent(text, platform, files, debug = false) {
59983
59993
  const result = await processFiles(platform, files, debug);
59984
59994
  if (result.blocks.length === 0) {
59985
- return text;
59995
+ return { content: text, skipped: result.skipped };
59986
59996
  }
59987
59997
  if (text) {
59988
59998
  result.blocks.push({
@@ -59990,7 +60000,12 @@ async function buildMessageContent(text, platform, files, debug = false) {
59990
60000
  text
59991
60001
  });
59992
60002
  }
59993
- return result.blocks;
60003
+ return { content: result.blocks, skipped: result.skipped };
60004
+ }
60005
+ async function postSkippedFilesFeedback(platform, threadId, skipped) {
60006
+ if (skipped.length === 0)
60007
+ return;
60008
+ await platform.createPost(formatSkippedFilesFeedback(skipped), threadId);
59994
60009
  }
59995
60010
  async function processFiles(platform, files, debug = false) {
59996
60011
  const blocks = [];
@@ -60044,6 +60059,18 @@ async function processFiles(platform, files, debug = false) {
60044
60059
  }
60045
60060
  return { blocks, skipped };
60046
60061
  }
60062
+ function formatSkippedFilesFeedback(skippedFiles) {
60063
+ const lines = ["⚠️ **Some files could not be processed:**"];
60064
+ for (const file of skippedFiles) {
60065
+ let line = `- **${file.name}**: ${file.reason}`;
60066
+ if (file.suggestion) {
60067
+ line += ` _(${file.suggestion})_`;
60068
+ }
60069
+ lines.push(line);
60070
+ }
60071
+ return lines.join(`
60072
+ `);
60073
+ }
60047
60074
  function startTyping(session) {
60048
60075
  if (session.timers.typingTimer)
60049
60076
  return;
@@ -60490,20 +60517,15 @@ class MessageManager {
60490
60517
  }
60491
60518
  this.session.threadLogger?.logUserMessage(username || this.session.startedBy, message, displayName, files && files.length > 0);
60492
60519
  await this.prepareForUserMessage();
60493
- let skippedFiles = [];
60494
- if (files && files.length > 0) {
60495
- const fileResult = await processFiles(this.platform, files);
60496
- skippedFiles = fileResult.skipped;
60497
- }
60498
60520
  let content = message;
60521
+ let skippedFiles = [];
60499
60522
  if (this.buildMessageContentCallback) {
60500
- content = await this.buildMessageContentCallback(message, this.platform, files);
60523
+ const built = await this.buildMessageContentCallback(message, this.platform, files);
60524
+ content = built.content;
60525
+ skippedFiles = built.skipped;
60501
60526
  }
60502
60527
  this.session.claude.sendMessage(content);
60503
- if (skippedFiles.length > 0) {
60504
- const feedback = this.formatSkippedFilesFeedback(skippedFiles);
60505
- await this.platform.createPost(feedback, this.threadId);
60506
- }
60528
+ await postSkippedFilesFeedback(this.platform, this.threadId, skippedFiles);
60507
60529
  this.session.lastActivityAt = new Date;
60508
60530
  this.session.isProcessing = true;
60509
60531
  this.emitSessionUpdateCallback?.({ status: "active", isTyping: true });
@@ -60511,18 +60533,6 @@ class MessageManager {
60511
60533
  logger.debug("User message sent to Claude");
60512
60534
  return true;
60513
60535
  }
60514
- formatSkippedFilesFeedback(skippedFiles) {
60515
- const lines = ["⚠️ **Some files could not be processed:**"];
60516
- for (const file of skippedFiles) {
60517
- let line = `- **${file.name}**: ${file.reason}`;
60518
- if (file.suggestion) {
60519
- line += ` _(${file.suggestion})_`;
60520
- }
60521
- lines.push(line);
60522
- }
60523
- return lines.join(`
60524
- `);
60525
- }
60526
60536
  getSession() {
60527
60537
  return this.session;
60528
60538
  }
@@ -66286,9 +66296,10 @@ ${fmt.formatItalic("Claude Code restarted in the new worktree")}`);
66286
66296
  const contextPrefix = options2.formatContextForClaude(threadMessages, workSummary);
66287
66297
  const messageToSend = contextPrefix + session.firstPrompt;
66288
66298
  session.messageCount++;
66289
- const content = await options2.buildMessageContent(messageToSend, session, undefined);
66299
+ const { content, skipped } = await options2.buildMessageContent(messageToSend, session, undefined);
66290
66300
  session.claude.sendMessage(content);
66291
66301
  options2.startTyping(session);
66302
+ await postSkippedFilesFeedback(session.platform, session.threadId, skipped);
66292
66303
  sessionLog3(session).debug(`\uD83C\uDF3F Auto-included ${threadMessages.length} messages + work summary for mid-session worktree`);
66293
66304
  }
66294
66305
  session.worktreeResponsePostId = undefined;
@@ -66891,11 +66902,12 @@ async function handleContextPromptTimeout(session, ctx) {
66891
66902
  }
66892
66903
  session.messageCount++;
66893
66904
  const messageToSend = ctx.injectMetadataReminder(queuedPrompt, session);
66894
- const content = await ctx.buildMessageContent(messageToSend, session, queuedFiles);
66905
+ const { content, skipped } = await ctx.buildMessageContent(messageToSend, session, queuedFiles);
66895
66906
  if (session.claude.isRunning()) {
66896
66907
  session.claude.sendMessage(content);
66897
66908
  ctx.startTyping(session);
66898
66909
  }
66910
+ await postSkippedFilesFeedback(session.platform, session.threadId, skipped);
66899
66911
  ctx.persistSession(session);
66900
66912
  sessionLog5(session).debug(`\uD83E\uDDF5 Context prompt timed out, continuing without thread context`);
66901
66913
  }
@@ -66912,11 +66924,12 @@ async function offerContextPrompt(session, queuedPrompt, queuedFiles, ctx, exclu
66912
66924
  sessionLog5(session).debug(`\uD83E\uDDF5 Including work summary (no thread messages)`);
66913
66925
  }
66914
66926
  messageToSend = ctx.injectMetadataReminder(messageToSend, session);
66915
- const content = await ctx.buildMessageContent(messageToSend, session, queuedFiles);
66927
+ const { content, skipped } = await ctx.buildMessageContent(messageToSend, session, queuedFiles);
66916
66928
  if (session.claude.isRunning()) {
66917
66929
  session.claude.sendMessage(content);
66918
66930
  ctx.startTyping(session);
66919
66931
  }
66932
+ await postSkippedFilesFeedback(session.platform, session.threadId, skipped);
66920
66933
  return false;
66921
66934
  }
66922
66935
  if (messageCount === 1) {
@@ -66930,11 +66943,12 @@ async function offerContextPrompt(session, queuedPrompt, queuedFiles, ctx, exclu
66930
66943
  }
66931
66944
  session.messageCount++;
66932
66945
  messageToSend = ctx.injectMetadataReminder(messageToSend, session);
66933
- const content = await ctx.buildMessageContent(messageToSend, session, queuedFiles);
66946
+ const { content, skipped } = await ctx.buildMessageContent(messageToSend, session, queuedFiles);
66934
66947
  if (session.claude.isRunning()) {
66935
66948
  session.claude.sendMessage(content);
66936
66949
  ctx.startTyping(session);
66937
66950
  }
66951
+ await postSkippedFilesFeedback(session.platform, session.threadId, skipped);
66938
66952
  sessionLog5(session).debug(`\uD83E\uDDF5 Auto-included 1 message as context (thread starter)${previousWorkSummary ? " + work summary" : ""}`);
66939
66953
  return false;
66940
66954
  }
@@ -67111,7 +67125,7 @@ function createMessageManager(session, ctx) {
67111
67125
  }
67112
67126
  session.messageCount++;
67113
67127
  messageToSend = maybeInjectMetadataReminder(messageToSend, session, ctx, session);
67114
- const content = await ctx.ops.buildMessageContent(messageToSend, session.platform, undefined);
67128
+ const { content } = await ctx.ops.buildMessageContent(messageToSend, session.platform, undefined);
67115
67129
  if (session.claude.isRunning()) {
67116
67130
  session.claude.sendMessage(content);
67117
67131
  ctx.ops.startTyping(session);
@@ -67417,17 +67431,19 @@ ${CHAT_PLATFORM_PROMPT}`;
67417
67431
  await ctx.ops.updateStickyMessage();
67418
67432
  return;
67419
67433
  }
67420
- const content = await ctx.ops.buildMessageContent(options2.prompt, session.platform, options2.files);
67434
+ const { content, skipped } = await ctx.ops.buildMessageContent(options2.prompt, session.platform, options2.files);
67421
67435
  const messageText = typeof content === "string" ? content : options2.prompt;
67422
67436
  if (replyToPostId) {
67423
67437
  const excludePostId = triggeringPostId || replyToPostId;
67424
67438
  const contextOffered = await ctx.ops.offerContextPrompt(session, messageText, options2.files, excludePostId);
67425
67439
  if (contextOffered) {
67440
+ await postSkippedFilesFeedback(session.platform, actualThreadId, skipped);
67426
67441
  return;
67427
67442
  }
67428
67443
  }
67429
67444
  session.messageCount++;
67430
67445
  claude.sendMessage(content);
67446
+ await postSkippedFilesFeedback(session.platform, actualThreadId, skipped);
67431
67447
  }
67432
67448
  async function resumeSession(state, ctx) {
67433
67449
  if (!state.threadId || !state.platformId || !state.claudeSessionId || !state.workingDir) {
@@ -67612,9 +67628,7 @@ async function sendFollowUp(session, message, files, ctx, username, displayName)
67612
67628
  if (session.needsContextPromptOnNextMessage) {
67613
67629
  session.needsContextPromptOnNextMessage = false;
67614
67630
  await session.messageManager?.prepareForUserMessage();
67615
- const content = await ctx.ops.buildMessageContent(message, session.platform, files);
67616
- const messageText = typeof content === "string" ? content : message;
67617
- const contextOffered = await ctx.ops.offerContextPrompt(session, messageText, files);
67631
+ const contextOffered = await ctx.ops.offerContextPrompt(session, message, files);
67618
67632
  if (contextOffered) {
67619
67633
  session.lastActivityAt = new Date;
67620
67634
  return;
@@ -79624,7 +79638,7 @@ async function main() {
79624
79638
  env: {
79625
79639
  ...process.env,
79626
79640
  CLAUDE_THREADS_BIN: binPath,
79627
- ...process.stdout.isTTY ? { CLAUDE_THREADS_INTERACTIVE: "1" } : {}
79641
+ CLAUDE_THREADS_INTERACTIVE: ""
79628
79642
  }
79629
79643
  });
79630
79644
  } else {
@@ -79633,7 +79647,7 @@ async function main() {
79633
79647
  env: {
79634
79648
  ...process.env,
79635
79649
  CLAUDE_THREADS_BIN: binPath,
79636
- ...process.stdout.isTTY ? { CLAUDE_THREADS_INTERACTIVE: "1" } : {}
79650
+ CLAUDE_THREADS_INTERACTIVE: ""
79637
79651
  }
79638
79652
  });
79639
79653
  }
@@ -15849,7 +15849,7 @@ var require_validation3 = __commonJS((exports, module) => {
15849
15849
 
15850
15850
  // node_modules/ws/lib/receiver.js
15851
15851
  var require_receiver = __commonJS((exports, module) => {
15852
- var { Writable: Writable2 } = __require("stream");
15852
+ var { Writable } = __require("stream");
15853
15853
  var PerMessageDeflate = require_permessage_deflate();
15854
15854
  var {
15855
15855
  BINARY_TYPES,
@@ -15868,7 +15868,7 @@ var require_receiver = __commonJS((exports, module) => {
15868
15868
  var INFLATING = 5;
15869
15869
  var DEFER_EVENT = 6;
15870
15870
 
15871
- class Receiver extends Writable2 {
15871
+ class Receiver extends Writable {
15872
15872
  constructor(options2 = {}) {
15873
15873
  super();
15874
15874
  this._allowSynchronousEvents = options2.allowSynchronousEvents !== undefined ? options2.allowSynchronousEvents : true;
@@ -16906,7 +16906,7 @@ var require_websocket = __commonJS((exports, module) => {
16906
16906
  var net = __require("net");
16907
16907
  var tls = __require("tls");
16908
16908
  var { randomBytes, createHash } = __require("crypto");
16909
- var { Duplex, Readable: Readable2 } = __require("stream");
16909
+ var { Duplex, Readable } = __require("stream");
16910
16910
  var { URL: URL2 } = __require("url");
16911
16911
  var PerMessageDeflate = require_permessage_deflate();
16912
16912
  var Receiver = require_receiver();
@@ -52407,661 +52407,28 @@ function createMessageManagerEvents() {
52407
52407
 
52408
52408
  // src/operations/streaming/handler.ts
52409
52409
  var import_yauzl = __toESM(require_yauzl(), 1);
52410
- import { createGunzip } from "zlib";
52411
- import { pipeline as pipeline2 } from "stream/promises";
52412
- import { Readable, Writable } from "stream";
52413
52410
  var log2 = createLogger("streaming");
52414
52411
  var MAX_PDF_SIZE = 32 * 1024 * 1024;
52415
52412
  var MAX_TEXT_FILE_SIZE = 1 * 1024 * 1024;
52416
52413
  var MAX_DECOMPRESSED_SIZE = 10 * 1024 * 1024;
52417
52414
  var MAX_ZIP_SIZE = 50 * 1024 * 1024;
52418
52415
  var MAX_GZIP_SIZE = 50 * 1024 * 1024;
52419
- var MAX_ZIP_FILES = 20;
52420
- var SUPPORTED_IMAGE_TYPES = [
52421
- "image/jpeg",
52422
- "image/png",
52423
- "image/gif",
52424
- "image/webp"
52425
- ];
52426
- var SUPPORTED_TEXT_TYPES = [
52427
- "text/plain",
52428
- "text/markdown",
52429
- "text/csv",
52430
- "text/xml",
52431
- "text/yaml",
52432
- "text/x-yaml",
52433
- "application/json",
52434
- "application/xml",
52435
- "application/x-yaml",
52436
- "application/yaml"
52437
- ];
52438
- var TEXT_FILE_EXTENSIONS = [
52439
- ".txt",
52440
- ".md",
52441
- ".markdown",
52442
- ".json",
52443
- ".csv",
52444
- ".xml",
52445
- ".yaml",
52446
- ".yml",
52447
- ".js",
52448
- ".ts",
52449
- ".jsx",
52450
- ".tsx",
52451
- ".py",
52452
- ".rb",
52453
- ".go",
52454
- ".rs",
52455
- ".java",
52456
- ".c",
52457
- ".cpp",
52458
- ".h",
52459
- ".hpp",
52460
- ".cs",
52461
- ".php",
52462
- ".swift",
52463
- ".kt",
52464
- ".scala",
52465
- ".sh",
52466
- ".bash",
52467
- ".zsh",
52468
- ".fish",
52469
- ".ps1",
52470
- ".bat",
52471
- ".cmd",
52472
- ".html",
52473
- ".htm",
52474
- ".css",
52475
- ".scss",
52476
- ".sass",
52477
- ".less",
52478
- ".sql",
52479
- ".graphql",
52480
- ".gql",
52481
- ".toml",
52482
- ".ini",
52483
- ".cfg",
52484
- ".conf",
52485
- ".env",
52486
- ".properties",
52487
- ".dockerfile",
52488
- ".gitignore",
52489
- ".gitattributes",
52490
- ".editorconfig"
52491
- ];
52492
- function isImageFile(file2) {
52493
- return file2.mimeType.startsWith("image/") && SUPPORTED_IMAGE_TYPES.includes(file2.mimeType);
52494
- }
52495
- function isPdfFile(file2) {
52496
- return file2.mimeType === "application/pdf" || file2.name.toLowerCase().endsWith(".pdf");
52497
- }
52498
- function isTextFile(file2) {
52499
- if (SUPPORTED_TEXT_TYPES.includes(file2.mimeType)) {
52500
- return true;
52501
- }
52502
- const lowerName = file2.name.toLowerCase();
52503
- return TEXT_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext));
52504
- }
52505
- function isGzipFile(file2) {
52506
- return file2.mimeType === "application/gzip" || file2.mimeType === "application/x-gzip" || file2.name.toLowerCase().endsWith(".gz");
52507
- }
52508
- function isZipFile(file2) {
52509
- return file2.mimeType === "application/zip" || file2.mimeType === "application/x-zip-compressed" || file2.name.toLowerCase().endsWith(".zip");
52510
- }
52511
- function categorizeFile(file2) {
52512
- if (isImageFile(file2))
52513
- return "image";
52514
- if (isPdfFile(file2))
52515
- return "pdf";
52516
- if (isZipFile(file2))
52517
- return "zip";
52518
- if (isGzipFile(file2))
52519
- return "gzip";
52520
- if (isTextFile(file2))
52521
- return "text";
52522
- return "unsupported";
52523
- }
52524
- async function processImageFile(file2, platform, debug = false) {
52525
- try {
52526
- if (!platform.downloadFile) {
52527
- return {
52528
- skipped: {
52529
- name: file2.name,
52530
- reason: "Platform does not support file downloads"
52531
- }
52532
- };
52533
- }
52534
- const buffer = await platform.downloadFile(file2.id);
52535
- const base644 = buffer.toString("base64");
52536
- if (debug) {
52537
- log2.debug(`Attached image: ${file2.name} (${file2.mimeType}, ${Math.round(buffer.length / 1024)}KB)`);
52538
- }
52539
- return {
52540
- block: {
52541
- type: "image",
52542
- source: {
52543
- type: "base64",
52544
- media_type: file2.mimeType,
52545
- data: base644
52546
- }
52547
- }
52548
- };
52549
- } catch (err) {
52550
- log2.error(`Failed to download image ${file2.name}: ${err}`);
52551
- return {
52552
- skipped: {
52553
- name: file2.name,
52554
- reason: `Download failed: ${err instanceof Error ? err.message : String(err)}`
52555
- }
52556
- };
52557
- }
52558
- }
52559
- async function processPdfFile(file2, platform, debug = false) {
52560
- try {
52561
- if (!platform.downloadFile) {
52562
- return {
52563
- skipped: {
52564
- name: file2.name,
52565
- reason: "Platform does not support file downloads"
52566
- }
52567
- };
52568
- }
52569
- const buffer = await platform.downloadFile(file2.id);
52570
- if (buffer.length > MAX_PDF_SIZE) {
52571
- return {
52572
- skipped: {
52573
- name: file2.name,
52574
- reason: `PDF exceeds ${Math.round(MAX_PDF_SIZE / 1024 / 1024)}MB limit (${Math.round(buffer.length / 1024 / 1024)}MB)`,
52575
- suggestion: "Try splitting the PDF into smaller parts"
52576
- }
52577
- };
52578
- }
52579
- const base644 = buffer.toString("base64");
52580
- if (debug) {
52581
- log2.debug(`Attached PDF: ${file2.name} (${Math.round(buffer.length / 1024)}KB)`);
52582
- }
52583
- return {
52584
- block: {
52585
- type: "document",
52586
- source: {
52587
- type: "base64",
52588
- media_type: "application/pdf",
52589
- data: base644
52590
- },
52591
- title: file2.name
52592
- }
52593
- };
52594
- } catch (err) {
52595
- log2.error(`Failed to process PDF ${file2.name}: ${err}`);
52596
- return {
52597
- skipped: {
52598
- name: file2.name,
52599
- reason: `Processing failed: ${err instanceof Error ? err.message : String(err)}`
52600
- }
52601
- };
52602
- }
52603
- }
52604
- async function processTextFile(file2, platform, debug = false) {
52605
- try {
52606
- if (!platform.downloadFile) {
52607
- return {
52608
- skipped: {
52609
- name: file2.name,
52610
- reason: "Platform does not support file downloads"
52611
- }
52612
- };
52613
- }
52614
- const buffer = await platform.downloadFile(file2.id);
52615
- if (buffer.length > MAX_TEXT_FILE_SIZE) {
52616
- return {
52617
- skipped: {
52618
- name: file2.name,
52619
- reason: `File exceeds ${Math.round(MAX_TEXT_FILE_SIZE / 1024)}KB limit (${Math.round(buffer.length / 1024)}KB)`,
52620
- suggestion: "Try splitting the file or extracting relevant portions"
52621
- }
52622
- };
52623
- }
52624
- const content = buffer.toString("utf-8");
52625
- if (debug) {
52626
- log2.debug(`Attached text file: ${file2.name} (${Math.round(buffer.length / 1024)}KB)`);
52627
- }
52628
- const wrappedContent = formatTextFileContent(file2.name, content);
52629
- return {
52630
- block: {
52631
- type: "text",
52632
- text: wrappedContent
52633
- }
52634
- };
52635
- } catch (err) {
52636
- log2.error(`Failed to process text file ${file2.name}: ${err}`);
52637
- return {
52638
- skipped: {
52639
- name: file2.name,
52640
- reason: `Processing failed: ${err instanceof Error ? err.message : String(err)}`
52641
- }
52642
- };
52643
- }
52644
- }
52645
- function formatTextFileContent(filename, content) {
52646
- return `\uD83D\uDCC4 **${filename}**:
52647
- \`\`\`
52648
- ${content}
52649
- \`\`\``;
52650
- }
52651
- async function decompressGzipStream(compressedBuffer) {
52652
- const chunks = [];
52653
- let totalSize = 0;
52654
- const gunzip = createGunzip();
52655
- const source = Readable.from(compressedBuffer);
52656
- const collector = new Writable({
52657
- write(chunk, _encoding, callback) {
52658
- totalSize += chunk.length;
52659
- if (totalSize > MAX_DECOMPRESSED_SIZE) {
52660
- callback(new Error(`Decompressed size exceeds ${Math.round(MAX_DECOMPRESSED_SIZE / 1024 / 1024)}MB limit`));
52661
- return;
52662
- }
52663
- chunks.push(chunk);
52664
- callback();
52665
- }
52666
- });
52667
- await pipeline2(source, gunzip, collector);
52668
- return Buffer.concat(chunks);
52669
- }
52670
- async function processGzipFile(file2, platform, debug = false) {
52671
- try {
52672
- if (!platform.downloadFile) {
52673
- return {
52674
- skipped: {
52675
- name: file2.name,
52676
- reason: "Platform does not support file downloads"
52677
- }
52678
- };
52679
- }
52680
- if (file2.size && file2.size > MAX_GZIP_SIZE) {
52681
- return {
52682
- skipped: {
52683
- name: file2.name,
52684
- reason: `Gzip file exceeds ${Math.round(MAX_GZIP_SIZE / 1024 / 1024)}MB limit (${Math.round(file2.size / 1024 / 1024)}MB)`,
52685
- suggestion: "Try compressing a smaller file or splitting the content"
52686
- }
52687
- };
52688
- }
52689
- let compressedBuffer;
52690
- try {
52691
- compressedBuffer = await platform.downloadFile(file2.id);
52692
- } catch (err) {
52693
- const errorMessage = err instanceof Error ? err.message : String(err);
52694
- log2.error(`Failed to download gzip file ${file2.name}: ${errorMessage}`);
52695
- return {
52696
- skipped: {
52697
- name: file2.name,
52698
- reason: `Download failed: ${errorMessage}`,
52699
- suggestion: "Check if the file is still available and try again"
52700
- }
52701
- };
52702
- }
52703
- if (file2.size && compressedBuffer.length !== file2.size) {
52704
- log2.warn(`Downloaded size mismatch for ${file2.name}: expected ${file2.size}, got ${compressedBuffer.length}`);
52705
- }
52706
- let decompressedBuffer;
52707
- try {
52708
- decompressedBuffer = await decompressGzipStream(compressedBuffer);
52709
- } catch (err) {
52710
- const errorMessage = err instanceof Error ? err.message : String(err);
52711
- let reason;
52712
- let suggestion;
52713
- if (errorMessage.includes("incorrect header check")) {
52714
- reason = "Invalid gzip file: the file header is corrupted or this is not a gzip file";
52715
- suggestion = "Verify the file is a valid gzip archive";
52716
- } else if (errorMessage.includes("unexpected end of file")) {
52717
- reason = "Incomplete gzip file: the file appears to be truncated";
52718
- suggestion = "Re-download the file or check if the upload completed";
52719
- } else if (errorMessage.includes("invalid stored block lengths")) {
52720
- reason = "Corrupted gzip file: the compressed data is damaged";
52721
- suggestion = "Try re-compressing the original file";
52722
- } else if (errorMessage.includes("exceeds") && errorMessage.includes("limit")) {
52723
- reason = errorMessage;
52724
- suggestion = "Try extracting only the relevant portions of the file";
52725
- } else {
52726
- reason = `Decompression failed: ${errorMessage}`;
52727
- suggestion = "Verify the file is a valid gzip archive";
52728
- }
52729
- return {
52730
- skipped: {
52731
- name: file2.name,
52732
- reason,
52733
- suggestion
52734
- }
52735
- };
52736
- }
52737
- const innerFilename = file2.name.toLowerCase().endsWith(".gz") ? file2.name.slice(0, -3) : file2.name;
52738
- const contentType = detectDecompressedContentType(decompressedBuffer, innerFilename);
52739
- if (debug) {
52740
- log2.debug(`Decompressed ${file2.name}: ${Math.round(decompressedBuffer.length / 1024)}KB, detected type: ${contentType}`);
52741
- }
52742
- if (contentType === "pdf") {
52743
- const base644 = decompressedBuffer.toString("base64");
52744
- return {
52745
- block: {
52746
- type: "document",
52747
- source: {
52748
- type: "base64",
52749
- media_type: "application/pdf",
52750
- data: base644
52751
- },
52752
- title: innerFilename
52753
- }
52754
- };
52755
- } else if (contentType === "text") {
52756
- const content = decompressedBuffer.toString("utf-8");
52757
- const wrappedContent = formatTextFileContent(innerFilename, content);
52758
- return {
52759
- block: {
52760
- type: "text",
52761
- text: wrappedContent
52762
- }
52763
- };
52764
- } else {
52765
- return {
52766
- skipped: {
52767
- name: file2.name,
52768
- reason: "Decompressed content type not supported",
52769
- suggestion: "Only text-based files and PDFs are supported after decompression"
52770
- }
52771
- };
52772
- }
52773
- } catch (err) {
52774
- const errorMessage = err instanceof Error ? err.message : String(err);
52775
- log2.error(`Failed to process gzip file ${file2.name}: ${errorMessage}`);
52776
- return {
52777
- skipped: {
52778
- name: file2.name,
52779
- reason: `Processing failed: ${errorMessage}`,
52780
- suggestion: "An unexpected error occurred. Please try again or contact support if the issue persists"
52781
- }
52782
- };
52783
- }
52784
- }
52785
- async function extractZipEntry(zipfile, entry) {
52786
- return new Promise((resolve2, reject) => {
52787
- zipfile.openReadStream(entry, (err, readStream) => {
52788
- if (err) {
52789
- reject(err);
52790
- return;
52791
- }
52792
- if (!readStream) {
52793
- reject(new Error("No read stream"));
52794
- return;
52795
- }
52796
- const chunks = [];
52797
- readStream.on("data", (chunk) => chunks.push(chunk));
52798
- readStream.on("end", () => resolve2(Buffer.concat(chunks)));
52799
- readStream.on("error", reject);
52800
- });
52801
- });
52802
- }
52803
- async function processZipFile(file2, platform, debug = false) {
52804
- const blocks = [];
52805
- const skipped = [];
52806
- try {
52807
- if (!platform.downloadFile) {
52808
- return {
52809
- blocks: [],
52810
- skipped: [{
52811
- name: file2.name,
52812
- reason: "Platform does not support file downloads"
52813
- }]
52814
- };
52815
- }
52816
- if (file2.size && file2.size > MAX_ZIP_SIZE) {
52817
- return {
52818
- blocks: [],
52819
- skipped: [{
52820
- name: file2.name,
52821
- reason: `Zip file exceeds ${Math.round(MAX_ZIP_SIZE / 1024 / 1024)}MB limit (${Math.round(file2.size / 1024 / 1024)}MB)`
52822
- }]
52823
- };
52824
- }
52825
- const zipBuffer = await platform.downloadFile(file2.id);
52826
- if (debug) {
52827
- log2.debug(`Processing zip file ${file2.name}: ${Math.round(zipBuffer.length / 1024)}KB`);
52828
- }
52829
- const zipfile = await new Promise((resolve2, reject) => {
52830
- import_yauzl.default.fromBuffer(zipBuffer, { lazyEntries: true }, (err, zf) => {
52831
- if (err)
52832
- reject(err);
52833
- else if (!zf)
52834
- reject(new Error("Failed to open zip file"));
52835
- else
52836
- resolve2(zf);
52837
- });
52838
- });
52839
- const entries = [];
52840
- await new Promise((resolve2, reject) => {
52841
- zipfile.on("entry", (entry) => {
52842
- if (!entry.fileName.endsWith("/")) {
52843
- entries.push(entry);
52844
- }
52845
- zipfile.readEntry();
52846
- });
52847
- zipfile.on("end", resolve2);
52848
- zipfile.on("error", reject);
52849
- zipfile.readEntry();
52850
- });
52851
- if (entries.length > MAX_ZIP_FILES) {
52852
- zipfile.close();
52853
- return {
52854
- blocks: [],
52855
- skipped: [{
52856
- name: file2.name,
52857
- reason: `Zip contains too many files (${entries.length}). Maximum is ${MAX_ZIP_FILES} files.`,
52858
- suggestion: "Extract and upload the most relevant files individually"
52859
- }]
52860
- };
52861
- }
52862
- if (entries.length === 0) {
52863
- zipfile.close();
52864
- return {
52865
- blocks: [],
52866
- skipped: [{
52867
- name: file2.name,
52868
- reason: "Zip archive is empty"
52869
- }]
52870
- };
52871
- }
52872
- const zipfile2 = await new Promise((resolve2, reject) => {
52873
- import_yauzl.default.fromBuffer(zipBuffer, { lazyEntries: true }, (err, zf) => {
52874
- if (err)
52875
- reject(err);
52876
- else if (!zf)
52877
- reject(new Error("Failed to open zip file"));
52878
- else
52879
- resolve2(zf);
52880
- });
52881
- });
52882
- let processedCount = 0;
52883
- await new Promise((resolve2, reject) => {
52884
- zipfile2.on("entry", async (entry) => {
52885
- try {
52886
- if (entry.fileName.endsWith("/")) {
52887
- zipfile2.readEntry();
52888
- return;
52889
- }
52890
- if (entry.uncompressedSize > MAX_DECOMPRESSED_SIZE) {
52891
- skipped.push({
52892
- name: entry.fileName,
52893
- reason: `File exceeds ${Math.round(MAX_DECOMPRESSED_SIZE / 1024 / 1024)}MB decompressed size limit`
52894
- });
52895
- zipfile2.readEntry();
52896
- return;
52897
- }
52898
- const buffer = await extractZipEntry(zipfile2, entry);
52899
- const contentType = detectDecompressedContentType(buffer, entry.fileName);
52900
- if (debug) {
52901
- log2.debug(`Extracted ${entry.fileName}: ${Math.round(buffer.length / 1024)}KB, type: ${contentType}`);
52902
- }
52903
- if (contentType === "pdf") {
52904
- const base644 = buffer.toString("base64");
52905
- blocks.push({
52906
- type: "document",
52907
- source: {
52908
- type: "base64",
52909
- media_type: "application/pdf",
52910
- data: base644
52911
- }
52912
- });
52913
- processedCount++;
52914
- } else if (contentType === "text") {
52915
- const content = buffer.toString("utf-8");
52916
- const wrappedContent = formatTextFileContent(entry.fileName, content);
52917
- blocks.push({
52918
- type: "text",
52919
- text: wrappedContent
52920
- });
52921
- processedCount++;
52922
- } else {
52923
- skipped.push({
52924
- name: entry.fileName,
52925
- reason: "Unsupported file type inside zip",
52926
- suggestion: "Only text-based files and PDFs are supported"
52927
- });
52928
- }
52929
- zipfile2.readEntry();
52930
- } catch (err) {
52931
- skipped.push({
52932
- name: entry.fileName,
52933
- reason: `Failed to extract: ${err instanceof Error ? err.message : String(err)}`
52934
- });
52935
- zipfile2.readEntry();
52936
- }
52937
- });
52938
- zipfile2.on("end", resolve2);
52939
- zipfile2.on("error", reject);
52940
- zipfile2.readEntry();
52941
- });
52942
- zipfile2.close();
52943
- if (debug) {
52944
- log2.debug(`Zip ${file2.name}: processed ${processedCount} files, skipped ${skipped.length}`);
52945
- }
52946
- return { blocks, skipped };
52947
- } catch (err) {
52948
- log2.error(`Failed to process zip file ${file2.name}: ${err}`);
52949
- return {
52950
- blocks: [],
52951
- skipped: [{
52952
- name: file2.name,
52953
- reason: `Failed to process zip: ${err instanceof Error ? err.message : String(err)}`
52954
- }]
52955
- };
52956
- }
52957
- }
52958
- function detectDecompressedContentType(buffer, filename) {
52959
- if (buffer.length >= 5 && buffer.toString("ascii", 0, 5) === "%PDF-") {
52960
- return "pdf";
52961
- }
52962
- const lowerFilename = filename.toLowerCase();
52963
- if (lowerFilename.endsWith(".pdf")) {
52964
- return "pdf";
52965
- }
52966
- if (TEXT_FILE_EXTENSIONS.some((ext) => lowerFilename.endsWith(ext))) {
52967
- return "text";
52968
- }
52969
- if (buffer.length > 0) {
52970
- const firstChar = String.fromCharCode(buffer[0]);
52971
- if (firstChar === "{" || firstChar === "[") {
52972
- return "text";
52973
- }
52974
- }
52975
- try {
52976
- const text = buffer.toString("utf-8");
52977
- const printableRatio = countPrintableChars(text) / text.length;
52978
- if (printableRatio > 0.9) {
52979
- return "text";
52980
- }
52981
- } catch {}
52982
- return "unknown";
52983
- }
52984
- function countPrintableChars(text) {
52985
- let count = 0;
52986
- for (let i = 0;i < text.length; i++) {
52987
- const code = text.charCodeAt(i);
52988
- if (code >= 32 && code <= 126 || code === 9 || code === 10 || code === 13) {
52989
- count++;
52990
- }
52991
- }
52992
- return count;
52993
- }
52994
- function getUnsupportedFileSuggestion(file2) {
52995
- const ext = file2.name.toLowerCase().split(".").pop();
52996
- const mime = file2.mimeType.toLowerCase();
52997
- if (ext === "doc" || ext === "docx" || mime.includes("msword") || mime.includes("wordprocessingml")) {
52998
- return "Convert to PDF for best results";
52999
- }
53000
- if (ext === "xls" || ext === "xlsx" || mime.includes("spreadsheet")) {
53001
- return "Export as CSV for text-based analysis";
53002
- }
53003
- if (ext === "ppt" || ext === "pptx" || mime.includes("presentation")) {
53004
- return "Convert to PDF for best results";
53005
- }
53006
- if (ext === "tar" || ext === "rar" || ext === "7z") {
53007
- return "Extract files and upload them individually, or use .zip format";
53008
- }
53009
- if (ext === "exe" || ext === "dll" || ext === "so" || ext === "dylib") {
53010
- return "Binary files are not supported";
53011
- }
53012
- return;
52416
+ async function postSkippedFilesFeedback(platform, threadId, skipped) {
52417
+ if (skipped.length === 0)
52418
+ return;
52419
+ await platform.createPost(formatSkippedFilesFeedback(skipped), threadId);
53013
52420
  }
53014
- async function processFiles(platform, files, debug = false) {
53015
- const blocks = [];
53016
- const skipped = [];
53017
- if (!files || files.length === 0) {
53018
- return { blocks, skipped };
53019
- }
53020
- for (const file2 of files) {
53021
- const category = categorizeFile(file2);
53022
- if (category === "zip") {
53023
- const zipResult = await processZipFile(file2, platform, debug);
53024
- blocks.push(...zipResult.blocks);
53025
- for (const s of zipResult.skipped) {
53026
- skipped.push(s);
53027
- log2.warn(`Skipped file ${s.name}: ${s.reason}`);
53028
- }
53029
- continue;
53030
- }
53031
- let result;
53032
- switch (category) {
53033
- case "image":
53034
- result = await processImageFile(file2, platform, debug);
53035
- break;
53036
- case "pdf":
53037
- result = await processPdfFile(file2, platform, debug);
53038
- break;
53039
- case "text":
53040
- result = await processTextFile(file2, platform, debug);
53041
- break;
53042
- case "gzip":
53043
- result = await processGzipFile(file2, platform, debug);
53044
- break;
53045
- case "unsupported":
53046
- default:
53047
- result = {
53048
- skipped: {
53049
- name: file2.name,
53050
- reason: `Unsupported file type: ${file2.mimeType}`,
53051
- suggestion: getUnsupportedFileSuggestion(file2)
53052
- }
53053
- };
53054
- break;
53055
- }
53056
- if (result.block) {
53057
- blocks.push(result.block);
53058
- }
53059
- if (result.skipped) {
53060
- skipped.push(result.skipped);
53061
- log2.warn(`Skipped file ${result.skipped.name}: ${result.skipped.reason}`);
52421
+ function formatSkippedFilesFeedback(skippedFiles) {
52422
+ const lines = ["⚠️ **Some files could not be processed:**"];
52423
+ for (const file2 of skippedFiles) {
52424
+ let line = `- **${file2.name}**: ${file2.reason}`;
52425
+ if (file2.suggestion) {
52426
+ line += ` _(${file2.suggestion})_`;
53062
52427
  }
52428
+ lines.push(line);
53063
52429
  }
53064
- return { blocks, skipped };
52430
+ return lines.join(`
52431
+ `);
53065
52432
  }
53066
52433
 
53067
52434
  // src/operations/message-manager.ts
@@ -53495,20 +52862,15 @@ class MessageManager {
53495
52862
  }
53496
52863
  this.session.threadLogger?.logUserMessage(username || this.session.startedBy, message, displayName, files && files.length > 0);
53497
52864
  await this.prepareForUserMessage();
53498
- let skippedFiles = [];
53499
- if (files && files.length > 0) {
53500
- const fileResult = await processFiles(this.platform, files);
53501
- skippedFiles = fileResult.skipped;
53502
- }
53503
52865
  let content = message;
52866
+ let skippedFiles = [];
53504
52867
  if (this.buildMessageContentCallback) {
53505
- content = await this.buildMessageContentCallback(message, this.platform, files);
52868
+ const built = await this.buildMessageContentCallback(message, this.platform, files);
52869
+ content = built.content;
52870
+ skippedFiles = built.skipped;
53506
52871
  }
53507
52872
  this.session.claude.sendMessage(content);
53508
- if (skippedFiles.length > 0) {
53509
- const feedback = this.formatSkippedFilesFeedback(skippedFiles);
53510
- await this.platform.createPost(feedback, this.threadId);
53511
- }
52873
+ await postSkippedFilesFeedback(this.platform, this.threadId, skippedFiles);
53512
52874
  this.session.lastActivityAt = new Date;
53513
52875
  this.session.isProcessing = true;
53514
52876
  this.emitSessionUpdateCallback?.({ status: "active", isTyping: true });
@@ -53516,18 +52878,6 @@ class MessageManager {
53516
52878
  logger.debug("User message sent to Claude");
53517
52879
  return true;
53518
52880
  }
53519
- formatSkippedFilesFeedback(skippedFiles) {
53520
- const lines = ["⚠️ **Some files could not be processed:**"];
53521
- for (const file2 of skippedFiles) {
53522
- let line = `- **${file2.name}**: ${file2.reason}`;
53523
- if (file2.suggestion) {
53524
- line += ` _(${file2.suggestion})_`;
53525
- }
53526
- lines.push(line);
53527
- }
53528
- return lines.join(`
53529
- `);
53530
- }
53531
52881
  getSession() {
53532
52882
  return this.session;
53533
52883
  }
@@ -54444,11 +53794,19 @@ class ClaudeCli extends EventEmitter2 {
54444
53794
  getMcpServerPath() {
54445
53795
  const __filename2 = fileURLToPath3(import.meta.url);
54446
53796
  const __dirname4 = dirname4(__filename2);
53797
+ const bundledPath = resolve3(__dirname4, "mcp", "permission-server.js");
53798
+ if (existsSync4(bundledPath)) {
53799
+ return bundledPath;
53800
+ }
54447
53801
  return resolve3(__dirname4, "..", "mcp", "permission-server.js");
54448
53802
  }
54449
53803
  getStatusLineWriterPath() {
54450
53804
  const __filename2 = fileURLToPath3(import.meta.url);
54451
53805
  const __dirname4 = dirname4(__filename2);
53806
+ const bundledPath = resolve3(__dirname4, "statusline", "writer.js");
53807
+ if (existsSync4(bundledPath)) {
53808
+ return bundledPath;
53809
+ }
54452
53810
  return resolve3(__dirname4, "..", "statusline", "writer.js");
54453
53811
  }
54454
53812
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.6.0",
3
+ "version": "1.6.3",
4
4
  "description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -99,7 +99,7 @@
99
99
  "husky": "^9.1.7",
100
100
  "lint-staged": "^16.2.7",
101
101
  "prettier": "^3.4.2",
102
- "typescript": "^5.9.3",
102
+ "typescript": "^6.0.2",
103
103
  "typescript-eslint": "^8.57.2",
104
104
  "yazl": "^3.3.1"
105
105
  },
@@ -118,13 +118,15 @@
118
118
  "@hono/node-server": "$@hono/node-server",
119
119
  "express-rate-limit": "$express-rate-limit",
120
120
  "flatted": ">=3.4.0",
121
- "picomatch": ">=2.3.2"
121
+ "picomatch": ">=2.3.2",
122
+ "path-to-regexp": ">=8.4.0"
122
123
  },
123
124
  "resolutions": {
124
125
  "hono": "$hono",
125
126
  "@hono/node-server": "$@hono/node-server",
126
127
  "express-rate-limit": "$express-rate-limit",
127
128
  "flatted": ">=3.4.0",
128
- "picomatch": ">=2.3.2"
129
+ "picomatch": ">=2.3.2",
130
+ "path-to-regexp": ">=8.4.0"
129
131
  }
130
132
  }