claude-threads 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11792,7 +11792,7 @@ import * as fs from "fs/promises";
11792
11792
  import { homedir as homedir4 } from "os";
11793
11793
  async function execGit(args, cwd) {
11794
11794
  const cmd = `git ${args.join(" ")}`;
11795
- log7.debug(`Executing: ${cmd}`);
11795
+ log9.debug(`Executing: ${cmd}`);
11796
11796
  return new Promise((resolve3, reject) => {
11797
11797
  const proc = crossSpawn("git", args, { cwd });
11798
11798
  let stdout = "";
@@ -11805,15 +11805,15 @@ async function execGit(args, cwd) {
11805
11805
  });
11806
11806
  proc.on("close", (code) => {
11807
11807
  if (code === 0) {
11808
- log7.debug(`${cmd} → success`);
11808
+ log9.debug(`${cmd} → success`);
11809
11809
  resolve3(stdout.trim());
11810
11810
  } else {
11811
- log7.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
11811
+ log9.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
11812
11812
  reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
11813
11813
  }
11814
11814
  });
11815
11815
  proc.on("error", (err) => {
11816
- log7.warn(`${cmd} → error: ${err}`);
11816
+ log9.warn(`${cmd} → error: ${err}`);
11817
11817
  reject(err);
11818
11818
  });
11819
11819
  });
@@ -11823,7 +11823,7 @@ async function isGitRepository(dir) {
11823
11823
  await execGit(["rev-parse", "--git-dir"], dir);
11824
11824
  return true;
11825
11825
  } catch (err) {
11826
- log7.debug(`Not a git repository: ${dir} (${err})`);
11826
+ log9.debug(`Not a git repository: ${dir} (${err})`);
11827
11827
  return false;
11828
11828
  }
11829
11829
  }
@@ -11958,7 +11958,7 @@ async function detectWorktreeInfo(workingDir) {
11958
11958
  const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
11959
11959
  const branch = branchOutput?.trim();
11960
11960
  if (!branch) {
11961
- log7.debug(`Could not detect branch for worktree at ${workingDir}`);
11961
+ log9.debug(`Could not detect branch for worktree at ${workingDir}`);
11962
11962
  return null;
11963
11963
  }
11964
11964
  const gitDirOutput = await execGit(["rev-parse", "--git-common-dir"], workingDir);
@@ -11970,45 +11970,45 @@ async function detectWorktreeInfo(workingDir) {
11970
11970
  repoRoot = repoRoot.slice(0, -4);
11971
11971
  }
11972
11972
  }
11973
- log7.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
11973
+ log9.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
11974
11974
  return {
11975
11975
  worktreePath: workingDir,
11976
11976
  branch,
11977
11977
  repoRoot: repoRoot || workingDir
11978
11978
  };
11979
11979
  } catch (err) {
11980
- log7.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
11980
+ log9.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
11981
11981
  return null;
11982
11982
  }
11983
11983
  }
11984
11984
  async function createWorktree(repoRoot, branch, targetDir) {
11985
- log7.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
11985
+ log9.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
11986
11986
  const parentDir = path.dirname(targetDir);
11987
- log7.debug(`Creating parent directory: ${parentDir}`);
11987
+ log9.debug(`Creating parent directory: ${parentDir}`);
11988
11988
  await fs.mkdir(parentDir, { recursive: true });
11989
11989
  const exists = await branchExists(repoRoot, branch);
11990
11990
  if (exists) {
11991
- log7.debug(`Branch '${branch}' exists, adding worktree`);
11991
+ log9.debug(`Branch '${branch}' exists, adding worktree`);
11992
11992
  await execGit(["worktree", "add", targetDir, branch], repoRoot);
11993
11993
  } else {
11994
- log7.debug(`Branch '${branch}' does not exist, creating with worktree`);
11994
+ log9.debug(`Branch '${branch}' does not exist, creating with worktree`);
11995
11995
  await execGit(["worktree", "add", "-b", branch, targetDir], repoRoot);
11996
11996
  }
11997
- log7.info(`Worktree created successfully: ${targetDir}`);
11997
+ log9.info(`Worktree created successfully: ${targetDir}`);
11998
11998
  return targetDir;
11999
11999
  }
12000
12000
  async function removeWorktree(repoRoot, worktreePath) {
12001
- log7.info(`Removing worktree: ${worktreePath}`);
12001
+ log9.info(`Removing worktree: ${worktreePath}`);
12002
12002
  try {
12003
12003
  await execGit(["worktree", "remove", worktreePath], repoRoot);
12004
- log7.debug("Worktree removed cleanly");
12004
+ log9.debug("Worktree removed cleanly");
12005
12005
  } catch (err) {
12006
- log7.debug(`Clean remove failed (${err}), trying force remove`);
12006
+ log9.debug(`Clean remove failed (${err}), trying force remove`);
12007
12007
  await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
12008
12008
  }
12009
- log7.debug("Pruning stale worktree references");
12009
+ log9.debug("Pruning stale worktree references");
12010
12010
  await execGit(["worktree", "prune"], repoRoot);
12011
- log7.info("Worktree removed and pruned successfully");
12011
+ log9.info("Worktree removed and pruned successfully");
12012
12012
  }
12013
12013
  async function findWorktreeByBranch(repoRoot, branch) {
12014
12014
  const worktrees = await listWorktrees(repoRoot);
@@ -12049,14 +12049,14 @@ async function writeMetadataStore(store) {
12049
12049
  await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
12050
12050
  await fs.chmod(METADATA_STORE_PATH, 384);
12051
12051
  } catch (err) {
12052
- log7.warn(`Failed to write worktree metadata store: ${err}`);
12052
+ log9.warn(`Failed to write worktree metadata store: ${err}`);
12053
12053
  }
12054
12054
  }
12055
12055
  async function writeWorktreeMetadata(worktreePath, metadata) {
12056
12056
  const store = await readMetadataStore();
12057
12057
  store[worktreePath] = metadata;
12058
12058
  await writeMetadataStore(store);
12059
- log7.debug(`Wrote worktree metadata for: ${worktreePath}`);
12059
+ log9.debug(`Wrote worktree metadata for: ${worktreePath}`);
12060
12060
  }
12061
12061
  async function readWorktreeMetadata(worktreePath) {
12062
12062
  const store = await readMetadataStore();
@@ -12079,14 +12079,14 @@ async function removeWorktreeMetadata(worktreePath) {
12079
12079
  if (store[worktreePath]) {
12080
12080
  delete store[worktreePath];
12081
12081
  await writeMetadataStore(store);
12082
- log7.debug(`Removed worktree metadata for: ${worktreePath}`);
12082
+ log9.debug(`Removed worktree metadata for: ${worktreePath}`);
12083
12083
  }
12084
12084
  }
12085
- var log7, WORKTREES_DIR, METADATA_STORE_PATH;
12085
+ var log9, WORKTREES_DIR, METADATA_STORE_PATH;
12086
12086
  var init_worktree = __esm(() => {
12087
12087
  init_spawn();
12088
12088
  init_logger();
12089
- log7 = createLogger("git-wt");
12089
+ log9 = createLogger("git-wt");
12090
12090
  WORKTREES_DIR = path.join(homedir4(), ".claude-threads", "worktrees");
12091
12091
  METADATA_STORE_PATH = path.join(homedir4(), ".claude-threads", "worktree-metadata.json");
12092
12092
  });
@@ -12665,8 +12665,8 @@ GFS4: `);
12665
12665
  fs3.createReadStream = createReadStream;
12666
12666
  fs3.createWriteStream = createWriteStream;
12667
12667
  var fs$readFile = fs3.readFile;
12668
- fs3.readFile = readFile3;
12669
- function readFile3(path2, options, cb) {
12668
+ fs3.readFile = readFile5;
12669
+ function readFile5(path2, options, cb) {
12670
12670
  if (typeof options === "function")
12671
12671
  cb = options, options = null;
12672
12672
  return go$readFile(path2, options, cb);
@@ -14118,8 +14118,8 @@ GFS4: `);
14118
14118
  fs5.createReadStream = createReadStream;
14119
14119
  fs5.createWriteStream = createWriteStream;
14120
14120
  var fs$readFile = fs5.readFile;
14121
- fs5.readFile = readFile3;
14122
- function readFile3(path6, options, cb) {
14121
+ fs5.readFile = readFile5;
14122
+ function readFile5(path6, options, cb) {
14123
14123
  if (typeof options === "function")
14124
14124
  cb = options, options = null;
14125
14125
  return go$readFile(path6, options, cb);
@@ -18048,7 +18048,7 @@ var require_react_reconciler_development = __commonJS((exports, module) => {
18048
18048
  return hook.checkDCE ? true : false;
18049
18049
  }
18050
18050
  function setIsStrictModeForDevtools(newIsStrictMode) {
18051
- typeof log32 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
18051
+ typeof log34 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
18052
18052
  if (injectedHook && typeof injectedHook.setStrictMode === "function")
18053
18053
  try {
18054
18054
  injectedHook.setStrictMode(rendererID, newIsStrictMode);
@@ -26132,7 +26132,7 @@ Check the render method of %s.`, getComponentNameFromFiber(current) || "Unknown"
26132
26132
  var fiberStack = [];
26133
26133
  var index$jscomp$0 = -1, emptyContextObject = {};
26134
26134
  Object.freeze(emptyContextObject);
26135
- var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log$1 = Math.log, LN2 = Math.LN2, nextTransitionUpdateLane = 256, nextTransitionDeferredLane = 262144, nextRetryLane = 4194304, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, shouldYield = Scheduler.unstable_shouldYield, requestPaint = Scheduler.unstable_requestPaint, now$1 = Scheduler.unstable_now, ImmediatePriority = Scheduler.unstable_ImmediatePriority, UserBlockingPriority = Scheduler.unstable_UserBlockingPriority, NormalPriority$1 = Scheduler.unstable_NormalPriority, IdlePriority = Scheduler.unstable_IdlePriority, log32 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
26135
+ var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log$1 = Math.log, LN2 = Math.LN2, nextTransitionUpdateLane = 256, nextTransitionDeferredLane = 262144, nextRetryLane = 4194304, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, shouldYield = Scheduler.unstable_shouldYield, requestPaint = Scheduler.unstable_requestPaint, now$1 = Scheduler.unstable_now, ImmediatePriority = Scheduler.unstable_ImmediatePriority, UserBlockingPriority = Scheduler.unstable_UserBlockingPriority, NormalPriority$1 = Scheduler.unstable_NormalPriority, IdlePriority = Scheduler.unstable_IdlePriority, log34 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
26136
26136
  if (typeof performance === "object" && typeof performance.now === "function") {
26137
26137
  var localPerformance = performance;
26138
26138
  var getCurrentTime = function() {
@@ -50273,6 +50273,78 @@ function convertMarkdownTablesToSlack(content) {
50273
50273
  });
50274
50274
  }
50275
50275
 
50276
+ // src/utils/safe-filename.ts
50277
+ import { basename } from "path";
50278
+ function sanitizeFilename(name) {
50279
+ const flat = basename(name.replace(/\\/g, "/"));
50280
+ const cleaned = flat.replace(/[\x00-\x1F\x7F]/g, "_").trim();
50281
+ if (!cleaned || cleaned === "." || cleaned === "..") {
50282
+ return "attachment";
50283
+ }
50284
+ return cleaned;
50285
+ }
50286
+ function formatBytes(bytes) {
50287
+ if (bytes < 1024)
50288
+ return `${bytes} B`;
50289
+ if (bytes < 1024 * 1024)
50290
+ return `${(bytes / 1024).toFixed(1)} KB`;
50291
+ if (bytes < 1024 * 1024 * 1024)
50292
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
50293
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
50294
+ }
50295
+
50296
+ // src/platform/mattermost/upload.ts
50297
+ init_logger();
50298
+ import { readFile } from "fs/promises";
50299
+ var log2 = createLogger("mm-upload");
50300
+ async function uploadFileMattermost(args) {
50301
+ const { url, token, channelId, threadId, filePath, filename, caption } = args;
50302
+ const buffer = await readFile(filePath);
50303
+ const uploadUrl = `${url}/api/v4/files?channel_id=${encodeURIComponent(channelId)}`;
50304
+ const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
50305
+ const formData = new FormData;
50306
+ formData.append("files", new Blob([arrayBuffer]), filename);
50307
+ log2.debug(`POST /files (${buffer.length} bytes, ${filename})`);
50308
+ const uploadResponse = await fetch(uploadUrl, {
50309
+ method: "POST",
50310
+ headers: {
50311
+ Authorization: `Bearer ${token}`
50312
+ },
50313
+ body: formData
50314
+ });
50315
+ if (!uploadResponse.ok) {
50316
+ const text = await uploadResponse.text();
50317
+ throw new Error(`Mattermost file upload failed: ${uploadResponse.status} ${text}`);
50318
+ }
50319
+ const uploadJson = await uploadResponse.json();
50320
+ const fileInfo = uploadJson.file_infos?.[0];
50321
+ if (!fileInfo?.id) {
50322
+ throw new Error("Mattermost file upload response missing file_infos[0].id");
50323
+ }
50324
+ const postUrl = `${url}/api/v4/posts`;
50325
+ const postBody = {
50326
+ channel_id: channelId,
50327
+ message: caption ?? "",
50328
+ root_id: threadId,
50329
+ file_ids: [fileInfo.id]
50330
+ };
50331
+ log2.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
50332
+ const postResponse = await fetch(postUrl, {
50333
+ method: "POST",
50334
+ headers: {
50335
+ Authorization: `Bearer ${token}`,
50336
+ "Content-Type": "application/json"
50337
+ },
50338
+ body: JSON.stringify(postBody)
50339
+ });
50340
+ if (!postResponse.ok) {
50341
+ const text = await postResponse.text();
50342
+ throw new Error(`Mattermost post-with-file failed: ${postResponse.status} ${text}`);
50343
+ }
50344
+ const post = await postResponse.json();
50345
+ return { postId: post.id, fileId: fileInfo.id, post };
50346
+ }
50347
+
50276
50348
  // src/platform/mattermost/formatter.ts
50277
50349
  class MattermostFormatter {
50278
50350
  formatBold(text) {
@@ -50343,7 +50415,7 @@ ${code}
50343
50415
  }
50344
50416
 
50345
50417
  // src/platform/mattermost/client.ts
50346
- var log2 = createLogger("mattermost");
50418
+ var log3 = createLogger("mattermost");
50347
50419
 
50348
50420
  class MattermostClient extends BasePlatformClient {
50349
50421
  platformId;
@@ -50353,6 +50425,7 @@ class MattermostClient extends BasePlatformClient {
50353
50425
  url;
50354
50426
  token;
50355
50427
  channelId;
50428
+ outboundFiles;
50356
50429
  userCache = new Map;
50357
50430
  botUserId = null;
50358
50431
  formatter = new MattermostFormatter;
@@ -50366,6 +50439,7 @@ class MattermostClient extends BasePlatformClient {
50366
50439
  this.channelId = platformConfig.channelId;
50367
50440
  this.botName = platformConfig.botName;
50368
50441
  this.allowedUsers = platformConfig.allowedUsers;
50442
+ this.outboundFiles = platformConfig.outboundFiles;
50369
50443
  }
50370
50444
  normalizePlatformUser(mattermostUser) {
50371
50445
  const displayName = mattermostUser.first_name || mattermostUser.nickname || mattermostUser.username;
@@ -50414,7 +50488,7 @@ class MattermostClient extends BasePlatformClient {
50414
50488
  const hasFileIds = fileIds && fileIds.length > 0;
50415
50489
  const hasFileMetadata = post.metadata?.files && post.metadata.files.length > 0;
50416
50490
  if (hasFileIds && !hasFileMetadata) {
50417
- log2.debug(`Post ${formatShortId(post.id)} has ${fileIds.length} file(s), fetching metadata`);
50491
+ log3.debug(`Post ${formatShortId(post.id)} has ${fileIds.length} file(s), fetching metadata`);
50418
50492
  try {
50419
50493
  const files = [];
50420
50494
  for (const fileId of fileIds) {
@@ -50422,7 +50496,7 @@ class MattermostClient extends BasePlatformClient {
50422
50496
  const file = await this.api("GET", `/files/${fileId}/info`);
50423
50497
  files.push(file);
50424
50498
  } catch (err) {
50425
- log2.warn(`Failed to fetch file info for ${fileId}: ${err}`);
50499
+ log3.warn(`Failed to fetch file info for ${fileId}: ${err}`);
50426
50500
  }
50427
50501
  }
50428
50502
  if (files.length > 0) {
@@ -50430,10 +50504,10 @@ class MattermostClient extends BasePlatformClient {
50430
50504
  ...post.metadata,
50431
50505
  files
50432
50506
  };
50433
- log2.debug(`Enriched post ${formatShortId(post.id)} with ${files.length} file(s)`);
50507
+ log3.debug(`Enriched post ${formatShortId(post.id)} with ${files.length} file(s)`);
50434
50508
  }
50435
50509
  } catch (err) {
50436
- log2.warn(`Failed to fetch file metadata for post ${formatShortId(post.id)}: ${err}`);
50510
+ log3.warn(`Failed to fetch file metadata for post ${formatShortId(post.id)}: ${err}`);
50437
50511
  }
50438
50512
  }
50439
50513
  const user = await this.getUser(post.user_id);
@@ -50447,7 +50521,7 @@ class MattermostClient extends BasePlatformClient {
50447
50521
  RETRY_DELAY_MS = 500;
50448
50522
  async api(method, path, body, retryCount = 0, options) {
50449
50523
  const url = `${this.url}/api/v4${path}`;
50450
- log2.debug(`API ${method} ${path}`);
50524
+ log3.debug(`API ${method} ${path}`);
50451
50525
  const response = await fetch(url, {
50452
50526
  method,
50453
50527
  headers: {
@@ -50460,19 +50534,19 @@ class MattermostClient extends BasePlatformClient {
50460
50534
  const text = await response.text();
50461
50535
  if (response.status === 500 && retryCount < this.MAX_RETRIES) {
50462
50536
  const delay = this.RETRY_DELAY_MS * Math.pow(2, retryCount);
50463
- log2.warn(`API ${method} ${path} failed with 500, retrying in ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
50537
+ log3.warn(`API ${method} ${path} failed with 500, retrying in ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
50464
50538
  await new Promise((resolve3) => setTimeout(resolve3, delay));
50465
50539
  return this.api(method, path, body, retryCount + 1, options);
50466
50540
  }
50467
50541
  const isSilent = options?.silent?.includes(response.status);
50468
50542
  if (isSilent) {
50469
- log2.debug(`API ${method} ${path} failed: ${response.status} (expected)`);
50543
+ log3.debug(`API ${method} ${path} failed: ${response.status} (expected)`);
50470
50544
  } else {
50471
- log2.warn(`API ${method} ${path} failed: ${response.status} ${text.substring(0, 100)}`);
50545
+ log3.warn(`API ${method} ${path} failed: ${response.status} ${text.substring(0, 100)}`);
50472
50546
  }
50473
50547
  throw new Error(`Mattermost API error ${response.status}: ${text}`);
50474
50548
  }
50475
- log2.debug(`API ${method} ${path} → ${response.status}`);
50549
+ log3.debug(`API ${method} ${path} → ${response.status}`);
50476
50550
  return response.json();
50477
50551
  }
50478
50552
  async getBotUser() {
@@ -50483,28 +50557,28 @@ class MattermostClient extends BasePlatformClient {
50483
50557
  async getUser(userId) {
50484
50558
  const cached = this.userCache.get(userId);
50485
50559
  if (cached) {
50486
- log2.debug(`User ${userId} found in cache: @${cached.username}`);
50560
+ log3.debug(`User ${userId} found in cache: @${cached.username}`);
50487
50561
  return this.normalizePlatformUser(cached);
50488
50562
  }
50489
50563
  try {
50490
50564
  const user = await this.api("GET", `/users/${userId}`);
50491
50565
  this.userCache.set(userId, user);
50492
- log2.debug(`User ${userId} fetched: @${user.username}`);
50566
+ log3.debug(`User ${userId} fetched: @${user.username}`);
50493
50567
  return this.normalizePlatformUser(user);
50494
50568
  } catch (err) {
50495
- log2.warn(`Failed to get user ${userId}: ${err}`);
50569
+ log3.warn(`Failed to get user ${userId}: ${err}`);
50496
50570
  return null;
50497
50571
  }
50498
50572
  }
50499
50573
  async getUserByUsername(username) {
50500
50574
  try {
50501
- log2.debug(`Looking up user by username: @${username}`);
50575
+ log3.debug(`Looking up user by username: @${username}`);
50502
50576
  const user = await this.api("GET", `/users/username/${username}`);
50503
50577
  this.userCache.set(user.id, user);
50504
- log2.debug(`User @${username} found: ${user.id}`);
50578
+ log3.debug(`User @${username} found: ${user.id}`);
50505
50579
  return this.normalizePlatformUser(user);
50506
50580
  } catch (err) {
50507
- log2.warn(`User @${username} not found: ${err}`);
50581
+ log3.warn(`User @${username} not found: ${err}`);
50508
50582
  return null;
50509
50583
  }
50510
50584
  }
@@ -50526,7 +50600,7 @@ class MattermostClient extends BasePlatformClient {
50526
50600
  return this.normalizePlatformPost(post);
50527
50601
  }
50528
50602
  async addReaction(postId, emojiName) {
50529
- log2.debug(`Adding reaction :${emojiName}: to post ${postId.substring(0, 8)}`);
50603
+ log3.debug(`Adding reaction :${emojiName}: to post ${postId.substring(0, 8)}`);
50530
50604
  await this.api("POST", "/reactions", {
50531
50605
  user_id: this.botUserId,
50532
50606
  post_id: postId,
@@ -50534,11 +50608,11 @@ class MattermostClient extends BasePlatformClient {
50534
50608
  });
50535
50609
  }
50536
50610
  async removeReaction(postId, emojiName) {
50537
- log2.debug(`Removing reaction :${emojiName}: from post ${postId.substring(0, 8)}`);
50611
+ log3.debug(`Removing reaction :${emojiName}: from post ${postId.substring(0, 8)}`);
50538
50612
  await this.api("DELETE", `/users/${this.botUserId}/posts/${postId}/reactions/${emojiName}`);
50539
50613
  }
50540
50614
  async downloadFile(fileId) {
50541
- log2.debug(`Downloading file ${fileId}`);
50615
+ log3.debug(`Downloading file ${fileId}`);
50542
50616
  const url = `${this.url}/api/v4/files/${fileId}`;
50543
50617
  const response = await fetch(url, {
50544
50618
  headers: {
@@ -50546,37 +50620,50 @@ class MattermostClient extends BasePlatformClient {
50546
50620
  }
50547
50621
  });
50548
50622
  if (!response.ok) {
50549
- log2.warn(`Failed to download file ${fileId}: ${response.status}`);
50623
+ log3.warn(`Failed to download file ${fileId}: ${response.status}`);
50550
50624
  throw new Error(`Failed to download file ${fileId}: ${response.status}`);
50551
50625
  }
50552
50626
  const arrayBuffer = await response.arrayBuffer();
50553
- log2.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
50627
+ log3.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
50554
50628
  return Buffer.from(arrayBuffer);
50555
50629
  }
50556
50630
  async getFileInfo(fileId) {
50557
50631
  const file = await this.api("GET", `/files/${fileId}/info`);
50558
50632
  return this.normalizePlatformFile(file);
50559
50633
  }
50634
+ async uploadFile(filePath, threadId, options) {
50635
+ const filename = sanitizeFilename(options?.filename ?? filePath);
50636
+ const result = await uploadFileMattermost({
50637
+ url: this.url,
50638
+ token: this.token,
50639
+ channelId: this.channelId,
50640
+ threadId,
50641
+ filePath,
50642
+ filename,
50643
+ caption: options?.caption
50644
+ });
50645
+ return { postId: result.postId, fileId: result.fileId };
50646
+ }
50560
50647
  async getPost(postId) {
50561
50648
  try {
50562
- log2.debug(`Fetching post ${postId.substring(0, 8)}`);
50649
+ log3.debug(`Fetching post ${postId.substring(0, 8)}`);
50563
50650
  const post = await this.api("GET", `/posts/${postId}`);
50564
50651
  return this.normalizePlatformPost(post);
50565
50652
  } catch (err) {
50566
- log2.debug(`Post ${postId.substring(0, 8)} not found: ${err}`);
50653
+ log3.debug(`Post ${postId.substring(0, 8)} not found: ${err}`);
50567
50654
  return null;
50568
50655
  }
50569
50656
  }
50570
50657
  async deletePost(postId) {
50571
- log2.debug(`Deleting post ${postId.substring(0, 8)}`);
50658
+ log3.debug(`Deleting post ${postId.substring(0, 8)}`);
50572
50659
  await this.api("DELETE", `/posts/${postId}`);
50573
50660
  }
50574
50661
  async pinPost(postId) {
50575
- log2.debug(`Pinning post ${postId.substring(0, 8)}`);
50662
+ log3.debug(`Pinning post ${postId.substring(0, 8)}`);
50576
50663
  await this.api("POST", `/posts/${postId}/pin`);
50577
50664
  }
50578
50665
  async unpinPost(postId) {
50579
- log2.debug(`Unpinning post ${postId.substring(0, 8)}`);
50666
+ log3.debug(`Unpinning post ${postId.substring(0, 8)}`);
50580
50667
  try {
50581
50668
  await this.api("POST", `/posts/${postId}/unpin`, undefined, 0, { silent: [403, 404] });
50582
50669
  } catch (err) {
@@ -50620,7 +50707,7 @@ class MattermostClient extends BasePlatformClient {
50620
50707
  }
50621
50708
  return messages;
50622
50709
  } catch (err) {
50623
- log2.warn(`Failed to get thread history for ${threadId}: ${err}`);
50710
+ log3.warn(`Failed to get thread history for ${threadId}: ${err}`);
50624
50711
  return [];
50625
50712
  }
50626
50713
  }
@@ -50639,7 +50726,7 @@ class MattermostClient extends BasePlatformClient {
50639
50726
  posts.sort((a, b) => (a.createAt ?? 0) - (b.createAt ?? 0));
50640
50727
  return posts;
50641
50728
  } catch (err) {
50642
- log2.warn(`Failed to get channel posts after ${afterPostId}: ${err}`);
50729
+ log3.warn(`Failed to get channel posts after ${afterPostId}: ${err}`);
50643
50730
  return [];
50644
50731
  }
50645
50732
  }
@@ -50767,13 +50854,13 @@ class MattermostClient extends BasePlatformClient {
50767
50854
  if (!this.lastProcessedPostId) {
50768
50855
  return;
50769
50856
  }
50770
- log2.info(`Recovering missed messages after post ${this.lastProcessedPostId}...`);
50857
+ log3.info(`Recovering missed messages after post ${this.lastProcessedPostId}...`);
50771
50858
  const missedPosts = await this.getChannelPostsAfter(this.lastProcessedPostId);
50772
50859
  if (missedPosts.length === 0) {
50773
- log2.info("No missed messages to recover");
50860
+ log3.info("No missed messages to recover");
50774
50861
  return;
50775
50862
  }
50776
- log2.info(`Recovered ${missedPosts.length} missed message(s)`);
50863
+ log3.info(`Recovered ${missedPosts.length} missed message(s)`);
50777
50864
  for (const post of missedPosts) {
50778
50865
  this.lastProcessedPostId = post.id;
50779
50866
  const user = await this.getUser(post.userId);
@@ -50798,7 +50885,8 @@ class MattermostClient extends BasePlatformClient {
50798
50885
  url: this.url,
50799
50886
  token: this.token,
50800
50887
  channelId: this.channelId,
50801
- allowedUsers: this.allowedUsers
50888
+ allowedUsers: this.allowedUsers,
50889
+ outboundFiles: this.outboundFiles
50802
50890
  };
50803
50891
  }
50804
50892
  getFormatter() {
@@ -50826,6 +50914,78 @@ class MattermostClient extends BasePlatformClient {
50826
50914
  // src/platform/slack/client.ts
50827
50915
  init_logger();
50828
50916
 
50917
+ // src/platform/slack/upload.ts
50918
+ init_logger();
50919
+ import { readFile as readFile2 } from "fs/promises";
50920
+ var log4 = createLogger("slack-upload");
50921
+ var DEFAULT_API_URL = "https://slack.com/api";
50922
+ async function uploadFileSlack(args) {
50923
+ const { botToken, channelId, threadTs, filePath, filename, caption } = args;
50924
+ const apiUrl = args.apiUrl ?? DEFAULT_API_URL;
50925
+ const buffer = await readFile2(filePath);
50926
+ const params = new URLSearchParams({ filename, length: String(buffer.length) });
50927
+ const step1Url = `${apiUrl}/files.getUploadURLExternal?${params.toString()}`;
50928
+ log4.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
50929
+ const step1Response = await fetch(step1Url, {
50930
+ method: "GET",
50931
+ headers: {
50932
+ Authorization: `Bearer ${botToken}`
50933
+ }
50934
+ });
50935
+ if (!step1Response.ok) {
50936
+ const text = await step1Response.text();
50937
+ throw new Error(`Slack getUploadURLExternal failed: ${step1Response.status} ${text}`);
50938
+ }
50939
+ const step1Data = await step1Response.json();
50940
+ if (!step1Data.ok || !step1Data.upload_url || !step1Data.file_id) {
50941
+ throw new Error(`Slack getUploadURLExternal error: ${step1Data.error || "missing upload_url/file_id"}`);
50942
+ }
50943
+ const uploadUrl = step1Data.upload_url;
50944
+ const fileId = step1Data.file_id;
50945
+ const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
50946
+ log4.debug(`POST <upload_url>`);
50947
+ const step2Response = await fetch(uploadUrl, {
50948
+ method: "POST",
50949
+ headers: {
50950
+ "Content-Type": "application/octet-stream"
50951
+ },
50952
+ body: arrayBuffer
50953
+ });
50954
+ if (!step2Response.ok) {
50955
+ const text = await step2Response.text();
50956
+ throw new Error(`Slack file bytes upload failed: ${step2Response.status} ${text}`);
50957
+ }
50958
+ const step3Body = {
50959
+ files: [{ id: fileId, title: caption ?? filename }],
50960
+ channel_id: channelId,
50961
+ thread_ts: threadTs
50962
+ };
50963
+ if (caption !== undefined) {
50964
+ step3Body.initial_comment = caption;
50965
+ }
50966
+ log4.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
50967
+ const step3Response = await fetch(`${apiUrl}/files.completeUploadExternal`, {
50968
+ method: "POST",
50969
+ headers: {
50970
+ Authorization: `Bearer ${botToken}`,
50971
+ "Content-Type": "application/json; charset=utf-8"
50972
+ },
50973
+ body: JSON.stringify(step3Body)
50974
+ });
50975
+ if (!step3Response.ok) {
50976
+ const text = await step3Response.text();
50977
+ throw new Error(`Slack completeUploadExternal failed: ${step3Response.status} ${text}`);
50978
+ }
50979
+ const step3Data = await step3Response.json();
50980
+ if (!step3Data.ok) {
50981
+ throw new Error(`Slack completeUploadExternal error: ${step3Data.error || "unknown"}`);
50982
+ }
50983
+ if (!step3Data.ts) {
50984
+ log4.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
50985
+ }
50986
+ return { fileId, postId: step3Data.ts ?? fileId };
50987
+ }
50988
+
50829
50989
  // src/platform/slack/formatter.ts
50830
50990
  class SlackFormatter {
50831
50991
  formatBold(text) {
@@ -50896,7 +51056,7 @@ ${code}
50896
51056
  }
50897
51057
 
50898
51058
  // src/platform/slack/client.ts
50899
- var log3 = createLogger("slack");
51059
+ var log5 = createLogger("slack");
50900
51060
 
50901
51061
  class SlackClient extends BasePlatformClient {
50902
51062
  platformId;
@@ -50918,6 +51078,7 @@ class SlackClient extends BasePlatformClient {
50918
51078
  MAX_PROCESSED_MESSAGES = 1000;
50919
51079
  rateLimitDelay = 0;
50920
51080
  rateLimitRetryAfter = 0;
51081
+ outboundFiles;
50921
51082
  formatter = new SlackFormatter;
50922
51083
  constructor(platformConfig) {
50923
51084
  super();
@@ -50930,6 +51091,7 @@ class SlackClient extends BasePlatformClient {
50930
51091
  this.allowedUsers = platformConfig.allowedUsers;
50931
51092
  this.skipPermissions = platformConfig.skipPermissions ?? false;
50932
51093
  this.apiUrl = platformConfig.apiUrl || "https://slack.com/api";
51094
+ this.outboundFiles = platformConfig.outboundFiles;
50933
51095
  }
50934
51096
  normalizePlatformUser(slackUser) {
50935
51097
  const displayName = slackUser.profile?.display_name || slackUser.profile?.real_name || slackUser.real_name || slackUser.name;
@@ -50969,13 +51131,13 @@ class SlackClient extends BasePlatformClient {
50969
51131
  const now = Date.now();
50970
51132
  if (now < this.rateLimitRetryAfter) {
50971
51133
  const waitTime = this.rateLimitRetryAfter - now;
50972
- log3.debug(`Rate limited, waiting ${waitTime}ms`);
51134
+ log5.debug(`Rate limited, waiting ${waitTime}ms`);
50973
51135
  await new Promise((resolve3) => setTimeout(resolve3, waitTime));
50974
51136
  }
50975
51137
  this.rateLimitDelay = 0;
50976
51138
  }
50977
51139
  const url = `${this.apiUrl}/${endpoint}`;
50978
- log3.debug(`API ${method} ${endpoint}`);
51140
+ log5.debug(`API ${method} ${endpoint}`);
50979
51141
  const headers = {
50980
51142
  Authorization: `Bearer ${this.botToken}`,
50981
51143
  "Content-Type": "application/json; charset=utf-8"
@@ -50987,25 +51149,25 @@ class SlackClient extends BasePlatformClient {
50987
51149
  });
50988
51150
  if (response.status === 429) {
50989
51151
  if (retryCount >= this.MAX_RATE_LIMIT_RETRIES) {
50990
- log3.error(`Rate limit max retries (${this.MAX_RATE_LIMIT_RETRIES}) exceeded for ${endpoint}`);
51152
+ log5.error(`Rate limit max retries (${this.MAX_RATE_LIMIT_RETRIES}) exceeded for ${endpoint}`);
50991
51153
  throw new Error(`Slack API rate limit exceeded after ${this.MAX_RATE_LIMIT_RETRIES} retries`);
50992
51154
  }
50993
51155
  const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
50994
51156
  this.rateLimitDelay = retryAfter * 1000;
50995
51157
  this.rateLimitRetryAfter = Date.now() + this.rateLimitDelay;
50996
- log3.warn(`Rate limited by Slack, retrying after ${retryAfter}s (attempt ${retryCount + 1}/${this.MAX_RATE_LIMIT_RETRIES})`);
51158
+ log5.warn(`Rate limited by Slack, retrying after ${retryAfter}s (attempt ${retryCount + 1}/${this.MAX_RATE_LIMIT_RETRIES})`);
50997
51159
  await new Promise((resolve3) => setTimeout(resolve3, this.rateLimitDelay));
50998
51160
  return this.api(method, endpoint, body, retryCount + 1);
50999
51161
  }
51000
51162
  if (!response.ok) {
51001
51163
  const text = await response.text();
51002
- log3.warn(`API ${method} ${endpoint} failed: ${response.status} ${text.substring(0, 100)}`);
51164
+ log5.warn(`API ${method} ${endpoint} failed: ${response.status} ${text.substring(0, 100)}`);
51003
51165
  throw new Error(`Slack API error ${response.status}: ${text}`);
51004
51166
  }
51005
51167
  const data = await response.json();
51006
51168
  if (!data.ok) {
51007
51169
  if (!expectedErrors.includes(data.error || "")) {
51008
- log3.warn(`API ${method} ${endpoint} error: ${data.error}`);
51170
+ log5.warn(`API ${method} ${endpoint} error: ${data.error}`);
51009
51171
  }
51010
51172
  throw new Error(`Slack API error: ${data.error}`);
51011
51173
  }
@@ -51013,7 +51175,7 @@ class SlackClient extends BasePlatformClient {
51013
51175
  }
51014
51176
  async appApi(method, endpoint, body) {
51015
51177
  const url = `${this.apiUrl}/${endpoint}`;
51016
- log3.debug(`App API ${method} ${endpoint}`);
51178
+ log5.debug(`App API ${method} ${endpoint}`);
51017
51179
  const headers = {
51018
51180
  Authorization: `Bearer ${this.appToken}`,
51019
51181
  "Content-Type": "application/json; charset=utf-8"
@@ -51076,7 +51238,7 @@ class SlackClient extends BasePlatformClient {
51076
51238
  this.onConnectionEstablished();
51077
51239
  if (this.isReconnecting && this.lastProcessedTs) {
51078
51240
  this.recoverMissedMessages().catch((err) => {
51079
- log3.warn(`Failed to recover missed messages: ${err}`);
51241
+ log5.warn(`Failed to recover missed messages: ${err}`);
51080
51242
  });
51081
51243
  }
51082
51244
  doResolve();
@@ -51173,7 +51335,7 @@ class SlackClient extends BasePlatformClient {
51173
51335
  this.emit("channel_post", post, user);
51174
51336
  }
51175
51337
  }).catch((err) => {
51176
- log3.warn(`Failed to get user for message event: ${err}`);
51338
+ log5.warn(`Failed to get user for message event: ${err}`);
51177
51339
  this.emit("message", post, null);
51178
51340
  });
51179
51341
  }
@@ -51193,7 +51355,7 @@ class SlackClient extends BasePlatformClient {
51193
51355
  this.getUser(event.user || "").then((user) => {
51194
51356
  this.emit("reaction", reaction, user);
51195
51357
  }).catch((err) => {
51196
- log3.warn(`Failed to get user for reaction event: ${err}`);
51358
+ log5.warn(`Failed to get user for reaction event: ${err}`);
51197
51359
  this.emit("reaction", reaction, null);
51198
51360
  });
51199
51361
  }
@@ -51213,7 +51375,7 @@ class SlackClient extends BasePlatformClient {
51213
51375
  this.getUser(event.user || "").then((user) => {
51214
51376
  this.emit("reaction_removed", reaction, user);
51215
51377
  }).catch((err) => {
51216
- log3.warn(`Failed to get user for reaction_removed event: ${err}`);
51378
+ log5.warn(`Failed to get user for reaction_removed event: ${err}`);
51217
51379
  this.emit("reaction_removed", reaction, null);
51218
51380
  });
51219
51381
  }
@@ -51250,15 +51412,15 @@ class SlackClient extends BasePlatformClient {
51250
51412
  if (!this.lastProcessedTs) {
51251
51413
  return;
51252
51414
  }
51253
- log3.info(`Recovering missed messages after ts ${this.lastProcessedTs}...`);
51415
+ log5.info(`Recovering missed messages after ts ${this.lastProcessedTs}...`);
51254
51416
  try {
51255
51417
  const response = await this.api("GET", `conversations.history?channel=${this.channelId}&oldest=${this.lastProcessedTs}&inclusive=false&limit=100`);
51256
51418
  const messages = response.messages || [];
51257
51419
  if (messages.length === 0) {
51258
- log3.info("No missed messages to recover");
51420
+ log5.info("No missed messages to recover");
51259
51421
  return;
51260
51422
  }
51261
- log3.info(`Recovered ${messages.length} missed message(s)`);
51423
+ log5.info(`Recovered ${messages.length} missed message(s)`);
51262
51424
  const sortedMessages = messages.sort((a, b) => parseFloat(a.ts) - parseFloat(b.ts));
51263
51425
  for (const message of sortedMessages) {
51264
51426
  if (message.user === this.botUserId || message.bot_id) {
@@ -51273,7 +51435,7 @@ class SlackClient extends BasePlatformClient {
51273
51435
  }
51274
51436
  }
51275
51437
  } catch (err) {
51276
- log3.warn(`Failed to recover missed messages: ${err}`);
51438
+ log5.warn(`Failed to recover missed messages: ${err}`);
51277
51439
  }
51278
51440
  }
51279
51441
  async fetchBotUser() {
@@ -51297,17 +51459,17 @@ class SlackClient extends BasePlatformClient {
51297
51459
  }
51298
51460
  const cached = this.userCache.get(userId);
51299
51461
  if (cached) {
51300
- log3.debug(`User ${userId} found in cache: @${cached.name}`);
51462
+ log5.debug(`User ${userId} found in cache: @${cached.name}`);
51301
51463
  return this.normalizePlatformUser(cached);
51302
51464
  }
51303
51465
  try {
51304
51466
  const response = await this.api("GET", `users.info?user=${userId}`);
51305
51467
  this.userCache.set(userId, response.user);
51306
51468
  this.usernameToIdCache.set(response.user.name, userId);
51307
- log3.debug(`User ${userId} fetched: @${response.user.name}`);
51469
+ log5.debug(`User ${userId} fetched: @${response.user.name}`);
51308
51470
  return this.normalizePlatformUser(response.user);
51309
51471
  } catch (err) {
51310
- log3.warn(`Failed to get user ${userId}: ${err}`);
51472
+ log5.warn(`Failed to get user ${userId}: ${err}`);
51311
51473
  return null;
51312
51474
  }
51313
51475
  }
@@ -51317,7 +51479,7 @@ class SlackClient extends BasePlatformClient {
51317
51479
  return this.getUser(cachedId);
51318
51480
  }
51319
51481
  try {
51320
- log3.debug(`Looking up user by username: @${username}`);
51482
+ log5.debug(`Looking up user by username: @${username}`);
51321
51483
  let cursor;
51322
51484
  do {
51323
51485
  const params = cursor ? `cursor=${cursor}&limit=200` : "limit=200";
@@ -51326,16 +51488,16 @@ class SlackClient extends BasePlatformClient {
51326
51488
  this.userCache.set(user.id, user);
51327
51489
  this.usernameToIdCache.set(user.name, user.id);
51328
51490
  if (user.name === username) {
51329
- log3.debug(`User @${username} found: ${user.id}`);
51491
+ log5.debug(`User @${username} found: ${user.id}`);
51330
51492
  return this.normalizePlatformUser(user);
51331
51493
  }
51332
51494
  }
51333
51495
  cursor = response.response_metadata?.next_cursor;
51334
51496
  } while (cursor);
51335
- log3.warn(`User @${username} not found`);
51497
+ log5.warn(`User @${username} not found`);
51336
51498
  return null;
51337
51499
  } catch (err) {
51338
- log3.warn(`Failed to lookup user @${username}: ${err}`);
51500
+ log5.warn(`Failed to lookup user @${username}: ${err}`);
51339
51501
  return null;
51340
51502
  }
51341
51503
  }
@@ -51346,7 +51508,8 @@ class SlackClient extends BasePlatformClient {
51346
51508
  token: this.botToken,
51347
51509
  channelId: this.channelId,
51348
51510
  allowedUsers: this.allowedUsers,
51349
- appToken: this.appToken
51511
+ appToken: this.appToken,
51512
+ outboundFiles: this.outboundFiles
51350
51513
  };
51351
51514
  }
51352
51515
  getFormatter() {
@@ -51410,19 +51573,19 @@ class SlackClient extends BasePlatformClient {
51410
51573
  }
51411
51574
  return null;
51412
51575
  } catch (err) {
51413
- log3.debug(`Post ${postId.substring(0, 12)} not found: ${err}`);
51576
+ log5.debug(`Post ${postId.substring(0, 12)} not found: ${err}`);
51414
51577
  return null;
51415
51578
  }
51416
51579
  }
51417
51580
  async deletePost(postId) {
51418
- log3.debug(`Deleting post ${postId.substring(0, 12)}`);
51581
+ log5.debug(`Deleting post ${postId.substring(0, 12)}`);
51419
51582
  await this.api("POST", "chat.delete", {
51420
51583
  channel: this.channelId,
51421
51584
  ts: postId
51422
51585
  });
51423
51586
  }
51424
51587
  async pinPost(postId) {
51425
- log3.debug(`Pinning post ${postId.substring(0, 12)}`);
51588
+ log5.debug(`Pinning post ${postId.substring(0, 12)}`);
51426
51589
  try {
51427
51590
  await this.api("POST", "pins.add", {
51428
51591
  channel: this.channelId,
@@ -51430,14 +51593,14 @@ class SlackClient extends BasePlatformClient {
51430
51593
  }, 0, ["already_pinned"]);
51431
51594
  } catch (err) {
51432
51595
  if (err instanceof Error && err.message.includes("already_pinned")) {
51433
- log3.debug(`Post ${postId.substring(0, 12)} already pinned`);
51596
+ log5.debug(`Post ${postId.substring(0, 12)} already pinned`);
51434
51597
  return;
51435
51598
  }
51436
51599
  throw err;
51437
51600
  }
51438
51601
  }
51439
51602
  async unpinPost(postId) {
51440
- log3.debug(`Unpinning post ${postId.substring(0, 12)}`);
51603
+ log5.debug(`Unpinning post ${postId.substring(0, 12)}`);
51441
51604
  try {
51442
51605
  await this.api("POST", "pins.remove", {
51443
51606
  channel: this.channelId,
@@ -51445,7 +51608,7 @@ class SlackClient extends BasePlatformClient {
51445
51608
  }, 0, ["no_pin"]);
51446
51609
  } catch (err) {
51447
51610
  if (err instanceof Error && err.message.includes("no_pin")) {
51448
- log3.debug(`Post ${postId.substring(0, 12)} was not pinned`);
51611
+ log5.debug(`Post ${postId.substring(0, 12)} was not pinned`);
51449
51612
  return;
51450
51613
  }
51451
51614
  throw err;
@@ -51463,7 +51626,7 @@ class SlackClient extends BasePlatformClient {
51463
51626
  if (message.length <= maxLength) {
51464
51627
  return message;
51465
51628
  }
51466
- log3.warn(`Truncating message from ${message.length} to ~${maxLength} chars`);
51629
+ log5.warn(`Truncating message from ${message.length} to ~${maxLength} chars`);
51467
51630
  return truncateMessageSafely(message, maxLength, "_... (truncated)_");
51468
51631
  }
51469
51632
  async getThreadHistory(threadId, options) {
@@ -51488,13 +51651,13 @@ class SlackClient extends BasePlatformClient {
51488
51651
  messages.sort((a, b) => a.createAt - b.createAt);
51489
51652
  return messages;
51490
51653
  } catch (err) {
51491
- log3.warn(`Failed to get thread history for ${threadId}: ${err}`);
51654
+ log5.warn(`Failed to get thread history for ${threadId}: ${err}`);
51492
51655
  return [];
51493
51656
  }
51494
51657
  }
51495
51658
  async addReaction(postId, emojiName) {
51496
51659
  const name = getEmojiName(emojiName);
51497
- log3.debug(`Adding reaction :${name}: to post ${postId.substring(0, 12)}`);
51660
+ log5.debug(`Adding reaction :${name}: to post ${postId.substring(0, 12)}`);
51498
51661
  await this.api("POST", "reactions.add", {
51499
51662
  channel: this.channelId,
51500
51663
  timestamp: postId,
@@ -51503,7 +51666,7 @@ class SlackClient extends BasePlatformClient {
51503
51666
  }
51504
51667
  async removeReaction(postId, emojiName) {
51505
51668
  const name = getEmojiName(emojiName);
51506
- log3.debug(`Removing reaction :${name}: from post ${postId.substring(0, 12)}`);
51669
+ log5.debug(`Removing reaction :${name}: from post ${postId.substring(0, 12)}`);
51507
51670
  await this.api("POST", "reactions.remove", {
51508
51671
  channel: this.channelId,
51509
51672
  timestamp: postId,
@@ -51529,7 +51692,7 @@ class SlackClient extends BasePlatformClient {
51529
51692
  }
51530
51693
  sendTyping(_threadId) {}
51531
51694
  async downloadFile(fileId) {
51532
- log3.debug(`Downloading file ${fileId}`);
51695
+ log5.debug(`Downloading file ${fileId}`);
51533
51696
  const fileInfo = await this.api("GET", `files.info?file=${fileId}`);
51534
51697
  const downloadUrl = fileInfo.file.url_private_download || fileInfo.file.url_private;
51535
51698
  if (!downloadUrl) {
@@ -51541,17 +51704,30 @@ class SlackClient extends BasePlatformClient {
51541
51704
  }
51542
51705
  });
51543
51706
  if (!response.ok) {
51544
- log3.warn(`Failed to download file ${fileId}: ${response.status}`);
51707
+ log5.warn(`Failed to download file ${fileId}: ${response.status}`);
51545
51708
  throw new Error(`Failed to download file ${fileId}: ${response.status}`);
51546
51709
  }
51547
51710
  const arrayBuffer = await response.arrayBuffer();
51548
- log3.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
51711
+ log5.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
51549
51712
  return Buffer.from(arrayBuffer);
51550
51713
  }
51551
51714
  async getFileInfo(fileId) {
51552
51715
  const response = await this.api("GET", `files.info?file=${fileId}`);
51553
51716
  return this.normalizePlatformFile(response.file);
51554
51717
  }
51718
+ async uploadFile(filePath, threadId, options) {
51719
+ const filename = sanitizeFilename(options?.filename ?? filePath);
51720
+ const result = await uploadFileSlack({
51721
+ botToken: this.botToken,
51722
+ channelId: this.channelId,
51723
+ threadTs: threadId,
51724
+ filePath,
51725
+ filename,
51726
+ caption: options?.caption,
51727
+ apiUrl: this.apiUrl
51728
+ });
51729
+ return { postId: result.postId, fileId: result.fileId };
51730
+ }
51555
51731
  }
51556
51732
  // src/platform/mattermost/permission-api.ts
51557
51733
  init_logger();
@@ -51748,6 +51924,20 @@ class MattermostPermissionApi {
51748
51924
  };
51749
51925
  });
51750
51926
  }
51927
+ async uploadFile(filePath, threadId, options) {
51928
+ const filename = sanitizeFilename(options?.filename ?? filePath);
51929
+ mcpLogger.debug(`uploadFile: ${filename} → thread ${formatShortId(threadId)}`);
51930
+ const result = await uploadFileMattermost({
51931
+ url: this.config.url,
51932
+ token: this.config.token,
51933
+ channelId: this.config.channelId,
51934
+ threadId,
51935
+ filePath,
51936
+ filename,
51937
+ caption: options?.caption
51938
+ });
51939
+ return { postId: result.postId };
51940
+ }
51751
51941
  }
51752
51942
 
51753
51943
  // src/platform/slack/permission-api.ts
@@ -51959,6 +52149,19 @@ class SlackPermissionApi {
51959
52149
  mcpLogger.debug("Got Socket Mode URL");
51960
52150
  return response.url;
51961
52151
  }
52152
+ async uploadFile(filePath, threadId, options) {
52153
+ const filename = sanitizeFilename(options?.filename ?? filePath);
52154
+ mcpLogger.debug(`uploadFile: ${filename} → thread_ts ${threadId}`);
52155
+ const result = await uploadFileSlack({
52156
+ botToken: this.config.botToken,
52157
+ channelId: this.config.channelId,
52158
+ threadTs: threadId,
52159
+ filePath,
52160
+ filename,
52161
+ caption: options?.caption
52162
+ });
52163
+ return { postId: result.postId };
52164
+ }
51962
52165
  }
51963
52166
  // src/session/manager.ts
51964
52167
  import { EventEmitter as EventEmitter4 } from "events";
@@ -51968,7 +52171,7 @@ init_logger();
51968
52171
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync, chmodSync as chmodSync2 } from "fs";
51969
52172
  import { homedir as homedir2 } from "os";
51970
52173
  import { join as join3 } from "path";
51971
- var log4 = createLogger("persist");
52174
+ var log6 = createLogger("persist");
51972
52175
  var STORE_VERSION = 2;
51973
52176
  var DEFAULT_CONFIG_DIR = join3(homedir2(), ".config", "claude-threads");
51974
52177
  var DEFAULT_SESSIONS_FILE = join3(DEFAULT_CONFIG_DIR, "sessions.json");
@@ -51993,13 +52196,13 @@ class SessionStore {
51993
52196
  load() {
51994
52197
  const sessions = new Map;
51995
52198
  if (!existsSync5(this.sessionsFile)) {
51996
- log4.debug("No sessions file found");
52199
+ log6.debug("No sessions file found");
51997
52200
  return sessions;
51998
52201
  }
51999
52202
  try {
52000
52203
  const data = this.loadRaw();
52001
52204
  if (data.version === 1) {
52002
- log4.info("Migrating sessions from v1 to v2 (adding platformId)");
52205
+ log6.info("Migrating sessions from v1 to v2 (adding platformId)");
52003
52206
  const newSessions = {};
52004
52207
  for (const [_oldKey, session] of Object.entries(data.sessions)) {
52005
52208
  const v1Session = session;
@@ -52013,7 +52216,7 @@ class SessionStore {
52013
52216
  data.version = 2;
52014
52217
  this.writeAtomic(data);
52015
52218
  } else if (data.version !== STORE_VERSION) {
52016
- log4.warn(`Sessions file version ${data.version} not supported, starting fresh`);
52219
+ log6.warn(`Sessions file version ${data.version} not supported, starting fresh`);
52017
52220
  return sessions;
52018
52221
  }
52019
52222
  for (const session of Object.values(data.sessions)) {
@@ -52022,9 +52225,9 @@ class SessionStore {
52022
52225
  const sessionId = `${session.platformId}:${session.threadId}`;
52023
52226
  sessions.set(sessionId, session);
52024
52227
  }
52025
- log4.debug(`Loaded ${sessions.size} active session(s)`);
52228
+ log6.debug(`Loaded ${sessions.size} active session(s)`);
52026
52229
  } catch (err) {
52027
- log4.error(`Failed to load sessions: ${err}`);
52230
+ log6.error(`Failed to load sessions: ${err}`);
52028
52231
  }
52029
52232
  return sessions;
52030
52233
  }
@@ -52033,7 +52236,7 @@ class SessionStore {
52033
52236
  data.sessions[sessionId] = session;
52034
52237
  this.writeAtomic(data);
52035
52238
  const shortId = sessionId.substring(0, 20);
52036
- log4.debug(`Saved session ${shortId}...`);
52239
+ log6.debug(`Saved session ${shortId}...`);
52037
52240
  }
52038
52241
  remove(sessionId) {
52039
52242
  const data = this.loadRaw();
@@ -52041,7 +52244,7 @@ class SessionStore {
52041
52244
  delete data.sessions[sessionId];
52042
52245
  this.writeAtomic(data);
52043
52246
  const shortId = sessionId.substring(0, 20);
52044
- log4.debug(`Removed session ${shortId}...`);
52247
+ log6.debug(`Removed session ${shortId}...`);
52045
52248
  }
52046
52249
  }
52047
52250
  softDelete(sessionId) {
@@ -52050,7 +52253,7 @@ class SessionStore {
52050
52253
  data.sessions[sessionId].cleanedAt = new Date().toISOString();
52051
52254
  this.writeAtomic(data);
52052
52255
  const shortId = sessionId.substring(0, 20);
52053
- log4.debug(`Soft-deleted session ${shortId}...`);
52256
+ log6.debug(`Soft-deleted session ${shortId}...`);
52054
52257
  }
52055
52258
  }
52056
52259
  cleanStale(maxAgeMs) {
@@ -52068,7 +52271,7 @@ class SessionStore {
52068
52271
  }
52069
52272
  if (staleIds.length > 0) {
52070
52273
  this.writeAtomic(data);
52071
- log4.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
52274
+ log6.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
52072
52275
  }
52073
52276
  return staleIds;
52074
52277
  }
@@ -52087,7 +52290,7 @@ class SessionStore {
52087
52290
  }
52088
52291
  if (removedCount > 0) {
52089
52292
  this.writeAtomic(data);
52090
- log4.debug(`Permanently removed ${removedCount} old session(s) from history`);
52293
+ log6.debug(`Permanently removed ${removedCount} old session(s) from history`);
52091
52294
  }
52092
52295
  return removedCount;
52093
52296
  }
@@ -52114,7 +52317,7 @@ class SessionStore {
52114
52317
  clear() {
52115
52318
  const data = this.loadRaw();
52116
52319
  this.writeAtomic({ version: STORE_VERSION, sessions: {}, stickyPostIds: data.stickyPostIds });
52117
- log4.debug("Cleared all sessions");
52320
+ log6.debug("Cleared all sessions");
52118
52321
  }
52119
52322
  saveStickyPostId(platformId, postId) {
52120
52323
  const data = this.loadRaw();
@@ -52123,7 +52326,7 @@ class SessionStore {
52123
52326
  }
52124
52327
  data.stickyPostIds[platformId] = postId;
52125
52328
  this.writeAtomic(data);
52126
- log4.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
52329
+ log6.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
52127
52330
  }
52128
52331
  getStickyPostIds() {
52129
52332
  const data = this.loadRaw();
@@ -52134,7 +52337,7 @@ class SessionStore {
52134
52337
  if (data.stickyPostIds && data.stickyPostIds[platformId]) {
52135
52338
  delete data.stickyPostIds[platformId];
52136
52339
  this.writeAtomic(data);
52137
- log4.debug(`Removed sticky post ID for ${platformId}`);
52340
+ log6.debug(`Removed sticky post ID for ${platformId}`);
52138
52341
  }
52139
52342
  }
52140
52343
  getPlatformEnabledState() {
@@ -52152,7 +52355,7 @@ class SessionStore {
52152
52355
  }
52153
52356
  data.platformEnabledState[platformId] = enabled;
52154
52357
  this.writeAtomic(data);
52155
- log4.debug(`Set platform ${platformId} enabled state to ${enabled}`);
52358
+ log6.debug(`Set platform ${platformId} enabled state to ${enabled}`);
52156
52359
  }
52157
52360
  findByThread(platformId, threadId) {
52158
52361
  const sessionId = `${platformId}:${threadId}`;
@@ -52206,7 +52409,7 @@ class SessionStore {
52206
52409
 
52207
52410
  // src/claude/account-pool.ts
52208
52411
  init_logger();
52209
- var log5 = createLogger("account-pool");
52412
+ var log7 = createLogger("account-pool");
52210
52413
  function hashThreadId(threadId) {
52211
52414
  let h = 2166136261;
52212
52415
  for (let i2 = 0;i2 < threadId.length; i2++) {
@@ -52226,11 +52429,11 @@ class AccountPool {
52226
52429
  this.accounts = (accounts ?? []).filter((acc) => {
52227
52430
  const hasAuth = !!acc.home || !!acc.apiKey;
52228
52431
  if (!hasAuth) {
52229
- log5.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
52432
+ log7.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
52230
52433
  return false;
52231
52434
  }
52232
52435
  if (acc.home && acc.apiKey) {
52233
- log5.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
52436
+ log7.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
52234
52437
  return false;
52235
52438
  }
52236
52439
  return true;
@@ -52255,7 +52458,7 @@ class AccountPool {
52255
52458
  this.incrementActive(preferred.id);
52256
52459
  return preferred;
52257
52460
  }
52258
- log5.warn(`Preferred account "${preferredId}" not in pool — falling back to round-robin`);
52461
+ log7.warn(`Preferred account "${preferredId}" not in pool — falling back to round-robin`);
52259
52462
  }
52260
52463
  const now = Date.now();
52261
52464
  const n = this.accounts.length;
@@ -52277,7 +52480,7 @@ class AccountPool {
52277
52480
  return candidate;
52278
52481
  }
52279
52482
  }
52280
- log5.warn(`All ${n} accounts are in rate-limit cooldown`);
52483
+ log7.warn(`All ${n} accounts are in rate-limit cooldown`);
52281
52484
  return null;
52282
52485
  }
52283
52486
  release(accountId) {
@@ -52288,14 +52491,14 @@ class AccountPool {
52288
52491
  }
52289
52492
  markCooling(accountId, untilEpochMs) {
52290
52493
  if (!this.byId.has(accountId)) {
52291
- log5.warn(`markCooling called for unknown account "${accountId}"`);
52494
+ log7.warn(`markCooling called for unknown account "${accountId}"`);
52292
52495
  return;
52293
52496
  }
52294
52497
  const existing = this.coolingUntil.get(accountId) ?? 0;
52295
52498
  if (untilEpochMs > existing) {
52296
52499
  this.coolingUntil.set(accountId, untilEpochMs);
52297
52500
  const minutes = Math.ceil((untilEpochMs - Date.now()) / 60000);
52298
- log5.info(`Account "${accountId}" cooling for ~${minutes}min`);
52501
+ log7.info(`Account "${accountId}" cooling for ~${minutes}min`);
52299
52502
  }
52300
52503
  }
52301
52504
  get(accountId) {
@@ -52329,7 +52532,7 @@ init_logger();
52329
52532
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, appendFileSync, readdirSync, statSync, unlinkSync, rmdirSync, readFileSync as readFileSync5, chmodSync as chmodSync3 } from "fs";
52330
52533
  import { homedir as homedir3 } from "os";
52331
52534
  import { join as join4, dirname as dirname4 } from "path";
52332
- var log6 = createLogger("thread-log");
52535
+ var log8 = createLogger("thread-log");
52333
52536
  var LOGS_BASE_DIR = join4(homedir3(), ".claude-threads", "logs");
52334
52537
 
52335
52538
  class ThreadLoggerImpl {
@@ -52359,7 +52562,7 @@ class ThreadLoggerImpl {
52359
52562
  this.flushTimer = setInterval(() => {
52360
52563
  this.flushSync();
52361
52564
  }, this.flushIntervalMs);
52362
- log6.debug(`Thread logger initialized: ${this.logPath}`);
52565
+ log8.debug(`Thread logger initialized: ${this.logPath}`);
52363
52566
  }
52364
52567
  }
52365
52568
  isEnabled() {
@@ -52473,7 +52676,7 @@ class ThreadLoggerImpl {
52473
52676
  this.flushTimer = null;
52474
52677
  }
52475
52678
  this.flushSync();
52476
- log6.debug(`Thread logger closed: ${this.logPath}`);
52679
+ log8.debug(`Thread logger closed: ${this.logPath}`);
52477
52680
  }
52478
52681
  addEntry(entry) {
52479
52682
  this.buffer.push(entry);
@@ -52495,7 +52698,7 @@ class ThreadLoggerImpl {
52495
52698
  }
52496
52699
  this.buffer = [];
52497
52700
  } catch (err) {
52498
- log6.error(`Failed to flush thread log: ${err}`);
52701
+ log8.error(`Failed to flush thread log: ${err}`);
52499
52702
  }
52500
52703
  }
52501
52704
  }
@@ -52546,25 +52749,25 @@ function cleanupOldLogs(retentionDays = 30) {
52546
52749
  if (fileStat.mtimeMs < cutoffMs) {
52547
52750
  unlinkSync(filePath);
52548
52751
  deletedCount++;
52549
- log6.debug(`Deleted old log file: ${filePath}`);
52752
+ log8.debug(`Deleted old log file: ${filePath}`);
52550
52753
  }
52551
52754
  } catch (err) {
52552
- log6.warn(`Failed to check/delete log file ${filePath}: ${err}`);
52755
+ log8.warn(`Failed to check/delete log file ${filePath}: ${err}`);
52553
52756
  }
52554
52757
  }
52555
52758
  try {
52556
52759
  const remaining = readdirSync(platformDir);
52557
52760
  if (remaining.length === 0) {
52558
52761
  rmdirSync(platformDir);
52559
- log6.debug(`Removed empty platform log directory: ${platformDir}`);
52762
+ log8.debug(`Removed empty platform log directory: ${platformDir}`);
52560
52763
  }
52561
52764
  } catch {}
52562
52765
  }
52563
52766
  if (deletedCount > 0) {
52564
- log6.info(`Cleaned up ${deletedCount} old log file(s)`);
52767
+ log8.info(`Cleaned up ${deletedCount} old log file(s)`);
52565
52768
  }
52566
52769
  } catch (err) {
52567
- log6.error(`Failed to clean up old logs: ${err}`);
52770
+ log8.error(`Failed to clean up old logs: ${err}`);
52568
52771
  }
52569
52772
  return deletedCount;
52570
52773
  }
@@ -52573,16 +52776,16 @@ function getLogFilePath(platformId, sessionId) {
52573
52776
  }
52574
52777
  function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
52575
52778
  const logPath = getLogFilePath(platformId, sessionId);
52576
- log6.debug(`Reading log entries from: ${logPath}`);
52779
+ log8.debug(`Reading log entries from: ${logPath}`);
52577
52780
  if (!existsSync6(logPath)) {
52578
- log6.debug(`Log file does not exist: ${logPath}`);
52781
+ log8.debug(`Log file does not exist: ${logPath}`);
52579
52782
  return [];
52580
52783
  }
52581
52784
  try {
52582
52785
  const content = readFileSync5(logPath, "utf8");
52583
52786
  const lines = content.trim().split(`
52584
52787
  `);
52585
- log6.debug(`Log file has ${lines.length} lines`);
52788
+ log8.debug(`Log file has ${lines.length} lines`);
52586
52789
  const recentLines = lines.slice(-maxLines);
52587
52790
  const entries = [];
52588
52791
  for (const line of recentLines) {
@@ -52592,17 +52795,17 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
52592
52795
  entries.push(JSON.parse(line));
52593
52796
  } catch {}
52594
52797
  }
52595
- log6.debug(`Parsed ${entries.length} log entries`);
52798
+ log8.debug(`Parsed ${entries.length} log entries`);
52596
52799
  return entries;
52597
52800
  } catch (err) {
52598
- log6.error(`Failed to read log file: ${err}`);
52801
+ log8.error(`Failed to read log file: ${err}`);
52599
52802
  return [];
52600
52803
  }
52601
52804
  }
52602
52805
 
52603
52806
  // src/cleanup/scheduler.ts
52604
52807
  init_worktree();
52605
- var log8 = createLogger("cleanup");
52808
+ var log10 = createLogger("cleanup");
52606
52809
  var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
52607
52810
  var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
52608
52811
 
@@ -52625,17 +52828,17 @@ class CleanupScheduler {
52625
52828
  }
52626
52829
  start() {
52627
52830
  if (this.isRunning) {
52628
- log8.debug("Cleanup scheduler already running");
52831
+ log10.debug("Cleanup scheduler already running");
52629
52832
  return;
52630
52833
  }
52631
52834
  this.isRunning = true;
52632
- log8.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
52835
+ log10.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
52633
52836
  this.runCleanup().catch((err) => {
52634
- log8.warn(`Initial cleanup failed: ${err}`);
52837
+ log10.warn(`Initial cleanup failed: ${err}`);
52635
52838
  });
52636
52839
  this.timer = setInterval(() => {
52637
52840
  this.runCleanup().catch((err) => {
52638
- log8.warn(`Periodic cleanup failed: ${err}`);
52841
+ log10.warn(`Periodic cleanup failed: ${err}`);
52639
52842
  });
52640
52843
  }, this.intervalMs);
52641
52844
  }
@@ -52645,11 +52848,11 @@ class CleanupScheduler {
52645
52848
  this.timer = null;
52646
52849
  }
52647
52850
  this.isRunning = false;
52648
- log8.debug("Cleanup scheduler stopped");
52851
+ log10.debug("Cleanup scheduler stopped");
52649
52852
  }
52650
52853
  async runCleanup() {
52651
52854
  const startTime = Date.now();
52652
- log8.debug("Running background cleanup...");
52855
+ log10.debug("Running background cleanup...");
52653
52856
  const stats = {
52654
52857
  logsDeleted: 0,
52655
52858
  worktreesCleaned: 0,
@@ -52675,9 +52878,9 @@ class CleanupScheduler {
52675
52878
  const elapsed = Date.now() - startTime;
52676
52879
  const totalCleaned = stats.logsDeleted + stats.worktreesCleaned + stats.metadataCleaned;
52677
52880
  if (totalCleaned > 0 || stats.errors.length > 0) {
52678
- log8.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
52881
+ log10.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
52679
52882
  } else {
52680
- log8.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
52883
+ log10.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
52681
52884
  }
52682
52885
  return stats;
52683
52886
  }
@@ -52690,7 +52893,7 @@ class CleanupScheduler {
52690
52893
  const deleted = cleanupOldLogs(this.logRetentionDays);
52691
52894
  resolve3(deleted);
52692
52895
  } catch (err) {
52693
- log8.warn(`Log cleanup error: ${err}`);
52896
+ log10.warn(`Log cleanup error: ${err}`);
52694
52897
  resolve3(0);
52695
52898
  }
52696
52899
  });
@@ -52699,7 +52902,7 @@ class CleanupScheduler {
52699
52902
  const worktreesDir = getWorktreesDir();
52700
52903
  const result = { cleaned: 0, metadata: 0 };
52701
52904
  if (!existsSync7(worktreesDir)) {
52702
- log8.debug("No worktrees directory exists, nothing to clean");
52905
+ log10.debug("No worktrees directory exists, nothing to clean");
52703
52906
  return result;
52704
52907
  }
52705
52908
  const persisted = this.sessionStore.load();
@@ -52717,7 +52920,7 @@ class CleanupScheduler {
52717
52920
  continue;
52718
52921
  const worktreePath = join6(worktreesDir, entry.name);
52719
52922
  if (activeWorktrees.has(worktreePath)) {
52720
- log8.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
52923
+ log10.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
52721
52924
  continue;
52722
52925
  }
52723
52926
  const meta = await readWorktreeMetadata(worktreePath);
@@ -52727,7 +52930,7 @@ class CleanupScheduler {
52727
52930
  const lastActivity = new Date(meta.lastActivityAt).getTime();
52728
52931
  const age = now - lastActivity;
52729
52932
  if (meta.sessionId && age < this.maxWorktreeAgeMs) {
52730
- log8.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
52933
+ log10.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
52731
52934
  continue;
52732
52935
  }
52733
52936
  const merged = age >= this.maxWorktreeAgeMs ? await isBranchMerged(meta.repoRoot, meta.branch).catch(() => false) : false;
@@ -52738,7 +52941,7 @@ class CleanupScheduler {
52738
52941
  shouldCleanup = true;
52739
52942
  cleanupReason = `inactive for ${Math.round(age / 3600000)}h`;
52740
52943
  } else {
52741
- log8.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
52944
+ log10.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
52742
52945
  continue;
52743
52946
  }
52744
52947
  } else {
@@ -52747,7 +52950,7 @@ class CleanupScheduler {
52747
52950
  }
52748
52951
  if (!shouldCleanup)
52749
52952
  continue;
52750
- log8.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
52953
+ log10.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
52751
52954
  try {
52752
52955
  if (meta?.repoRoot) {
52753
52956
  await removeWorktree(meta.repoRoot, worktreePath);
@@ -52758,19 +52961,19 @@ class CleanupScheduler {
52758
52961
  await removeWorktreeMetadata(worktreePath);
52759
52962
  result.metadata++;
52760
52963
  } catch (err) {
52761
- log8.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
52964
+ log10.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
52762
52965
  try {
52763
52966
  await rm(worktreePath, { recursive: true, force: true });
52764
52967
  result.cleaned++;
52765
52968
  await removeWorktreeMetadata(worktreePath);
52766
52969
  result.metadata++;
52767
52970
  } catch (rmErr) {
52768
- log8.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
52971
+ log10.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
52769
52972
  }
52770
52973
  }
52771
52974
  }
52772
52975
  } catch (err) {
52773
- log8.warn(`Failed to scan worktrees directory: ${err}`);
52976
+ log10.warn(`Failed to scan worktrees directory: ${err}`);
52774
52977
  }
52775
52978
  return result;
52776
52979
  }
@@ -52780,7 +52983,7 @@ init_logger();
52780
52983
 
52781
52984
  // src/session/lifecycle-fsm.ts
52782
52985
  init_logger();
52783
- var log9 = createLogger("fsm");
52986
+ var log11 = createLogger("fsm");
52784
52987
  var ALLOWED_TRANSITIONS = {
52785
52988
  starting: new Set(["active", "paused", "interrupted", "cancelling", "restarting"]),
52786
52989
  active: new Set([
@@ -52819,7 +53022,7 @@ function checkTransition(from, to, sessionId) {
52819
53022
  if (process.env.CLAUDE_THREADS_FSM_STRICT === "1") {
52820
53023
  throw new Error(`${msg} (sessionId=${sessionId})`);
52821
53024
  }
52822
- log9.warn(msg, payload);
53025
+ log11.warn(msg, payload);
52823
53026
  }
52824
53027
 
52825
53028
  // src/session/timer-manager.ts
@@ -52893,6 +53096,14 @@ import { existsSync as existsSync8, readFileSync as readFileSync6, watchFile, un
52893
53096
  import { tmpdir } from "os";
52894
53097
  import { join as join7 } from "path";
52895
53098
 
53099
+ // src/mcp/outbound-env.ts
53100
+ var OUTBOUND_ENV = {
53101
+ SESSION_WORKING_DIR: "SESSION_WORKING_DIR",
53102
+ SESSION_UPLOAD_DIR: "SESSION_UPLOAD_DIR",
53103
+ OUTBOUND_FILES_ENABLED: "OUTBOUND_FILES_ENABLED",
53104
+ OUTBOUND_FILES_MAX_BYTES: "OUTBOUND_FILES_MAX_BYTES"
53105
+ };
53106
+
52896
53107
  // src/claude/rate-limit-detector.ts
52897
53108
  var RATE_LIMIT_PHRASES = [
52898
53109
  /usage limit reached/i,
@@ -52955,7 +53166,7 @@ function extractResetAt(text, now) {
52955
53166
  }
52956
53167
 
52957
53168
  // src/claude/cli.ts
52958
- var log10 = createLogger("claude");
53169
+ var log12 = createLogger("claude");
52959
53170
  function cleanupBrowserBridgeSockets() {
52960
53171
  try {
52961
53172
  const tempDir = tmpdir();
@@ -52967,13 +53178,13 @@ function cleanupBrowserBridgeSockets() {
52967
53178
  const stats = statSync2(filePath);
52968
53179
  if (stats.isSocket()) {
52969
53180
  unlinkSync2(filePath);
52970
- log10.debug(`Removed stale browser bridge socket: ${file}`);
53181
+ log12.debug(`Removed stale browser bridge socket: ${file}`);
52971
53182
  }
52972
53183
  } catch {}
52973
53184
  }
52974
53185
  }
52975
53186
  } catch (err) {
52976
- log10.debug(`Browser bridge cleanup failed: ${err}`);
53187
+ log12.debug(`Browser bridge cleanup failed: ${err}`);
52977
53188
  }
52978
53189
  }
52979
53190
  function buildClaudeChildEnv(parentEnv, account) {
@@ -53014,7 +53225,7 @@ function materializeMcpConfig(config, sessionId, opts = {}) {
53014
53225
  }
53015
53226
  function buildPermissionArgs(opts) {
53016
53227
  const args = [];
53017
- if (opts.permissionMode === "bypass") {
53228
+ if (opts.permissionMode === "bypass" && !opts.platformConfig) {
53018
53229
  args.push("--dangerously-skip-permissions");
53019
53230
  return { args, tempFile: null };
53020
53231
  }
@@ -53034,6 +53245,18 @@ function buildPermissionArgs(opts) {
53034
53245
  if (opts.platformConfig.appToken) {
53035
53246
  mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
53036
53247
  }
53248
+ if (opts.workingDir) {
53249
+ mcpEnv[OUTBOUND_ENV.SESSION_WORKING_DIR] = opts.workingDir;
53250
+ }
53251
+ if (opts.uploadDir) {
53252
+ mcpEnv[OUTBOUND_ENV.SESSION_UPLOAD_DIR] = opts.uploadDir;
53253
+ }
53254
+ if (opts.outboundFiles?.enabled === false) {
53255
+ mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_ENABLED] = "0";
53256
+ }
53257
+ if (typeof opts.outboundFiles?.maxBytes === "number" && Number.isFinite(opts.outboundFiles.maxBytes) && opts.outboundFiles.maxBytes > 0) {
53258
+ mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_MAX_BYTES] = String(opts.outboundFiles.maxBytes);
53259
+ }
53037
53260
  const mcpConfig = {
53038
53261
  mcpServers: {
53039
53262
  "claude-threads-permissions": {
@@ -53052,9 +53275,13 @@ function buildPermissionArgs(opts) {
53052
53275
  } else {
53053
53276
  args.push("--mcp-config", materialized.value);
53054
53277
  }
53055
- args.push("--permission-prompt-tool", "mcp__claude-threads-permissions__permission_prompt");
53056
- if (opts.permissionMode === "auto") {
53057
- args.push("--permission-mode", "auto");
53278
+ if (opts.permissionMode === "bypass") {
53279
+ args.push("--dangerously-skip-permissions");
53280
+ } else {
53281
+ args.push("--permission-prompt-tool", "mcp__claude-threads-permissions__permission_prompt");
53282
+ if (opts.permissionMode === "auto") {
53283
+ args.push("--permission-mode", "auto");
53284
+ }
53058
53285
  }
53059
53286
  return { args, tempFile };
53060
53287
  }
@@ -53149,7 +53376,10 @@ class ClaudeCli extends EventEmitter2 {
53149
53376
  threadId: this.options.threadId,
53150
53377
  sessionId: this.options.sessionId,
53151
53378
  permissionTimeoutMs: this.options.permissionTimeoutMs ?? 120000,
53152
- debug: this.debug
53379
+ debug: this.debug,
53380
+ workingDir: this.options.workingDir,
53381
+ uploadDir: this.options.uploadDir,
53382
+ outboundFiles: this.options.outboundFiles
53153
53383
  });
53154
53384
  args.push(...permResult.args);
53155
53385
  this.mcpConfigTempFile = permResult.tempFile;
@@ -54179,6 +54409,15 @@ You are running inside a chat platform (like Mattermost or Slack). Users interac
54179
54409
  - Keep responses concise - very long responses are split across multiple messages
54180
54410
  - Multiple users may participate in a session (the owner can invite others)
54181
54411
 
54412
+ ## Sending files into THIS thread
54413
+ You are RIGHT NOW running inside a chat thread (Mattermost or Slack). The \`send_file\` MCP tool — exposed as \`mcp__claude-threads-permissions__send_file\` in your tool list — uploads a file from your working directory and posts it directly into THIS thread, where the user is talking to you. It is NOT a hypothetical capability that requires extra setup; it works for the session you are in right now.
54414
+
54415
+ Use it whenever the user asks to "send", "share", "show", or "post" a file, OR whenever you produce an artifact (screenshot, generated audio, plot, document, PDF) that the user would benefit from seeing inline rather than as a path to read.
54416
+
54417
+ Arguments: \`{ path: <absolute path inside the working directory>, caption?: <optional one-line message> }\`. Returns a JSON envelope: \`{ ok: true, postId }\` on success or \`{ ok: false, reason }\` on failure — when it fails, surface \`reason\` to the user verbatim so they understand what went wrong (e.g. "outside the working directory", "file too large").
54418
+
54419
+ Do NOT tell the user the tool isn't available, doesn't apply, or requires Mattermost — it's wired up and pointed at this very thread. Just call it.
54420
+
54182
54421
  ## Permissions & Interactions
54183
54422
  - Permission requests (file writes, commands, etc.) appear as messages with emoji options
54184
54423
  - Users approve with \uD83D\uDC4D or deny with \uD83D\uDC4E by reacting to the message
@@ -54210,7 +54449,7 @@ import { existsSync as existsSync11 } from "fs";
54210
54449
  // src/utils/keep-alive.ts
54211
54450
  init_logger();
54212
54451
  import { spawn as spawn2 } from "child_process";
54213
- var log11 = createLogger("keepalive");
54452
+ var log13 = createLogger("keepalive");
54214
54453
 
54215
54454
  class KeepAliveManager {
54216
54455
  activeSessionCount = 0;
@@ -54225,7 +54464,7 @@ class KeepAliveManager {
54225
54464
  if (!enabled && this.keepAliveProcess) {
54226
54465
  this.stopKeepAlive();
54227
54466
  }
54228
- log11.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
54467
+ log13.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
54229
54468
  }
54230
54469
  isEnabled() {
54231
54470
  return this.enabled;
@@ -54235,7 +54474,7 @@ class KeepAliveManager {
54235
54474
  }
54236
54475
  sessionStarted() {
54237
54476
  this.activeSessionCount++;
54238
- log11.debug(`Session started (${this.activeSessionCount} active)`);
54477
+ log13.debug(`Session started (${this.activeSessionCount} active)`);
54239
54478
  if (this.activeSessionCount === 1) {
54240
54479
  this.startKeepAlive();
54241
54480
  }
@@ -54244,7 +54483,7 @@ class KeepAliveManager {
54244
54483
  if (this.activeSessionCount > 0) {
54245
54484
  this.activeSessionCount--;
54246
54485
  }
54247
- log11.debug(`Session ended (${this.activeSessionCount} active)`);
54486
+ log13.debug(`Session ended (${this.activeSessionCount} active)`);
54248
54487
  if (this.activeSessionCount === 0) {
54249
54488
  this.stopKeepAlive();
54250
54489
  }
@@ -54258,11 +54497,11 @@ class KeepAliveManager {
54258
54497
  }
54259
54498
  startKeepAlive() {
54260
54499
  if (!this.enabled) {
54261
- log11.debug("Keep-alive disabled, skipping");
54500
+ log13.debug("Keep-alive disabled, skipping");
54262
54501
  return;
54263
54502
  }
54264
54503
  if (this.keepAliveProcess) {
54265
- log11.debug("Keep-alive already running");
54504
+ log13.debug("Keep-alive already running");
54266
54505
  return;
54267
54506
  }
54268
54507
  switch (this.platform) {
@@ -54276,12 +54515,12 @@ class KeepAliveManager {
54276
54515
  this.startWindowsKeepAlive();
54277
54516
  break;
54278
54517
  default:
54279
- log11.warn(`Keep-alive not supported on ${this.platform}`);
54518
+ log13.warn(`Keep-alive not supported on ${this.platform}`);
54280
54519
  }
54281
54520
  }
54282
54521
  stopKeepAlive() {
54283
54522
  if (this.keepAliveProcess) {
54284
- log11.debug("Stopping keep-alive");
54523
+ log13.debug("Stopping keep-alive");
54285
54524
  this.keepAliveProcess.kill();
54286
54525
  this.keepAliveProcess = null;
54287
54526
  }
@@ -54293,18 +54532,18 @@ class KeepAliveManager {
54293
54532
  detached: false
54294
54533
  });
54295
54534
  this.keepAliveProcess.on("error", (err) => {
54296
- log11.error(`Failed to start caffeinate: ${err.message}`);
54535
+ log13.error(`Failed to start caffeinate: ${err.message}`);
54297
54536
  this.keepAliveProcess = null;
54298
54537
  });
54299
54538
  this.keepAliveProcess.on("exit", (code) => {
54300
54539
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
54301
- log11.debug(`caffeinate exited with code ${code}`);
54540
+ log13.debug(`caffeinate exited with code ${code}`);
54302
54541
  }
54303
54542
  this.keepAliveProcess = null;
54304
54543
  });
54305
- log11.info("Sleep prevention active (caffeinate)");
54544
+ log13.info("Sleep prevention active (caffeinate)");
54306
54545
  } catch (err) {
54307
- log11.error(`Failed to start caffeinate: ${err}`);
54546
+ log13.error(`Failed to start caffeinate: ${err}`);
54308
54547
  }
54309
54548
  }
54310
54549
  startLinuxKeepAlive() {
@@ -54320,19 +54559,19 @@ class KeepAliveManager {
54320
54559
  detached: false
54321
54560
  });
54322
54561
  this.keepAliveProcess.on("error", (err) => {
54323
- log11.debug(`systemd-inhibit not available: ${err.message}`);
54562
+ log13.debug(`systemd-inhibit not available: ${err.message}`);
54324
54563
  this.keepAliveProcess = null;
54325
54564
  this.startLinuxKeepAliveFallback();
54326
54565
  });
54327
54566
  this.keepAliveProcess.on("exit", (code) => {
54328
54567
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
54329
- log11.debug(`systemd-inhibit exited with code ${code}`);
54568
+ log13.debug(`systemd-inhibit exited with code ${code}`);
54330
54569
  }
54331
54570
  this.keepAliveProcess = null;
54332
54571
  });
54333
- log11.info("Sleep prevention active (systemd-inhibit)");
54572
+ log13.info("Sleep prevention active (systemd-inhibit)");
54334
54573
  } catch (err) {
54335
- log11.debug(`Failed to start systemd-inhibit: ${err}`);
54574
+ log13.debug(`Failed to start systemd-inhibit: ${err}`);
54336
54575
  this.startLinuxKeepAliveFallback();
54337
54576
  }
54338
54577
  }
@@ -54346,15 +54585,15 @@ class KeepAliveManager {
54346
54585
  detached: false
54347
54586
  });
54348
54587
  this.keepAliveProcess.on("error", (err) => {
54349
- log11.warn(`Linux keep-alive fallback not available: ${err.message}`);
54588
+ log13.warn(`Linux keep-alive fallback not available: ${err.message}`);
54350
54589
  this.keepAliveProcess = null;
54351
54590
  });
54352
54591
  this.keepAliveProcess.on("exit", () => {
54353
54592
  this.keepAliveProcess = null;
54354
54593
  });
54355
- log11.info("Sleep prevention active (xdg-screensaver)");
54594
+ log13.info("Sleep prevention active (xdg-screensaver)");
54356
54595
  } catch (err) {
54357
- log11.warn(`Linux keep-alive not available: ${err}`);
54596
+ log13.warn(`Linux keep-alive not available: ${err}`);
54358
54597
  }
54359
54598
  }
54360
54599
  startWindowsKeepAlive() {
@@ -54379,18 +54618,18 @@ class KeepAliveManager {
54379
54618
  windowsHide: true
54380
54619
  });
54381
54620
  this.keepAliveProcess.on("error", (err) => {
54382
- log11.warn(`Windows keep-alive not available: ${err.message}`);
54621
+ log13.warn(`Windows keep-alive not available: ${err.message}`);
54383
54622
  this.keepAliveProcess = null;
54384
54623
  });
54385
54624
  this.keepAliveProcess.on("exit", (code) => {
54386
54625
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
54387
- log11.debug(`PowerShell keep-alive exited with code ${code}`);
54626
+ log13.debug(`PowerShell keep-alive exited with code ${code}`);
54388
54627
  }
54389
54628
  this.keepAliveProcess = null;
54390
54629
  });
54391
- log11.info("Sleep prevention active (SetThreadExecutionState)");
54630
+ log13.info("Sleep prevention active (SetThreadExecutionState)");
54392
54631
  } catch (err) {
54393
- log11.warn(`Windows keep-alive not available: ${err}`);
54632
+ log13.warn(`Windows keep-alive not available: ${err}`);
54394
54633
  }
54395
54634
  }
54396
54635
  }
@@ -54398,7 +54637,7 @@ var keepAlive = new KeepAliveManager;
54398
54637
 
54399
54638
  // src/utils/error-handler/index.ts
54400
54639
  init_logger();
54401
- var log12 = createLogger("error");
54640
+ var log14 = createLogger("error");
54402
54641
 
54403
54642
  class SessionError extends Error {
54404
54643
  sessionId;
@@ -54424,19 +54663,19 @@ async function handleError(error, context, severity = "recoverable") {
54424
54663
  const sessionPart = sessionId ? ` (${formatShortId(sessionId)})` : "";
54425
54664
  const logMessage = `${context.action}${sessionPart}: ${message}`;
54426
54665
  if (severity === "recoverable") {
54427
- log12.warn(logMessage);
54666
+ log14.warn(logMessage);
54428
54667
  } else {
54429
- log12.error(logMessage, error instanceof Error ? error : undefined);
54668
+ log14.error(logMessage, error instanceof Error ? error : undefined);
54430
54669
  }
54431
54670
  if (context.details) {
54432
- log12.debugJson("Error details", context.details);
54671
+ log14.debugJson("Error details", context.details);
54433
54672
  }
54434
54673
  if (context.notifyUser && context.session) {
54435
54674
  try {
54436
54675
  const fmt = context.session.platform.getFormatter();
54437
54676
  await context.session.platform.createPost(`⚠️ ${fmt.formatBold("Error")}: ${context.action} failed - ${message}`, context.session.threadId);
54438
54677
  } catch (notifyError) {
54439
- log12.warn(`Could not notify user: ${notifyError}`);
54678
+ log14.warn(`Could not notify user: ${notifyError}`);
54440
54679
  }
54441
54680
  }
54442
54681
  if (severity === "session-fatal" || severity === "system-fatal") {
@@ -54463,7 +54702,7 @@ async function logAndNotify(error, context) {
54463
54702
  }
54464
54703
  function logSilentError(context, error) {
54465
54704
  const message = error instanceof Error ? error.message : String(error);
54466
- log12.debug(`[${context}] Silently caught: ${message}`);
54705
+ log14.debug(`[${context}] Silently caught: ${message}`);
54467
54706
  }
54468
54707
 
54469
54708
  // src/session/lifecycle.ts
@@ -54483,8 +54722,8 @@ function createSessionLog(baseLog) {
54483
54722
  init_logger();
54484
54723
  init_emoji();
54485
54724
  init_worktree();
54486
- var log13 = createLogger("helpers");
54487
- var sessionLog = createSessionLog(log13);
54725
+ var log15 = createLogger("helpers");
54726
+ var sessionLog = createSessionLog(log15);
54488
54727
  var POST_TYPES = {
54489
54728
  info: "",
54490
54729
  success: "✅",
@@ -54568,7 +54807,7 @@ function updateLastMessage(session, post2) {
54568
54807
  // src/claude/quick-query.ts
54569
54808
  init_spawn();
54570
54809
  init_logger();
54571
- var log14 = createLogger("query");
54810
+ var log16 = createLogger("query");
54572
54811
  async function quickQuery(options) {
54573
54812
  const {
54574
54813
  prompt,
@@ -54584,7 +54823,7 @@ async function quickQuery(options) {
54584
54823
  args.push("--system-prompt", systemPrompt);
54585
54824
  }
54586
54825
  args.push(prompt);
54587
- log14.debug(`Quick query: model=${model}, timeout=${timeout}ms, prompt="${prompt.substring(0, 50)}..."`);
54826
+ log16.debug(`Quick query: model=${model}, timeout=${timeout}ms, prompt="${prompt.substring(0, 50)}..."`);
54588
54827
  return new Promise((resolve5) => {
54589
54828
  let stdout = "";
54590
54829
  let stderr = "";
@@ -54598,7 +54837,7 @@ async function quickQuery(options) {
54598
54837
  if (!resolved) {
54599
54838
  resolved = true;
54600
54839
  proc.kill("SIGTERM");
54601
- log14.debug(`Quick query timed out after ${timeout}ms`);
54840
+ log16.debug(`Quick query timed out after ${timeout}ms`);
54602
54841
  resolve5({
54603
54842
  success: false,
54604
54843
  error: "timeout",
@@ -54616,7 +54855,7 @@ async function quickQuery(options) {
54616
54855
  if (!resolved) {
54617
54856
  resolved = true;
54618
54857
  clearTimeout(timeoutId);
54619
- log14.debug(`Quick query error: ${err.message}`);
54858
+ log16.debug(`Quick query error: ${err.message}`);
54620
54859
  resolve5({
54621
54860
  success: false,
54622
54861
  error: err.message,
@@ -54630,14 +54869,14 @@ async function quickQuery(options) {
54630
54869
  clearTimeout(timeoutId);
54631
54870
  const durationMs = Date.now() - startTime;
54632
54871
  if (code === 0 && stdout.trim()) {
54633
- log14.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
54872
+ log16.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
54634
54873
  resolve5({
54635
54874
  success: true,
54636
54875
  response: stdout.trim(),
54637
54876
  durationMs
54638
54877
  });
54639
54878
  } else {
54640
- log14.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
54879
+ log16.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
54641
54880
  resolve5({
54642
54881
  success: false,
54643
54882
  error: stderr || `exit code ${code}`,
@@ -54652,7 +54891,7 @@ async function quickQuery(options) {
54652
54891
 
54653
54892
  // src/operations/suggestions/title.ts
54654
54893
  init_logger();
54655
- var log15 = createLogger("title");
54894
+ var log17 = createLogger("title");
54656
54895
  var SUGGESTION_TIMEOUT = 15000;
54657
54896
  var MIN_TITLE_LENGTH = 3;
54658
54897
  var MAX_TITLE_LENGTH = 50;
@@ -54716,32 +54955,32 @@ function parseMetadata(response) {
54716
54955
  const titleMatch = response.match(/TITLE:\s*(.+)/i);
54717
54956
  const descMatch = response.match(/DESC:\s*(.+)/i);
54718
54957
  if (!titleMatch || !descMatch) {
54719
- log15.debug("Failed to parse title/description from response");
54958
+ log17.debug("Failed to parse title/description from response");
54720
54959
  return null;
54721
54960
  }
54722
54961
  let title = titleMatch[1].trim();
54723
54962
  let description = descMatch[1].trim();
54724
54963
  if (title.length < MIN_TITLE_LENGTH) {
54725
- log15.debug(`Title too short: ${title.length} chars`);
54964
+ log17.debug(`Title too short: ${title.length} chars`);
54726
54965
  return null;
54727
54966
  }
54728
54967
  if (title.length > MAX_TITLE_LENGTH) {
54729
- log15.debug(`Title too long (${title.length} chars), truncating`);
54968
+ log17.debug(`Title too long (${title.length} chars), truncating`);
54730
54969
  title = truncateAtWord(title, MAX_TITLE_LENGTH);
54731
54970
  }
54732
54971
  if (description.length < MIN_DESC_LENGTH) {
54733
- log15.debug(`Description too short: ${description.length} chars`);
54972
+ log17.debug(`Description too short: ${description.length} chars`);
54734
54973
  return null;
54735
54974
  }
54736
54975
  if (description.length > MAX_DESC_LENGTH) {
54737
- log15.debug(`Description too long (${description.length} chars), truncating`);
54976
+ log17.debug(`Description too long (${description.length} chars), truncating`);
54738
54977
  description = truncateAtWord(description, MAX_DESC_LENGTH);
54739
54978
  }
54740
54979
  return { title, description };
54741
54980
  }
54742
54981
  async function suggestSessionMetadata(context) {
54743
54982
  const logContext = typeof context === "string" ? context.substring(0, 50) : context.originalTask.substring(0, 50);
54744
- log15.debug(`Suggesting title for: "${logContext}..."`);
54983
+ log17.debug(`Suggesting title for: "${logContext}..."`);
54745
54984
  try {
54746
54985
  const result = await quickQuery({
54747
54986
  prompt: buildTitlePrompt(context),
@@ -54749,23 +54988,23 @@ async function suggestSessionMetadata(context) {
54749
54988
  timeout: SUGGESTION_TIMEOUT
54750
54989
  });
54751
54990
  if (!result.success || !result.response) {
54752
- log15.debug(`Title suggestion failed: ${result.error || "no response"}`);
54991
+ log17.debug(`Title suggestion failed: ${result.error || "no response"}`);
54753
54992
  return null;
54754
54993
  }
54755
54994
  const metadata = parseMetadata(result.response);
54756
54995
  if (metadata) {
54757
- log15.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
54996
+ log17.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
54758
54997
  }
54759
54998
  return metadata;
54760
54999
  } catch (err) {
54761
- log15.debug(`Title suggestion error: ${err}`);
55000
+ log17.debug(`Title suggestion error: ${err}`);
54762
55001
  return null;
54763
55002
  }
54764
55003
  }
54765
55004
 
54766
55005
  // src/operations/suggestions/tag.ts
54767
55006
  init_logger();
54768
- var log16 = createLogger("tags");
55007
+ var log18 = createLogger("tags");
54769
55008
  var SUGGESTION_TIMEOUT2 = 15000;
54770
55009
  var MAX_TAGS = 3;
54771
55010
  var VALID_TAGS = [
@@ -54797,7 +55036,7 @@ function parseTags(response) {
54797
55036
  return [...new Set(tags)].slice(0, MAX_TAGS);
54798
55037
  }
54799
55038
  async function suggestSessionTags(userMessage) {
54800
- log16.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
55039
+ log18.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
54801
55040
  try {
54802
55041
  const result = await quickQuery({
54803
55042
  prompt: buildTagPrompt(userMessage),
@@ -54805,14 +55044,14 @@ async function suggestSessionTags(userMessage) {
54805
55044
  timeout: SUGGESTION_TIMEOUT2
54806
55045
  });
54807
55046
  if (!result.success || !result.response) {
54808
- log16.debug(`Tag suggestion failed: ${result.error || "no response"}`);
55047
+ log18.debug(`Tag suggestion failed: ${result.error || "no response"}`);
54809
55048
  return [];
54810
55049
  }
54811
55050
  const tags = parseTags(result.response);
54812
- log16.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
55051
+ log18.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
54813
55052
  return tags;
54814
55053
  } catch (err) {
54815
- log16.debug(`Tag suggestion error: ${err}`);
55054
+ log18.debug(`Tag suggestion error: ${err}`);
54816
55055
  return [];
54817
55056
  }
54818
55057
  }
@@ -58332,7 +58571,7 @@ class BugReportExecutor extends BaseExecutor {
58332
58571
  // src/operations/executors/worktree-prompt.ts
58333
58572
  init_emoji();
58334
58573
  init_logger();
58335
- var log17 = createLogger("wt-prompt");
58574
+ var log19 = createLogger("wt-prompt");
58336
58575
  // src/operations/message-manager.ts
58337
58576
  init_logger();
58338
58577
 
@@ -58367,8 +58606,8 @@ function createMessageManagerEvents() {
58367
58606
  init_logger();
58368
58607
  import { lstat, mkdir as mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
58369
58608
  import { tmpdir as tmpdir2 } from "os";
58370
- import { basename, join as join8 } from "path";
58371
- var log18 = createLogger("streaming");
58609
+ import { join as join8 } from "path";
58610
+ var log20 = createLogger("streaming");
58372
58611
  var MAX_UPLOAD_SIZE = 100 * 1024 * 1024;
58373
58612
  var UPLOAD_ROOT_DIR = "claude-threads-uploads";
58374
58613
  function safeIdSegment(id) {
@@ -58384,29 +58623,12 @@ async function cleanupSessionUploads(platformId, threadId) {
58384
58623
  try {
58385
58624
  await rm2(dir, { recursive: true, force: true });
58386
58625
  } catch (err) {
58387
- log18.debug(`Upload cleanup for ${platformId}:${threadId} failed (ignored): ${err}`);
58626
+ log20.debug(`Upload cleanup for ${platformId}:${threadId} failed (ignored): ${err}`);
58388
58627
  }
58389
58628
  }
58390
- function sanitizeFilename(name) {
58391
- const flat = basename(name.replace(/\\/g, "/"));
58392
- const cleaned = flat.replace(/[\x00-\x1F\x7F]/g, "_").trim();
58393
- if (!cleaned || cleaned === "." || cleaned === "..") {
58394
- return "attachment";
58395
- }
58396
- return cleaned;
58397
- }
58398
58629
  function sanitizeForPrompt(value) {
58399
58630
  return value.replace(/[\x00-\x1F\x7F]/g, "");
58400
58631
  }
58401
- function formatBytes(bytes) {
58402
- if (bytes < 1024)
58403
- return `${bytes} B`;
58404
- if (bytes < 1024 * 1024)
58405
- return `${(bytes / 1024).toFixed(1)} KB`;
58406
- if (bytes < 1024 * 1024 * 1024)
58407
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
58408
- return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
58409
- }
58410
58632
  async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
58411
58633
  const saved = [];
58412
58634
  const skipped = [];
@@ -58422,7 +58644,7 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
58422
58644
  for (const file of files) {
58423
58645
  skipped.push({ name: file.name, reason: "Refusing to write under symlinked upload directory" });
58424
58646
  }
58425
- log18.error(`Upload dir is a symlink, refusing all writes: ${uploadDir}`);
58647
+ log20.error(`Upload dir is a symlink, refusing all writes: ${uploadDir}`);
58426
58648
  return { saved, skipped };
58427
58649
  }
58428
58650
  const messageDir = await mkdtemp(join8(uploadDir, `${Date.now().toString(36)}-`));
@@ -58447,11 +58669,11 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
58447
58669
  size: buffer.length
58448
58670
  });
58449
58671
  if (debug) {
58450
- log18.debug(`Saved ${file.name} → ${absolutePath} (${formatBytes(buffer.length)})`);
58672
+ log20.debug(`Saved ${file.name} → ${absolutePath} (${formatBytes(buffer.length)})`);
58451
58673
  }
58452
58674
  } catch (err) {
58453
58675
  const message = err instanceof Error ? err.message : String(err);
58454
- log18.error(`Failed to save uploaded file ${file.name}: ${message}`);
58676
+ log20.error(`Failed to save uploaded file ${file.name}: ${message}`);
58455
58677
  skipped.push({
58456
58678
  name: file.name,
58457
58679
  reason: `Download failed: ${message}`
@@ -58511,7 +58733,7 @@ function stopTyping(session) {
58511
58733
  }
58512
58734
 
58513
58735
  // src/operations/message-manager.ts
58514
- var log19 = createLogger("msg-mgr");
58736
+ var log21 = createLogger("msg-mgr");
58515
58737
 
58516
58738
  class MessageManager {
58517
58739
  platform;
@@ -58601,7 +58823,7 @@ class MessageManager {
58601
58823
  });
58602
58824
  }
58603
58825
  async handleEvent(event) {
58604
- const logger = log19.forSession(this.sessionId);
58826
+ const logger = log21.forSession(this.sessionId);
58605
58827
  const transformCtx = {
58606
58828
  sessionId: this.sessionId,
58607
58829
  formatter: this.platform.getFormatter(),
@@ -58651,7 +58873,7 @@ class MessageManager {
58651
58873
  }
58652
58874
  }
58653
58875
  async executeOperation(op) {
58654
- const logger = log19.forSession(this.sessionId);
58876
+ const logger = log21.forSession(this.sessionId);
58655
58877
  const ctx = this.getExecutorContext();
58656
58878
  try {
58657
58879
  if (isContentOp(op)) {
@@ -58719,7 +58941,7 @@ class MessageManager {
58719
58941
  threadId: this.threadId,
58720
58942
  platform: this.platform,
58721
58943
  formatter: this.platform.getFormatter(),
58722
- logger: log19.forSession(this.sessionId),
58944
+ logger: log21.forSession(this.sessionId),
58723
58945
  postTracker: this.postTracker,
58724
58946
  contentBreaker: this.contentBreaker,
58725
58947
  threadLogger: this.session.threadLogger,
@@ -58930,13 +59152,13 @@ class MessageManager {
58930
59152
  return this.systemExecutor.postSuccess(message, this.getExecutorContext());
58931
59153
  }
58932
59154
  async prepareForUserMessage() {
58933
- const logger = log19.forSession(this.sessionId);
59155
+ const logger = log21.forSession(this.sessionId);
58934
59156
  logger.debug("Preparing for new user message");
58935
59157
  await this.closeCurrentPost();
58936
59158
  await this.bumpTaskList();
58937
59159
  }
58938
59160
  async handleUserMessage(message, files, username, displayName) {
58939
- const logger = log19.forSession(this.sessionId);
59161
+ const logger = log21.forSession(this.sessionId);
58940
59162
  if (!this.session.claude.isRunning()) {
58941
59163
  logger.debug("Claude not running, ignoring user message");
58942
59164
  return false;
@@ -58973,7 +59195,7 @@ class MessageManager {
58973
59195
  ];
58974
59196
  }
58975
59197
  async handleReaction(postId, emoji, user, action) {
58976
- const logger = log19.forSession(this.sessionId);
59198
+ const logger = log21.forSession(this.sessionId);
58977
59199
  const ctx = this.getExecutorContext();
58978
59200
  logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
58979
59201
  for (const { name, executor } of this.reactionDispatchList()) {
@@ -59008,7 +59230,7 @@ class MessageManager {
59008
59230
  // src/utils/battery.ts
59009
59231
  import { exec } from "child_process";
59010
59232
  import { promisify } from "util";
59011
- import { readFile as readFile2 } from "fs/promises";
59233
+ import { readFile as readFile4 } from "fs/promises";
59012
59234
  var execAsync = promisify(exec);
59013
59235
  async function getBatteryStatus() {
59014
59236
  switch (process.platform) {
@@ -59041,9 +59263,9 @@ async function getLinuxBattery() {
59041
59263
  for (const name of batteryNames) {
59042
59264
  try {
59043
59265
  const basePath = `/sys/class/power_supply/${name}`;
59044
- const capacityStr = await readFile2(`${basePath}/capacity`, "utf-8");
59266
+ const capacityStr = await readFile4(`${basePath}/capacity`, "utf-8");
59045
59267
  const percentage = parseInt(capacityStr.trim(), 10);
59046
- const status = await readFile2(`${basePath}/status`, "utf-8");
59268
+ const status = await readFile4(`${basePath}/status`, "utf-8");
59047
59269
  const charging = status.trim().toLowerCase() !== "discharging";
59048
59270
  return { percentage, charging };
59049
59271
  } catch {
@@ -59154,7 +59376,7 @@ function formatPullRequestLink(url, formatter) {
59154
59376
  }
59155
59377
 
59156
59378
  // src/operations/sticky-message/handler.ts
59157
- var log20 = createLogger("sticky");
59379
+ var log22 = createLogger("sticky");
59158
59380
  var botStartedAt = new Date;
59159
59381
  function getPendingPrompts(session) {
59160
59382
  const prompts2 = [];
@@ -59229,21 +59451,21 @@ function initialize(store) {
59229
59451
  stickyPostIds.set(platformId, postId);
59230
59452
  }
59231
59453
  if (persistedIds.size > 0) {
59232
- log20.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
59454
+ log22.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
59233
59455
  }
59234
59456
  }
59235
59457
  function setPlatformPaused(platformId, paused) {
59236
59458
  if (paused) {
59237
59459
  pausedPlatforms.set(platformId, true);
59238
- log20.debug(`Platform ${platformId} marked as paused`);
59460
+ log22.debug(`Platform ${platformId} marked as paused`);
59239
59461
  } else {
59240
59462
  pausedPlatforms.delete(platformId);
59241
- log20.debug(`Platform ${platformId} marked as active`);
59463
+ log22.debug(`Platform ${platformId} marked as active`);
59242
59464
  }
59243
59465
  }
59244
59466
  function setShuttingDown(shuttingDown) {
59245
59467
  isShuttingDown = shuttingDown;
59246
- log20.debug(`Bot shutdown state: ${shuttingDown}`);
59468
+ log22.debug(`Bot shutdown state: ${shuttingDown}`);
59247
59469
  }
59248
59470
  function getTaskContent(session) {
59249
59471
  const taskState = session.messageManager?.getTaskListState();
@@ -59554,12 +59776,12 @@ async function validateLastMessageIds(platform, sessions) {
59554
59776
  try {
59555
59777
  const post2 = await platform.getPost(lastMessageId);
59556
59778
  if (!post2) {
59557
- log20.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
59779
+ log22.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
59558
59780
  session.lastMessageId = undefined;
59559
59781
  session.lastMessageTs = undefined;
59560
59782
  }
59561
59783
  } catch (err) {
59562
- log20.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
59784
+ log22.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
59563
59785
  session.lastMessageId = undefined;
59564
59786
  session.lastMessageTs = undefined;
59565
59787
  }
@@ -59568,63 +59790,63 @@ async function validateLastMessageIds(platform, sessions) {
59568
59790
  }
59569
59791
  async function updateStickyMessageImpl(platform, sessions, config) {
59570
59792
  const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
59571
- log20.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
59793
+ log22.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
59572
59794
  for (const s of platformSessions) {
59573
- log20.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
59795
+ log22.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
59574
59796
  }
59575
59797
  await validateLastMessageIds(platform, platformSessions);
59576
59798
  const formatter = platform.getFormatter();
59577
59799
  const content = await buildStickyMessage(sessions, platform.platformId, config, formatter, (threadId) => platform.getThreadLink(threadId));
59578
59800
  const existingPostId = stickyPostIds.get(platform.platformId);
59579
59801
  const shouldBump = needsBump.get(platform.platformId) ?? false;
59580
- log20.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
59802
+ log22.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
59581
59803
  try {
59582
59804
  if (existingPostId && !shouldBump) {
59583
- log20.debug(`Updating existing post in place...`);
59805
+ log22.debug(`Updating existing post in place...`);
59584
59806
  try {
59585
59807
  await platform.updatePost(existingPostId, content);
59586
59808
  try {
59587
59809
  await platform.pinPost(existingPostId);
59588
- log20.debug(`Re-pinned post`);
59810
+ log22.debug(`Re-pinned post`);
59589
59811
  } catch (pinErr) {
59590
- log20.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
59812
+ log22.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
59591
59813
  }
59592
- log20.debug(`Updated successfully`);
59814
+ log22.debug(`Updated successfully`);
59593
59815
  return;
59594
59816
  } catch (err) {
59595
- log20.debug(`Update failed, will create new: ${err}`);
59817
+ log22.debug(`Update failed, will create new: ${err}`);
59596
59818
  }
59597
59819
  }
59598
59820
  needsBump.set(platform.platformId, false);
59599
59821
  if (existingPostId) {
59600
- log20.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
59822
+ log22.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
59601
59823
  try {
59602
59824
  await platform.unpinPost(existingPostId);
59603
- log20.debug(`Unpinned successfully`);
59825
+ log22.debug(`Unpinned successfully`);
59604
59826
  } catch (err) {
59605
- log20.debug(`Unpin failed (probably already unpinned): ${err}`);
59827
+ log22.debug(`Unpin failed (probably already unpinned): ${err}`);
59606
59828
  }
59607
59829
  try {
59608
59830
  await platform.deletePost(existingPostId);
59609
- log20.debug(`Deleted successfully`);
59831
+ log22.debug(`Deleted successfully`);
59610
59832
  } catch (err) {
59611
- log20.debug(`Delete failed (probably already deleted): ${err}`);
59833
+ log22.debug(`Delete failed (probably already deleted): ${err}`);
59612
59834
  }
59613
59835
  stickyPostIds.delete(platform.platformId);
59614
59836
  }
59615
- log20.debug(`Creating new post...`);
59837
+ log22.debug(`Creating new post...`);
59616
59838
  const post2 = await platform.createPost(content);
59617
59839
  stickyPostIds.set(platform.platformId, post2.id);
59618
59840
  try {
59619
59841
  await platform.pinPost(post2.id);
59620
- log20.debug(`Pinned post successfully`);
59842
+ log22.debug(`Pinned post successfully`);
59621
59843
  } catch (err) {
59622
- log20.debug(`Failed to pin post: ${err}`);
59844
+ log22.debug(`Failed to pin post: ${err}`);
59623
59845
  }
59624
59846
  if (sessionStore) {
59625
59847
  sessionStore.saveStickyPostId(platform.platformId, post2.id);
59626
59848
  }
59627
- log20.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
59849
+ log22.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
59628
59850
  const excludePostIds = new Set;
59629
59851
  if (sessionStore) {
59630
59852
  for (const session of sessionStore.load().values()) {
@@ -59640,10 +59862,10 @@ async function updateStickyMessageImpl(platform, sessions, config) {
59640
59862
  }
59641
59863
  const botUser = await platform.getBotUser();
59642
59864
  cleanupOldStickyMessages(platform, botUser.id, false, excludePostIds).catch((err) => {
59643
- log20.debug(`Background cleanup failed: ${err}`);
59865
+ log22.debug(`Background cleanup failed: ${err}`);
59644
59866
  });
59645
59867
  } catch (err) {
59646
- log20.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
59868
+ log22.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
59647
59869
  }
59648
59870
  }
59649
59871
  async function updateAllStickyMessages(platforms, sessions, config) {
@@ -59668,7 +59890,7 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
59668
59890
  if (!forceRun) {
59669
59891
  const lastRun = lastCleanupTime.get(platformId) || 0;
59670
59892
  if (now - lastRun < CLEANUP_THROTTLE_MS) {
59671
- log20.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
59893
+ log22.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
59672
59894
  return;
59673
59895
  }
59674
59896
  }
@@ -59678,31 +59900,31 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
59678
59900
  const pinnedPostIds = await platform.getPinnedPosts();
59679
59901
  const recentPinnedIds = pinnedPostIds.filter((id) => id !== currentStickyId && !excludePostIds?.has(id) && isRecentPost(id));
59680
59902
  if (recentPinnedIds.length === 0) {
59681
- log20.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
59903
+ log22.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
59682
59904
  return;
59683
59905
  }
59684
- log20.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
59906
+ log22.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
59685
59907
  for (const postId of recentPinnedIds) {
59686
59908
  try {
59687
59909
  const post2 = await platform.getPost(postId);
59688
59910
  if (!post2)
59689
59911
  continue;
59690
59912
  if (post2.userId === botUserId) {
59691
- log20.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
59913
+ log22.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
59692
59914
  try {
59693
59915
  await platform.unpinPost(postId);
59694
59916
  await platform.deletePost(postId);
59695
- log20.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
59917
+ log22.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
59696
59918
  } catch (err) {
59697
- log20.debug(`Failed to cleanup ${postId}: ${err}`);
59919
+ log22.debug(`Failed to cleanup ${postId}: ${err}`);
59698
59920
  }
59699
59921
  }
59700
59922
  } catch (err) {
59701
- log20.debug(`Could not check post ${postId}: ${err}`);
59923
+ log22.debug(`Could not check post ${postId}: ${err}`);
59702
59924
  }
59703
59925
  }
59704
59926
  } catch (err) {
59705
- log20.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
59927
+ log22.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
59706
59928
  }
59707
59929
  }
59708
59930
  // src/operations/bug-report/handler.ts
@@ -60348,6 +60570,21 @@ function formatBugPreview(title, description, context, imageUrls, imageErrors, f
60348
60570
  return lines.join(`
60349
60571
  `);
60350
60572
  }
60573
+ // src/claude/restart-options.ts
60574
+ function buildRestartCliOptions(session, ctx) {
60575
+ const platformMcpConfig = session.platform.getMcpConfig();
60576
+ return {
60577
+ threadId: session.threadId,
60578
+ chrome: ctx.chromeEnabled,
60579
+ platformConfig: platformMcpConfig,
60580
+ logSessionId: session.sessionId,
60581
+ permissionTimeoutMs: ctx.permissionTimeoutMs,
60582
+ account: ctx.account,
60583
+ uploadDir: getSessionUploadDir(session.platformId, session.threadId),
60584
+ outboundFiles: platformMcpConfig.outboundFiles
60585
+ };
60586
+ }
60587
+
60351
60588
  // src/operations/commands/handler.ts
60352
60589
  import { randomUUID as randomUUID2 } from "crypto";
60353
60590
  import { resolve as resolve5 } from "path";
@@ -63765,8 +64002,8 @@ function getUpdateInfo() {
63765
64002
  init_emoji();
63766
64003
  init_logger();
63767
64004
  init_worktree();
63768
- var log21 = createLogger("commands");
63769
- var sessionLog2 = createSessionLog(log21);
64005
+ var log23 = createLogger("commands");
64006
+ var sessionLog2 = createSessionLog(log23);
63770
64007
  function sessionAccountOption(session, ctx) {
63771
64008
  if (!session.claudeAccountId)
63772
64009
  return;
@@ -63775,6 +64012,13 @@ function sessionAccountOption(session, ctx) {
63775
64012
  return;
63776
64013
  return { id: account.id, home: account.home, apiKey: account.apiKey };
63777
64014
  }
64015
+ function commonRestartCliOptions(session, ctx) {
64016
+ return buildRestartCliOptions(session, {
64017
+ chromeEnabled: ctx.config.chromeEnabled,
64018
+ permissionTimeoutMs: ctx.config.permissionTimeoutMs,
64019
+ account: sessionAccountOption(session, ctx)
64020
+ });
64021
+ }
63778
64022
  async function restartClaudeSession(session, cliOptions, ctx, actionName) {
63779
64023
  ctx.ops.stopTyping(session);
63780
64024
  transitionTo(session, "restarting");
@@ -63927,8 +64171,8 @@ async function changeDirectory(session, newDir, username, ctx) {
63927
64171
 
63928
64172
  ${CHAT_PLATFORM_PROMPT}`;
63929
64173
  const cliOptions = {
64174
+ ...commonRestartCliOptions(session, ctx),
63930
64175
  workingDir: absoluteDir,
63931
- threadId: session.threadId,
63932
64176
  permissionMode: effectivePermissionMode({
63933
64177
  override: session.permissionModeOverride,
63934
64178
  sessionHasInteractiveOverride: session.forceInteractivePermissions,
@@ -63936,12 +64180,7 @@ ${CHAT_PLATFORM_PROMPT}`;
63936
64180
  }),
63937
64181
  sessionId: newSessionId,
63938
64182
  resume: false,
63939
- chrome: ctx.config.chromeEnabled,
63940
- platformConfig: session.platform.getMcpConfig(),
63941
- appendSystemPrompt,
63942
- logSessionId: session.sessionId,
63943
- permissionTimeoutMs: ctx.config.permissionTimeoutMs,
63944
- account: sessionAccountOption(session, ctx)
64183
+ appendSystemPrompt
63945
64184
  };
63946
64185
  const success = await restartClaudeSession(session, cliOptions, ctx, "Restart Claude for directory change");
63947
64186
  if (!success)
@@ -64020,16 +64259,11 @@ async function setSessionPermissionMode(session, username, mode, ctx) {
64020
64259
  session.threadLogger?.logCommand("permissions", mode, username);
64021
64260
  const canResume = session.lifecycle.hasClaudeResponded;
64022
64261
  const cliOptions = {
64262
+ ...commonRestartCliOptions(session, ctx),
64023
64263
  workingDir: session.workingDir,
64024
- threadId: session.threadId,
64025
64264
  permissionMode: mode,
64026
64265
  sessionId: session.claudeSessionId,
64027
- resume: canResume,
64028
- chrome: ctx.config.chromeEnabled,
64029
- platformConfig: session.platform.getMcpConfig(),
64030
- logSessionId: session.sessionId,
64031
- permissionTimeoutMs: ctx.config.permissionTimeoutMs,
64032
- account: sessionAccountOption(session, ctx)
64266
+ resume: canResume
64033
64267
  };
64034
64268
  const success = await restartClaudeSession(session, cliOptions, ctx, `Set permission mode to ${mode}`);
64035
64269
  if (!success)
@@ -64300,7 +64534,7 @@ init_logger();
64300
64534
  import { exec as exec3 } from "child_process";
64301
64535
  import { promisify as promisify3 } from "util";
64302
64536
  var execAsync2 = promisify3(exec3);
64303
- var log22 = createLogger("branch");
64537
+ var log24 = createLogger("branch");
64304
64538
  var SUGGESTION_TIMEOUT3 = 15000;
64305
64539
  var MAX_SUGGESTIONS = 3;
64306
64540
  async function getCurrentBranch3(workingDir) {
@@ -64349,7 +64583,7 @@ function parseBranchSuggestions(response) {
64349
64583
  return lines.slice(0, MAX_SUGGESTIONS);
64350
64584
  }
64351
64585
  async function suggestBranchNames(workingDir, userMessage) {
64352
- log22.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
64586
+ log24.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
64353
64587
  try {
64354
64588
  const [currentBranch, recentCommits] = await Promise.all([
64355
64589
  getCurrentBranch3(workingDir),
@@ -64363,14 +64597,14 @@ async function suggestBranchNames(workingDir, userMessage) {
64363
64597
  workingDir
64364
64598
  });
64365
64599
  if (!result.success || !result.response) {
64366
- log22.debug(`Branch suggestion failed: ${result.error || "no response"}`);
64600
+ log24.debug(`Branch suggestion failed: ${result.error || "no response"}`);
64367
64601
  return [];
64368
64602
  }
64369
64603
  const suggestions = parseBranchSuggestions(result.response);
64370
- log22.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
64604
+ log24.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
64371
64605
  return suggestions;
64372
64606
  } catch (err) {
64373
- log22.debug(`Branch suggestion error: ${err}`);
64607
+ log24.debug(`Branch suggestion error: ${err}`);
64374
64608
  return [];
64375
64609
  }
64376
64610
  }
@@ -64379,8 +64613,8 @@ async function suggestBranchNames(workingDir, userMessage) {
64379
64613
  init_worktree();
64380
64614
  import { randomUUID as randomUUID3 } from "crypto";
64381
64615
  init_logger();
64382
- var log23 = createLogger("worktree");
64383
- var sessionLog3 = createSessionLog(log23);
64616
+ var log25 = createLogger("worktree");
64617
+ var sessionLog3 = createSessionLog(log25);
64384
64618
  function parseWorktreeError(error) {
64385
64619
  const message = error instanceof Error ? error.message : String(error);
64386
64620
  const lowerMessage = message.toLowerCase();
@@ -64627,8 +64861,11 @@ async function createAndSwitchToWorktree(session, branch, username, options) {
64627
64861
  const needsTitlePrompt = !session.sessionTitle;
64628
64862
  const sessionContext = needsTitlePrompt ? buildSessionContext(session.platform, existing.path, session.threadId) : null;
64629
64863
  const cliOptions = {
64864
+ ...buildRestartCliOptions(session, {
64865
+ chromeEnabled: options.chromeEnabled,
64866
+ permissionTimeoutMs: options.permissionTimeoutMs
64867
+ }),
64630
64868
  workingDir: existing.path,
64631
- threadId: session.threadId,
64632
64869
  permissionMode: effectivePermissionMode({
64633
64870
  override: session.permissionModeOverride,
64634
64871
  sessionHasInteractiveOverride: session.forceInteractivePermissions,
@@ -64636,13 +64873,9 @@ async function createAndSwitchToWorktree(session, branch, username, options) {
64636
64873
  }),
64637
64874
  sessionId: newSessionId,
64638
64875
  resume: false,
64639
- chrome: options.chromeEnabled,
64640
- platformConfig: session.platform.getMcpConfig(),
64641
64876
  appendSystemPrompt: sessionContext ? `${sessionContext}
64642
64877
 
64643
- ${options.appendSystemPrompt}` : undefined,
64644
- logSessionId: session.sessionId,
64645
- permissionTimeoutMs: options.permissionTimeoutMs
64878
+ ${options.appendSystemPrompt}` : undefined
64646
64879
  };
64647
64880
  session.claude = new ClaudeCli(cliOptions);
64648
64881
  session.claude.on("event", (e) => options.handleEvent(session.sessionId, e));
@@ -64723,8 +64956,11 @@ ${fmt.formatItalic("Claude Code restarted in the worktree")}`);
64723
64956
  const needsTitlePrompt = !session.sessionTitle;
64724
64957
  const sessionContext = needsTitlePrompt ? buildSessionContext(session.platform, worktreePath, session.threadId) : null;
64725
64958
  const cliOptions = {
64959
+ ...buildRestartCliOptions(session, {
64960
+ chromeEnabled: options.chromeEnabled,
64961
+ permissionTimeoutMs: options.permissionTimeoutMs
64962
+ }),
64726
64963
  workingDir: worktreePath,
64727
- threadId: session.threadId,
64728
64964
  permissionMode: effectivePermissionMode({
64729
64965
  override: session.permissionModeOverride,
64730
64966
  sessionHasInteractiveOverride: session.forceInteractivePermissions,
@@ -64732,13 +64968,9 @@ ${fmt.formatItalic("Claude Code restarted in the worktree")}`);
64732
64968
  }),
64733
64969
  sessionId: newSessionId,
64734
64970
  resume: false,
64735
- chrome: options.chromeEnabled,
64736
- platformConfig: session.platform.getMcpConfig(),
64737
64971
  appendSystemPrompt: sessionContext ? `${sessionContext}
64738
64972
 
64739
- ${options.appendSystemPrompt}` : undefined,
64740
- logSessionId: session.sessionId,
64741
- permissionTimeoutMs: options.permissionTimeoutMs
64973
+ ${options.appendSystemPrompt}` : undefined
64742
64974
  };
64743
64975
  session.claude = new ClaudeCli(cliOptions);
64744
64976
  session.claude.on("event", (e) => options.handleEvent(session.sessionId, e));
@@ -64954,8 +65186,8 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
64954
65186
  }
64955
65187
  // src/operations/events/handler.ts
64956
65188
  init_logger();
64957
- var log24 = createLogger("events");
64958
- var sessionLog4 = createSessionLog(log24);
65189
+ var log26 = createLogger("events");
65190
+ var sessionLog4 = createSessionLog(log26);
64959
65191
  function detectAndExecuteClaudeCommands(text, session, ctx) {
64960
65192
  const parsed = parseClaudeCommand(text);
64961
65193
  if (parsed && isClaudeAllowedCommand(parsed.command)) {
@@ -65203,8 +65435,8 @@ function createSessionContext(config, state, ops) {
65203
65435
  // src/operations/context-prompt/handler.ts
65204
65436
  init_emoji();
65205
65437
  init_logger();
65206
- var log25 = createLogger("context");
65207
- var sessionLog5 = createSessionLog(log25);
65438
+ var log27 = createLogger("context");
65439
+ var sessionLog5 = createSessionLog(log27);
65208
65440
  var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
65209
65441
  var CONTEXT_OPTIONS = [3, 5, 10];
65210
65442
  var contextPromptTimeouts = new Map;
@@ -65454,8 +65686,8 @@ function formatRelativeTime(date) {
65454
65686
  }
65455
65687
  // src/session/lifecycle.ts
65456
65688
  init_worktree();
65457
- var log26 = createLogger("lifecycle");
65458
- var sessionLog6 = createSessionLog(log26);
65689
+ var log28 = createLogger("lifecycle");
65690
+ var sessionLog6 = createSessionLog(log28);
65459
65691
  function mutableSessions(ctx) {
65460
65692
  return ctx.state.sessions;
65461
65693
  }
@@ -65845,17 +66077,17 @@ async function startSession(options, username, displayName, replyToPostId, platf
65845
66077
  return;
65846
66078
  }
65847
66079
  workingDir = resolvedDir;
65848
- log26.info(`Starting session in directory: ${workingDir} (from !cd command)`);
66080
+ log28.info(`Starting session in directory: ${workingDir} (from !cd command)`);
65849
66081
  }
65850
66082
  if (initialOptions?.permissionMode) {
65851
66083
  permissionMode = initialOptions.permissionMode;
65852
66084
  forceInteractivePermissions = permissionMode === "default";
65853
66085
  sessionPermissionModeOverride = permissionMode;
65854
- log26.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
66086
+ log28.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
65855
66087
  } else if (initialOptions?.forceInteractivePermissions) {
65856
66088
  forceInteractivePermissions = true;
65857
66089
  permissionMode = "default";
65858
- log26.info(`Starting session with interactive permissions (from !permissions command)`);
66090
+ log28.info(`Starting session with interactive permissions (from !permissions command)`);
65859
66091
  }
65860
66092
  const sessionContext = buildSessionContext(platform, workingDir, actualThreadId);
65861
66093
  const systemPrompt = `${sessionContext}
@@ -65864,7 +66096,7 @@ ${CHAT_PLATFORM_PROMPT}`;
65864
66096
  const platformMcpConfig = platform.getMcpConfig();
65865
66097
  const claudeAccount = ctx.ops.acquireClaudeAccount(undefined, actualThreadId);
65866
66098
  if (claudeAccount) {
65867
- log26.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
66099
+ log28.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
65868
66100
  }
65869
66101
  const cliOptions = {
65870
66102
  workingDir,
@@ -65877,7 +66109,9 @@ ${CHAT_PLATFORM_PROMPT}`;
65877
66109
  appendSystemPrompt: systemPrompt,
65878
66110
  logSessionId: sessionId,
65879
66111
  permissionTimeoutMs: ctx.config.permissionTimeoutMs,
65880
- account: claudeAccount ? { id: claudeAccount.id, home: claudeAccount.home, apiKey: claudeAccount.apiKey } : undefined
66112
+ account: claudeAccount ? { id: claudeAccount.id, home: claudeAccount.home, apiKey: claudeAccount.apiKey } : undefined,
66113
+ uploadDir: getSessionUploadDir(platformId, actualThreadId),
66114
+ outboundFiles: platformMcpConfig.outboundFiles
65881
66115
  };
65882
66116
  const claude = new ClaudeCli(cliOptions);
65883
66117
  const session = {
@@ -65971,28 +66205,28 @@ async function resumeSession(state, ctx) {
65971
66205
  !state.claudeSessionId && "claudeSessionId",
65972
66206
  !state.workingDir && "workingDir"
65973
66207
  ].filter(Boolean).join(", ");
65974
- log26.warn(`Skipping session with missing required fields: ${missing}`);
66208
+ log28.warn(`Skipping session with missing required fields: ${missing}`);
65975
66209
  return;
65976
66210
  }
65977
66211
  const shortId = state.threadId.substring(0, 8);
65978
66212
  const platforms = ctx.state.platforms;
65979
66213
  const platform = platforms.get(state.platformId);
65980
66214
  if (!platform) {
65981
- log26.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
66215
+ log28.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
65982
66216
  return;
65983
66217
  }
65984
66218
  const threadPost = await platform.getPost(state.threadId);
65985
66219
  if (!threadPost) {
65986
- log26.warn(`Thread ${shortId}... deleted, skipping resume`);
66220
+ log28.warn(`Thread ${shortId}... deleted, skipping resume`);
65987
66221
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
65988
66222
  return;
65989
66223
  }
65990
66224
  if (ctx.state.sessions.size >= ctx.config.maxSessions) {
65991
- log26.warn(`Max sessions reached, skipping resume for ${shortId}...`);
66225
+ log28.warn(`Max sessions reached, skipping resume for ${shortId}...`);
65992
66226
  return;
65993
66227
  }
65994
66228
  if (!existsSync11(state.workingDir)) {
65995
- log26.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
66229
+ log28.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
65996
66230
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
65997
66231
  const resumeFormatter = platform.getFormatter();
65998
66232
  const tempSession = {
@@ -66016,7 +66250,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
66016
66250
  ${CHAT_PLATFORM_PROMPT}`;
66017
66251
  const claudeAccount = ctx.ops.acquireClaudeAccount(state.claudeAccountId, state.threadId);
66018
66252
  if (state.claudeAccountId && !claudeAccount) {
66019
- log26.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
66253
+ log28.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
66020
66254
  }
66021
66255
  const cliOptions = {
66022
66256
  workingDir: state.workingDir,
@@ -66029,7 +66263,9 @@ ${CHAT_PLATFORM_PROMPT}`;
66029
66263
  appendSystemPrompt,
66030
66264
  logSessionId: sessionId,
66031
66265
  permissionTimeoutMs: ctx.config.permissionTimeoutMs,
66032
- account: claudeAccount ? { id: claudeAccount.id, home: claudeAccount.home, apiKey: claudeAccount.apiKey } : undefined
66266
+ account: claudeAccount ? { id: claudeAccount.id, home: claudeAccount.home, apiKey: claudeAccount.apiKey } : undefined,
66267
+ uploadDir: getSessionUploadDir(platformId, state.threadId),
66268
+ outboundFiles: platformMcpConfig.outboundFiles
66033
66269
  };
66034
66270
  const claude = new ClaudeCli(cliOptions);
66035
66271
  const session = {
@@ -66081,7 +66317,7 @@ ${CHAT_PLATFORM_PROMPT}`;
66081
66317
  worktreePath: detected.worktreePath,
66082
66318
  branch: detected.branch
66083
66319
  };
66084
- log26.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
66320
+ log28.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
66085
66321
  }
66086
66322
  }
66087
66323
  session.messageManager = createMessageManager(session, ctx);
@@ -66137,7 +66373,7 @@ ${sessionFormatter.formatItalic("Reconnected to Claude session. You can continue
66137
66373
  await ctx.ops.updateStickyMessage();
66138
66374
  ctx.ops.persistSession(session);
66139
66375
  } catch (err) {
66140
- log26.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
66376
+ log28.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
66141
66377
  session.messageManager?.dispose();
66142
66378
  ctx.ops.emitSessionRemove(sessionId);
66143
66379
  mutableSessions(ctx).delete(sessionId);
@@ -66178,18 +66414,18 @@ async function resumePausedSession(threadId, message, files, ctx) {
66178
66414
  const persisted = ctx.state.sessionStore.load();
66179
66415
  const state = findPersistedByThreadId(persisted, threadId);
66180
66416
  if (!state) {
66181
- log26.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
66417
+ log28.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
66182
66418
  return;
66183
66419
  }
66184
66420
  const shortId = threadId.substring(0, 8);
66185
- log26.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
66421
+ log28.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
66186
66422
  await resumeSession(state, ctx);
66187
66423
  const session = ctx.ops.findSessionByThreadId(threadId);
66188
66424
  if (session && session.claude.isRunning() && session.messageManager) {
66189
66425
  session.messageCount++;
66190
66426
  await session.messageManager.handleUserMessage(message, files, state.startedBy);
66191
66427
  } else {
66192
- log26.warn(`Failed to resume session ${shortId}..., could not send message`);
66428
+ log28.warn(`Failed to resume session ${shortId}..., could not send message`);
66193
66429
  }
66194
66430
  }
66195
66431
  async function handleExit(sessionId, code, ctx) {
@@ -66197,7 +66433,7 @@ async function handleExit(sessionId, code, ctx) {
66197
66433
  const shortId = sessionId.substring(0, 8);
66198
66434
  sessionLog6(session).debug(`handleExit called code=${code} isShuttingDown=${ctx.state.isShuttingDown}`);
66199
66435
  if (!session) {
66200
- log26.debug(`Session ${shortId}... not found (already cleaned up)`);
66436
+ log28.debug(`Session ${shortId}... not found (already cleaned up)`);
66201
66437
  return;
66202
66438
  }
66203
66439
  if (isSessionRestarting(session)) {
@@ -66390,7 +66626,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
66390
66626
  }
66391
66627
 
66392
66628
  // src/operations/monitor/handler.ts
66393
- var log27 = createLogger("monitor");
66629
+ var log29 = createLogger("monitor");
66394
66630
  var DEFAULT_INTERVAL_MS = 60 * 1000;
66395
66631
 
66396
66632
  class SessionMonitor {
@@ -66412,14 +66648,14 @@ class SessionMonitor {
66412
66648
  }
66413
66649
  start() {
66414
66650
  if (this.isRunning) {
66415
- log27.debug("Session monitor already running");
66651
+ log29.debug("Session monitor already running");
66416
66652
  return;
66417
66653
  }
66418
66654
  this.isRunning = true;
66419
- log27.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
66655
+ log29.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
66420
66656
  this.timer = setInterval(() => {
66421
66657
  this.runCheck().catch((err) => {
66422
- log27.error(`Error during session monitoring: ${err}`);
66658
+ log29.error(`Error during session monitoring: ${err}`);
66423
66659
  });
66424
66660
  }, this.intervalMs);
66425
66661
  }
@@ -66429,7 +66665,7 @@ class SessionMonitor {
66429
66665
  this.timer = null;
66430
66666
  }
66431
66667
  this.isRunning = false;
66432
- log27.debug("Session monitor stopped");
66668
+ log29.debug("Session monitor stopped");
66433
66669
  }
66434
66670
  async runCheck() {
66435
66671
  await cleanupIdleSessions(this.sessionTimeoutMs, this.sessionWarningMs, this.getContext());
@@ -66441,8 +66677,8 @@ class SessionMonitor {
66441
66677
  // src/operations/plugin/handler.ts
66442
66678
  init_spawn();
66443
66679
  init_logger();
66444
- var log28 = createLogger("plugin");
66445
- var sessionLog7 = createSessionLog(log28);
66680
+ var log30 = createLogger("plugin");
66681
+ var sessionLog7 = createSessionLog(log30);
66446
66682
  async function runPluginCommand(args, cwd, timeout2 = 60000) {
66447
66683
  return new Promise((resolve6) => {
66448
66684
  const claudePath = process.env.CLAUDE_PATH || "claude";
@@ -66463,7 +66699,7 @@ async function runPluginCommand(args, cwd, timeout2 = 60000) {
66463
66699
  });
66464
66700
  proc.on("error", (err) => {
66465
66701
  resolve6({ stdout, stderr, exitCode: 1 });
66466
- log28.error(`Plugin command error: ${err.message}`);
66702
+ log30.error(`Plugin command error: ${err.message}`);
66467
66703
  });
66468
66704
  });
66469
66705
  }
@@ -66665,7 +66901,7 @@ class SessionRegistry {
66665
66901
  // src/session/reaction-router.ts
66666
66902
  init_emoji();
66667
66903
  init_logger();
66668
- var log29 = createLogger("manager");
66904
+ var log31 = createLogger("manager");
66669
66905
  async function handleReaction(deps, platformId, postId, emojiName, username, action) {
66670
66906
  const normalizedEmoji = normalizeEmojiName(emojiName);
66671
66907
  if (action === "added" && isResumeEmoji(normalizedEmoji)) {
@@ -66679,7 +66915,7 @@ async function handleReaction(deps, platformId, postId, emojiName, username, act
66679
66915
  if (session.platformId !== platformId)
66680
66916
  return;
66681
66917
  if (!session.sessionAllowedUsers.has(username) && !session.platform.isUserAllowed(username)) {
66682
- log29.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
66918
+ log31.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
66683
66919
  event: "reaction.rejected",
66684
66920
  platformId,
66685
66921
  sessionId: session.sessionId,
@@ -66715,7 +66951,7 @@ async function tryResumeFromReaction(deps, platformId, postId, username) {
66715
66951
  return false;
66716
66952
  }
66717
66953
  const shortId = persistedSession.threadId.substring(0, 8);
66718
- log29.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
66954
+ log31.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
66719
66955
  await resumeSession(persistedSession, deps.getContext());
66720
66956
  return true;
66721
66957
  }
@@ -66745,7 +66981,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
66745
66981
  }
66746
66982
  if (session.lastError?.postId === postId && isBugReportEmoji(emojiName)) {
66747
66983
  if (session.startedBy === username || session.platform.isUserAllowed(username) || session.sessionAllowedUsers.has(username)) {
66748
- log29.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
66984
+ log31.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
66749
66985
  await reportBug(session, undefined, username, deps.getContext(), session.lastError);
66750
66986
  return;
66751
66987
  }
@@ -66760,7 +66996,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
66760
66996
 
66761
66997
  // src/session/manager.ts
66762
66998
  init_logger();
66763
- var log30 = createLogger("manager");
66999
+ var log32 = createLogger("manager");
66764
67000
 
66765
67001
  class SessionManager extends EventEmitter4 {
66766
67002
  platforms = new Map;
@@ -66829,7 +67065,7 @@ class SessionManager extends EventEmitter4 {
66829
67065
  markNeedsBump(platformId);
66830
67066
  this.updateStickyMessage();
66831
67067
  });
66832
- log30.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
67068
+ log32.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
66833
67069
  }
66834
67070
  removePlatform(platformId) {
66835
67071
  this.platforms.delete(platformId);
@@ -66845,7 +67081,7 @@ class SessionManager extends EventEmitter4 {
66845
67081
  if (users) {
66846
67082
  users.add(sessionId);
66847
67083
  }
66848
- log30.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
67084
+ log32.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
66849
67085
  }
66850
67086
  unregisterWorktreeUser(worktreePath, sessionId) {
66851
67087
  const users = this.worktreeUsers.get(worktreePath);
@@ -67134,11 +67370,11 @@ class SessionManager extends EventEmitter4 {
67134
67370
  }
67135
67371
  }
67136
67372
  if (sessionsToKill.length === 0) {
67137
- log30.info(`No active sessions to pause for platform ${platformId}`);
67373
+ log32.info(`No active sessions to pause for platform ${platformId}`);
67138
67374
  await this.updateStickyMessage();
67139
67375
  return;
67140
67376
  }
67141
- log30.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
67377
+ log32.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
67142
67378
  for (const session of sessionsToKill) {
67143
67379
  try {
67144
67380
  const fmt = session.platform.getFormatter();
@@ -67154,9 +67390,9 @@ class SessionManager extends EventEmitter4 {
67154
67390
  session.claude.kill();
67155
67391
  this.registry.unregister(session.sessionId);
67156
67392
  this.emitSessionRemove(session.sessionId);
67157
- log30.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
67393
+ log32.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
67158
67394
  } catch (err) {
67159
- log30.warn(`Failed to pause session ${session.threadId}: ${err}`);
67395
+ log32.warn(`Failed to pause session ${session.threadId}: ${err}`);
67160
67396
  }
67161
67397
  }
67162
67398
  for (const session of sessionsToKill) {
@@ -67177,17 +67413,17 @@ class SessionManager extends EventEmitter4 {
67177
67413
  sessionsToResume.push(state);
67178
67414
  }
67179
67415
  if (sessionsToResume.length === 0) {
67180
- log30.info(`No paused sessions to resume for platform ${platformId}`);
67416
+ log32.info(`No paused sessions to resume for platform ${platformId}`);
67181
67417
  await this.updateStickyMessage();
67182
67418
  return;
67183
67419
  }
67184
- log30.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
67420
+ log32.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
67185
67421
  for (const state of sessionsToResume) {
67186
67422
  try {
67187
67423
  await resumeSession(state, this.getContext());
67188
- log30.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
67424
+ log32.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
67189
67425
  } catch (err) {
67190
- log30.warn(`Failed to resume session ${state.threadId}: ${err}`);
67426
+ log32.warn(`Failed to resume session ${state.threadId}: ${err}`);
67191
67427
  }
67192
67428
  }
67193
67429
  await this.updateStickyMessage();
@@ -67199,14 +67435,14 @@ class SessionManager extends EventEmitter4 {
67199
67435
  const sessionTimeoutMs = this.limits.sessionTimeoutMinutes * 60 * 1000;
67200
67436
  const staleIds = this.sessionStore.cleanStale(sessionTimeoutMs * 2);
67201
67437
  if (staleIds.length > 0) {
67202
- log30.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
67438
+ log32.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
67203
67439
  }
67204
67440
  const removedCount = this.sessionStore.cleanHistory();
67205
67441
  if (removedCount > 0) {
67206
- log30.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
67442
+ log32.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
67207
67443
  }
67208
67444
  const persisted = this.sessionStore.load();
67209
- log30.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
67445
+ log32.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
67210
67446
  const excludePostIdsByPlatform = new Map;
67211
67447
  for (const session of persisted.values()) {
67212
67448
  const platformId = session.platformId;
@@ -67226,10 +67462,10 @@ class SessionManager extends EventEmitter4 {
67226
67462
  const excludePostIds = excludePostIdsByPlatform.get(platform.platformId);
67227
67463
  platform.getBotUser().then((botUser) => {
67228
67464
  cleanupOldStickyMessages(platform, botUser.id, true, excludePostIds).catch((err) => {
67229
- log30.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
67465
+ log32.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
67230
67466
  });
67231
67467
  }).catch((err) => {
67232
- log30.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
67468
+ log32.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
67233
67469
  });
67234
67470
  }
67235
67471
  if (persisted.size > 0) {
@@ -67243,10 +67479,10 @@ class SessionManager extends EventEmitter4 {
67243
67479
  }
67244
67480
  }
67245
67481
  if (pausedToSkip.length > 0) {
67246
- log30.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
67482
+ log32.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
67247
67483
  }
67248
67484
  if (activeToResume.length > 0) {
67249
- log30.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
67485
+ log32.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
67250
67486
  for (const state of activeToResume) {
67251
67487
  await resumeSession(state, this.getContext());
67252
67488
  }
@@ -67674,7 +67910,7 @@ Mention me to start a session in this worktree.`, threadId);
67674
67910
  const message = messageBuilder(formatter);
67675
67911
  await post(session, "info", message);
67676
67912
  } catch (err) {
67677
- log30.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
67913
+ log32.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
67678
67914
  }
67679
67915
  }
67680
67916
  }
@@ -67693,7 +67929,7 @@ Mention me to start a session in this worktree.`, threadId);
67693
67929
  session.messageManager?.setPendingUpdatePrompt({ postId: post2.id });
67694
67930
  this.registerPost(post2.id, session.threadId);
67695
67931
  } catch (err) {
67696
- log30.warn(`Failed to post ask message to ${threadId}: ${err}`);
67932
+ log32.warn(`Failed to post ask message to ${threadId}: ${err}`);
67697
67933
  }
67698
67934
  }
67699
67935
  }
@@ -75289,29 +75525,29 @@ function SessionLog({ logs, maxLines = 20 }) {
75289
75525
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
75290
75526
  flexDirection: "column",
75291
75527
  flexShrink: 0,
75292
- children: displayLogs.map((log31) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
75528
+ children: displayLogs.map((log33) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
75293
75529
  flexShrink: 0,
75294
75530
  children: [
75295
75531
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
75296
- color: getColorForLevel(log31.level),
75532
+ color: getColorForLevel(log33.level),
75297
75533
  dimColor: true,
75298
75534
  wrap: "truncate",
75299
75535
  children: [
75300
75536
  "[",
75301
- padComponent(log31.component),
75537
+ padComponent(log33.component),
75302
75538
  "]"
75303
75539
  ]
75304
75540
  }, undefined, true, undefined, this),
75305
75541
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
75306
- color: getColorForLevel(log31.level),
75542
+ color: getColorForLevel(log33.level),
75307
75543
  wrap: "truncate",
75308
75544
  children: [
75309
75545
  " ",
75310
- log31.message
75546
+ log33.message
75311
75547
  ]
75312
75548
  }, undefined, true, undefined, this)
75313
75549
  ]
75314
- }, log31.id, true, undefined, this))
75550
+ }, log33.id, true, undefined, this))
75315
75551
  }, undefined, false, undefined, this);
75316
75552
  }
75317
75553
  // src/ui/components/Footer.tsx
@@ -75835,7 +76071,7 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
75835
76071
  const scrollRef = import_react59.default.useRef(null);
75836
76072
  const { stdout } = use_stdout_default();
75837
76073
  const isDebug = process.env.DEBUG === "1";
75838
- const displayLogs = logs.filter((log31) => isDebug || log31.level !== "debug");
76074
+ const displayLogs = logs.filter((log33) => isDebug || log33.level !== "debug");
75839
76075
  const visibleLogs = displayLogs.slice(-Math.max(maxLines * 3, 100));
75840
76076
  import_react59.default.useEffect(() => {
75841
76077
  const handleResize = () => scrollRef.current?.remeasure();
@@ -75875,25 +76111,25 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
75875
76111
  overflow: "hidden",
75876
76112
  children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ScrollView, {
75877
76113
  ref: scrollRef,
75878
- children: visibleLogs.map((log31) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
76114
+ children: visibleLogs.map((log33) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
75879
76115
  children: [
75880
76116
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
75881
76117
  dimColor: true,
75882
76118
  children: [
75883
76119
  "[",
75884
- padComponent2(log31.component),
76120
+ padComponent2(log33.component),
75885
76121
  "]"
75886
76122
  ]
75887
76123
  }, undefined, true, undefined, this),
75888
76124
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
75889
- color: getLevelColor(log31.level),
76125
+ color: getLevelColor(log33.level),
75890
76126
  children: [
75891
76127
  " ",
75892
- log31.message
76128
+ log33.message
75893
76129
  ]
75894
76130
  }, undefined, true, undefined, this)
75895
76131
  ]
75896
- }, log31.id, true, undefined, this))
76132
+ }, log33.id, true, undefined, this))
75897
76133
  }, undefined, false, undefined, this)
75898
76134
  }, undefined, false, undefined, this);
75899
76135
  }
@@ -76410,10 +76646,10 @@ function useAppState(initialConfig) {
76410
76646
  });
76411
76647
  }, []);
76412
76648
  const getLogsForSession = import_react60.useCallback((sessionId) => {
76413
- return state.logs.filter((log31) => log31.sessionId === sessionId);
76649
+ return state.logs.filter((log33) => log33.sessionId === sessionId);
76414
76650
  }, [state.logs]);
76415
76651
  const getGlobalLogs = import_react60.useCallback(() => {
76416
- return state.logs.filter((log31) => !log31.sessionId);
76652
+ return state.logs.filter((log33) => !log33.sessionId);
76417
76653
  }, [state.logs]);
76418
76654
  const togglePlatformEnabled = import_react60.useCallback((platformId) => {
76419
76655
  let newEnabled = false;
@@ -77420,7 +77656,7 @@ import { EventEmitter as EventEmitter9 } from "events";
77420
77656
  // src/auto-update/checker.ts
77421
77657
  init_logger();
77422
77658
  import { EventEmitter as EventEmitter7 } from "events";
77423
- var log31 = createLogger("checker");
77659
+ var log33 = createLogger("checker");
77424
77660
  var PACKAGE_NAME = "claude-threads";
77425
77661
  function compareVersions(a, b) {
77426
77662
  const partsA = a.replace(/^v/, "").split(".").map(Number);
@@ -77443,13 +77679,13 @@ async function fetchLatestVersion() {
77443
77679
  }
77444
77680
  });
77445
77681
  if (!response.ok) {
77446
- log31.warn(`Failed to fetch latest version: HTTP ${response.status}`);
77682
+ log33.warn(`Failed to fetch latest version: HTTP ${response.status}`);
77447
77683
  return null;
77448
77684
  }
77449
77685
  const data = await response.json();
77450
77686
  return data.version ?? null;
77451
77687
  } catch (err) {
77452
- log31.warn(`Failed to fetch latest version: ${err}`);
77688
+ log33.warn(`Failed to fetch latest version: ${err}`);
77453
77689
  return null;
77454
77690
  }
77455
77691
  }
@@ -77466,38 +77702,38 @@ class UpdateChecker extends EventEmitter7 {
77466
77702
  }
77467
77703
  start() {
77468
77704
  if (!this.config.enabled) {
77469
- log31.debug("Auto-update disabled, not starting checker");
77705
+ log33.debug("Auto-update disabled, not starting checker");
77470
77706
  return;
77471
77707
  }
77472
77708
  setTimeout(() => {
77473
77709
  this.check().catch((err) => {
77474
- log31.warn(`Initial update check failed: ${err}`);
77710
+ log33.warn(`Initial update check failed: ${err}`);
77475
77711
  });
77476
77712
  }, 5000);
77477
77713
  const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
77478
77714
  this.checkInterval = setInterval(() => {
77479
77715
  this.check().catch((err) => {
77480
- log31.warn(`Periodic update check failed: ${err}`);
77716
+ log33.warn(`Periodic update check failed: ${err}`);
77481
77717
  });
77482
77718
  }, intervalMs);
77483
- log31.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
77719
+ log33.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
77484
77720
  }
77485
77721
  stop() {
77486
77722
  if (this.checkInterval) {
77487
77723
  clearInterval(this.checkInterval);
77488
77724
  this.checkInterval = null;
77489
77725
  }
77490
- log31.debug("Update checker stopped");
77726
+ log33.debug("Update checker stopped");
77491
77727
  }
77492
77728
  async check() {
77493
77729
  if (this.isChecking) {
77494
- log31.debug("Check already in progress, skipping");
77730
+ log33.debug("Check already in progress, skipping");
77495
77731
  return this.lastUpdateInfo;
77496
77732
  }
77497
77733
  this.isChecking = true;
77498
77734
  this.emit("check:start");
77499
77735
  try {
77500
- log31.debug("Checking for updates...");
77736
+ log33.debug("Checking for updates...");
77501
77737
  const latestVersion2 = await fetchLatestVersion();
77502
77738
  if (!latestVersion2) {
77503
77739
  this.emit("check:complete", false);
@@ -77514,18 +77750,18 @@ class UpdateChecker extends EventEmitter7 {
77514
77750
  detectedAt: new Date
77515
77751
  };
77516
77752
  if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
77517
- log31.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
77753
+ log33.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
77518
77754
  this.lastUpdateInfo = updateInfo;
77519
77755
  this.emit("update", updateInfo);
77520
77756
  }
77521
77757
  this.emit("check:complete", true);
77522
77758
  return updateInfo;
77523
77759
  }
77524
- log31.debug(`Up to date (v${currentVersion})`);
77760
+ log33.debug(`Up to date (v${currentVersion})`);
77525
77761
  this.emit("check:complete", false);
77526
77762
  return null;
77527
77763
  } catch (err) {
77528
- log31.warn(`Update check failed: ${err}`);
77764
+ log33.warn(`Update check failed: ${err}`);
77529
77765
  this.emit("check:error", err);
77530
77766
  return null;
77531
77767
  } finally {
@@ -77596,7 +77832,7 @@ function isInScheduledWindow(window2) {
77596
77832
  }
77597
77833
 
77598
77834
  // src/auto-update/scheduler.ts
77599
- var log32 = createLogger("scheduler");
77835
+ var log34 = createLogger("scheduler");
77600
77836
 
77601
77837
  class UpdateScheduler extends EventEmitter8 {
77602
77838
  config;
@@ -77620,7 +77856,7 @@ class UpdateScheduler extends EventEmitter8 {
77620
77856
  scheduleUpdate(updateInfo) {
77621
77857
  this.pendingUpdate = updateInfo;
77622
77858
  if (this.config.autoRestartMode === "immediate") {
77623
- log32.info("Immediate mode: triggering update now");
77859
+ log34.info("Immediate mode: triggering update now");
77624
77860
  this.emit("ready", updateInfo);
77625
77861
  return;
77626
77862
  }
@@ -77633,19 +77869,19 @@ class UpdateScheduler extends EventEmitter8 {
77633
77869
  this.scheduledRestartAt = null;
77634
77870
  this.askApprovals.clear();
77635
77871
  this.askStartTime = null;
77636
- log32.debug("Update schedule cancelled");
77872
+ log34.debug("Update schedule cancelled");
77637
77873
  }
77638
77874
  deferUpdate(minutes) {
77639
77875
  const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
77640
77876
  this.scheduledRestartAt = null;
77641
77877
  this.idleStartTime = null;
77642
77878
  this.emit("deferred", deferUntil);
77643
- log32.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
77879
+ log34.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
77644
77880
  return deferUntil;
77645
77881
  }
77646
77882
  recordAskResponse(threadId, approved) {
77647
77883
  this.askApprovals.set(threadId, approved);
77648
- log32.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
77884
+ log34.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
77649
77885
  this.checkAskCondition();
77650
77886
  }
77651
77887
  getScheduledRestartAt() {
@@ -77666,7 +77902,7 @@ class UpdateScheduler extends EventEmitter8 {
77666
77902
  return;
77667
77903
  this.checkCondition();
77668
77904
  this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
77669
- log32.debug(`Started checking for ${this.config.autoRestartMode} condition`);
77905
+ log34.debug(`Started checking for ${this.config.autoRestartMode} condition`);
77670
77906
  }
77671
77907
  stopChecking() {
77672
77908
  if (this.checkTimer) {
@@ -77697,17 +77933,17 @@ class UpdateScheduler extends EventEmitter8 {
77697
77933
  if (activity.activeSessionCount === 0) {
77698
77934
  if (!this.idleStartTime) {
77699
77935
  this.idleStartTime = new Date;
77700
- log32.debug("No active sessions, starting idle timer");
77936
+ log34.debug("No active sessions, starting idle timer");
77701
77937
  }
77702
77938
  const idleMs = Date.now() - this.idleStartTime.getTime();
77703
77939
  const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
77704
77940
  if (idleMs >= requiredMs) {
77705
- log32.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
77941
+ log34.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
77706
77942
  this.triggerCountdown();
77707
77943
  }
77708
77944
  } else {
77709
77945
  if (this.idleStartTime) {
77710
- log32.debug("Sessions became active, resetting idle timer");
77946
+ log34.debug("Sessions became active, resetting idle timer");
77711
77947
  this.idleStartTime = null;
77712
77948
  }
77713
77949
  }
@@ -77718,7 +77954,7 @@ class UpdateScheduler extends EventEmitter8 {
77718
77954
  const quietMs = Date.now() - activity.lastActivityAt.getTime();
77719
77955
  const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
77720
77956
  if (quietMs >= requiredMs && !activity.anySessionBusy) {
77721
- log32.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
77957
+ log34.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
77722
77958
  this.triggerCountdown();
77723
77959
  }
77724
77960
  } else if (activity.activeSessionCount === 0) {
@@ -77728,7 +77964,7 @@ class UpdateScheduler extends EventEmitter8 {
77728
77964
  const idleMs = Date.now() - this.idleStartTime.getTime();
77729
77965
  const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
77730
77966
  if (idleMs >= requiredMs) {
77731
- log32.info("No sessions and quiet timeout reached, triggering update");
77967
+ log34.info("No sessions and quiet timeout reached, triggering update");
77732
77968
  this.triggerCountdown();
77733
77969
  }
77734
77970
  }
@@ -77739,13 +77975,13 @@ class UpdateScheduler extends EventEmitter8 {
77739
77975
  }
77740
77976
  const activity = this.getSessionActivity();
77741
77977
  if (activity.activeSessionCount === 0) {
77742
- log32.info("Within scheduled window and no active sessions, triggering update");
77978
+ log34.info("Within scheduled window and no active sessions, triggering update");
77743
77979
  this.triggerCountdown();
77744
77980
  } else if (activity.lastActivityAt) {
77745
77981
  const quietMs = Date.now() - activity.lastActivityAt.getTime();
77746
77982
  const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
77747
77983
  if (quietMs >= requiredMs && !activity.anySessionBusy) {
77748
- log32.info("Within scheduled window and sessions quiet, triggering update");
77984
+ log34.info("Within scheduled window and sessions quiet, triggering update");
77749
77985
  this.triggerCountdown();
77750
77986
  }
77751
77987
  }
@@ -77753,14 +77989,14 @@ class UpdateScheduler extends EventEmitter8 {
77753
77989
  checkAskCondition() {
77754
77990
  const threadIds = this.getActiveThreadIds();
77755
77991
  if (threadIds.length === 0) {
77756
- log32.info("No active threads, proceeding with update");
77992
+ log34.info("No active threads, proceeding with update");
77757
77993
  this.triggerCountdown();
77758
77994
  return;
77759
77995
  }
77760
77996
  if (!this.askStartTime && this.pendingUpdate) {
77761
77997
  this.askStartTime = new Date;
77762
77998
  this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
77763
- log32.warn(`Failed to post ask message: ${err}`);
77999
+ log34.warn(`Failed to post ask message: ${err}`);
77764
78000
  });
77765
78001
  return;
77766
78002
  }
@@ -77773,12 +78009,12 @@ class UpdateScheduler extends EventEmitter8 {
77773
78009
  denials++;
77774
78010
  }
77775
78011
  if (approvals > threadIds.length / 2) {
77776
- log32.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
78012
+ log34.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
77777
78013
  this.triggerCountdown();
77778
78014
  return;
77779
78015
  }
77780
78016
  if (denials > threadIds.length / 2) {
77781
- log32.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
78017
+ log34.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
77782
78018
  this.deferUpdate(60);
77783
78019
  return;
77784
78020
  }
@@ -77786,7 +78022,7 @@ class UpdateScheduler extends EventEmitter8 {
77786
78022
  const elapsedMs = Date.now() - this.askStartTime.getTime();
77787
78023
  const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
77788
78024
  if (elapsedMs >= timeoutMs) {
77789
- log32.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
78025
+ log34.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
77790
78026
  this.triggerCountdown();
77791
78027
  }
77792
78028
  }
@@ -77806,7 +78042,7 @@ class UpdateScheduler extends EventEmitter8 {
77806
78042
  this.emit("ready", this.pendingUpdate);
77807
78043
  }
77808
78044
  }, 1000);
77809
- log32.info("Update countdown started (60 seconds)");
78045
+ log34.info("Update countdown started (60 seconds)");
77810
78046
  }
77811
78047
  stopCountdown() {
77812
78048
  if (this.countdownTimer) {
@@ -77822,24 +78058,24 @@ import { spawn as spawn4, spawnSync } from "child_process";
77822
78058
  import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "fs";
77823
78059
  import { dirname as dirname8, resolve as resolve6 } from "path";
77824
78060
  import { homedir as homedir5 } from "os";
77825
- var log33 = createLogger("installer");
78061
+ var log35 = createLogger("installer");
77826
78062
  function detectPackageManager() {
77827
78063
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
77828
78064
  const originalInstaller = detectOriginalInstaller();
77829
78065
  if (originalInstaller) {
77830
- log33.debug(`Detected original installer: ${originalInstaller}`);
78066
+ log35.debug(`Detected original installer: ${originalInstaller}`);
77831
78067
  if (originalInstaller === "bun") {
77832
78068
  const bunCheck2 = spawnSync("bun", ["--version"], { stdio: "ignore" });
77833
78069
  if (bunCheck2.status === 0) {
77834
78070
  return { cmd: "bun", isBun: true };
77835
78071
  }
77836
- log33.warn("Originally installed with bun, but bun not found. Falling back to npm.");
78072
+ log35.warn("Originally installed with bun, but bun not found. Falling back to npm.");
77837
78073
  } else {
77838
78074
  const npmCheck2 = spawnSync(npmCmd, ["--version"], { stdio: "ignore" });
77839
78075
  if (npmCheck2.status === 0) {
77840
78076
  return { cmd: npmCmd, isBun: false };
77841
78077
  }
77842
- log33.warn("Originally installed with npm, but npm not found. Falling back to bun.");
78078
+ log35.warn("Originally installed with npm, but npm not found. Falling back to bun.");
77843
78079
  }
77844
78080
  }
77845
78081
  const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore" });
@@ -77890,7 +78126,7 @@ function loadUpdateState() {
77890
78126
  return JSON.parse(content);
77891
78127
  }
77892
78128
  } catch (err) {
77893
- log33.warn(`Failed to load update state: ${err}`);
78129
+ log35.warn(`Failed to load update state: ${err}`);
77894
78130
  }
77895
78131
  return {};
77896
78132
  }
@@ -77901,9 +78137,9 @@ function saveUpdateState(state) {
77901
78137
  mkdirSync4(dir, { recursive: true });
77902
78138
  }
77903
78139
  writeFileSync6(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
77904
- log33.debug("Update state saved");
78140
+ log35.debug("Update state saved");
77905
78141
  } catch (err) {
77906
- log33.warn(`Failed to save update state: ${err}`);
78142
+ log35.warn(`Failed to save update state: ${err}`);
77907
78143
  }
77908
78144
  }
77909
78145
  function clearUpdateState() {
@@ -77912,7 +78148,7 @@ function clearUpdateState() {
77912
78148
  writeFileSync6(STATE_PATH, "{}", "utf-8");
77913
78149
  }
77914
78150
  } catch (err) {
77915
- log33.warn(`Failed to clear update state: ${err}`);
78151
+ log35.warn(`Failed to clear update state: ${err}`);
77916
78152
  }
77917
78153
  }
77918
78154
  function checkJustUpdated() {
@@ -77944,11 +78180,11 @@ function clearRuntimeSettings() {
77944
78180
  }
77945
78181
  }
77946
78182
  async function installVersion(version) {
77947
- log33.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
78183
+ log35.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
77948
78184
  const pm = detectPackageManager();
77949
78185
  if (!pm) {
77950
78186
  const error = "Neither bun nor npm found in PATH. Cannot install update.";
77951
- log33.error(`❌ ${error}`);
78187
+ log35.error(`❌ ${error}`);
77952
78188
  return { success: false, error };
77953
78189
  }
77954
78190
  saveUpdateState({
@@ -77960,7 +78196,7 @@ async function installVersion(version) {
77960
78196
  return new Promise((resolve7) => {
77961
78197
  const { cmd, isBun: isBun3 } = pm;
77962
78198
  const args = ["install", "-g", `${PACKAGE_NAME2}@${version}`];
77963
- log33.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
78199
+ log35.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
77964
78200
  const child = spawn4(cmd, args, {
77965
78201
  stdio: ["ignore", "pipe", "pipe"],
77966
78202
  env: {
@@ -77978,7 +78214,7 @@ async function installVersion(version) {
77978
78214
  });
77979
78215
  child.on("close", (code) => {
77980
78216
  if (code === 0) {
77981
- log33.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
78217
+ log35.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
77982
78218
  saveUpdateState({
77983
78219
  previousVersion: VERSION,
77984
78220
  targetVersion: version,
@@ -77988,20 +78224,20 @@ async function installVersion(version) {
77988
78224
  resolve7({ success: true });
77989
78225
  } else {
77990
78226
  const errorMsg = stderr || stdout || `Exit code: ${code}`;
77991
- log33.error(`❌ Installation failed: ${errorMsg}`);
78227
+ log35.error(`❌ Installation failed: ${errorMsg}`);
77992
78228
  clearUpdateState();
77993
78229
  resolve7({ success: false, error: errorMsg });
77994
78230
  }
77995
78231
  });
77996
78232
  child.on("error", (err) => {
77997
- log33.error(`❌ Failed to spawn npm: ${err}`);
78233
+ log35.error(`❌ Failed to spawn npm: ${err}`);
77998
78234
  clearUpdateState();
77999
78235
  resolve7({ success: false, error: err.message });
78000
78236
  });
78001
78237
  setTimeout(() => {
78002
78238
  if (child.exitCode === null) {
78003
78239
  child.kill();
78004
- log33.error("❌ Installation timed out");
78240
+ log35.error("❌ Installation timed out");
78005
78241
  clearUpdateState();
78006
78242
  resolve7({ success: false, error: "Installation timed out" });
78007
78243
  }
@@ -78043,7 +78279,7 @@ class UpdateInstaller {
78043
78279
  }
78044
78280
 
78045
78281
  // src/auto-update/manager.ts
78046
- var log34 = createLogger("updater");
78282
+ var log36 = createLogger("updater");
78047
78283
 
78048
78284
  class AutoUpdateManager extends EventEmitter9 {
78049
78285
  config;
@@ -78066,23 +78302,23 @@ class AutoUpdateManager extends EventEmitter9 {
78066
78302
  }
78067
78303
  start() {
78068
78304
  if (!this.config.enabled) {
78069
- log34.info("Auto-update is disabled");
78305
+ log36.info("Auto-update is disabled");
78070
78306
  return;
78071
78307
  }
78072
78308
  const updateResult = this.installer.checkJustUpdated();
78073
78309
  if (updateResult) {
78074
- log34.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
78310
+ log36.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
78075
78311
  this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
78076
- log34.warn(`Failed to broadcast update notification: ${err}`);
78312
+ log36.warn(`Failed to broadcast update notification: ${err}`);
78077
78313
  });
78078
78314
  }
78079
78315
  this.checker.start();
78080
- log34.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
78316
+ log36.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
78081
78317
  }
78082
78318
  stop() {
78083
78319
  this.checker.stop();
78084
78320
  this.scheduler.stop();
78085
- log34.debug("Auto-update manager stopped");
78321
+ log36.debug("Auto-update manager stopped");
78086
78322
  }
78087
78323
  getState() {
78088
78324
  return { ...this.state };
@@ -78096,10 +78332,10 @@ class AutoUpdateManager extends EventEmitter9 {
78096
78332
  async forceUpdate() {
78097
78333
  const updateInfo = this.state.updateInfo || await this.checker.check();
78098
78334
  if (!updateInfo) {
78099
- log34.info("No update available");
78335
+ log36.info("No update available");
78100
78336
  return;
78101
78337
  }
78102
- log34.info("Forcing immediate update");
78338
+ log36.info("Forcing immediate update");
78103
78339
  await this.performUpdate(updateInfo);
78104
78340
  }
78105
78341
  deferUpdate(minutes = 60) {
@@ -78154,7 +78390,7 @@ class AutoUpdateManager extends EventEmitter9 {
78154
78390
  await this.callbacks.broadcastUpdate((fmt) => `✅ ${fmt.formatBold("Update installed")} - restarting now. ${fmt.formatItalic("Sessions will resume automatically.")}`).catch(() => {});
78155
78391
  await new Promise((resolve7) => setTimeout(resolve7, 1000));
78156
78392
  await this.callbacks.prepareForRestart();
78157
- log34.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
78393
+ log36.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
78158
78394
  process.stdout.write("\x1B[2J\x1B[H");
78159
78395
  process.stdout.write("\x1B[?25h");
78160
78396
  process.exit(RESTART_EXIT_CODE);