claude-threads 1.8.3 → 1.9.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
@@ -11862,7 +11862,7 @@ import * as fs from "fs/promises";
11862
11862
  import { homedir as homedir4 } from "os";
11863
11863
  async function execGit(args, cwd) {
11864
11864
  const cmd = `git ${args.join(" ")}`;
11865
- log8.debug(`Executing: ${cmd}`);
11865
+ log7.debug(`Executing: ${cmd}`);
11866
11866
  return new Promise((resolve3, reject) => {
11867
11867
  const proc = crossSpawn("git", args, { cwd });
11868
11868
  let stdout = "";
@@ -11875,15 +11875,15 @@ async function execGit(args, cwd) {
11875
11875
  });
11876
11876
  proc.on("close", (code) => {
11877
11877
  if (code === 0) {
11878
- log8.debug(`${cmd} → success`);
11878
+ log7.debug(`${cmd} → success`);
11879
11879
  resolve3(stdout.trim());
11880
11880
  } else {
11881
- log8.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
11881
+ log7.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
11882
11882
  reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
11883
11883
  }
11884
11884
  });
11885
11885
  proc.on("error", (err) => {
11886
- log8.warn(`${cmd} → error: ${err}`);
11886
+ log7.warn(`${cmd} → error: ${err}`);
11887
11887
  reject(err);
11888
11888
  });
11889
11889
  });
@@ -11893,7 +11893,7 @@ async function isGitRepository(dir) {
11893
11893
  await execGit(["rev-parse", "--git-dir"], dir);
11894
11894
  return true;
11895
11895
  } catch (err) {
11896
- log8.debug(`Not a git repository: ${dir} (${err})`);
11896
+ log7.debug(`Not a git repository: ${dir} (${err})`);
11897
11897
  return false;
11898
11898
  }
11899
11899
  }
@@ -12028,7 +12028,7 @@ async function detectWorktreeInfo(workingDir) {
12028
12028
  const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
12029
12029
  const branch = branchOutput?.trim();
12030
12030
  if (!branch) {
12031
- log8.debug(`Could not detect branch for worktree at ${workingDir}`);
12031
+ log7.debug(`Could not detect branch for worktree at ${workingDir}`);
12032
12032
  return null;
12033
12033
  }
12034
12034
  const gitDirOutput = await execGit(["rev-parse", "--git-common-dir"], workingDir);
@@ -12040,45 +12040,45 @@ async function detectWorktreeInfo(workingDir) {
12040
12040
  repoRoot = repoRoot.slice(0, -4);
12041
12041
  }
12042
12042
  }
12043
- log8.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
12043
+ log7.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
12044
12044
  return {
12045
12045
  worktreePath: workingDir,
12046
12046
  branch,
12047
12047
  repoRoot: repoRoot || workingDir
12048
12048
  };
12049
12049
  } catch (err) {
12050
- log8.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
12050
+ log7.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
12051
12051
  return null;
12052
12052
  }
12053
12053
  }
12054
12054
  async function createWorktree(repoRoot, branch, targetDir) {
12055
- log8.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
12055
+ log7.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
12056
12056
  const parentDir = path.dirname(targetDir);
12057
- log8.debug(`Creating parent directory: ${parentDir}`);
12057
+ log7.debug(`Creating parent directory: ${parentDir}`);
12058
12058
  await fs.mkdir(parentDir, { recursive: true });
12059
12059
  const exists = await branchExists(repoRoot, branch);
12060
12060
  if (exists) {
12061
- log8.debug(`Branch '${branch}' exists, adding worktree`);
12061
+ log7.debug(`Branch '${branch}' exists, adding worktree`);
12062
12062
  await execGit(["worktree", "add", targetDir, branch], repoRoot);
12063
12063
  } else {
12064
- log8.debug(`Branch '${branch}' does not exist, creating with worktree`);
12064
+ log7.debug(`Branch '${branch}' does not exist, creating with worktree`);
12065
12065
  await execGit(["worktree", "add", "-b", branch, targetDir], repoRoot);
12066
12066
  }
12067
- log8.info(`Worktree created successfully: ${targetDir}`);
12067
+ log7.info(`Worktree created successfully: ${targetDir}`);
12068
12068
  return targetDir;
12069
12069
  }
12070
12070
  async function removeWorktree(repoRoot, worktreePath) {
12071
- log8.info(`Removing worktree: ${worktreePath}`);
12071
+ log7.info(`Removing worktree: ${worktreePath}`);
12072
12072
  try {
12073
12073
  await execGit(["worktree", "remove", worktreePath], repoRoot);
12074
- log8.debug("Worktree removed cleanly");
12074
+ log7.debug("Worktree removed cleanly");
12075
12075
  } catch (err) {
12076
- log8.debug(`Clean remove failed (${err}), trying force remove`);
12076
+ log7.debug(`Clean remove failed (${err}), trying force remove`);
12077
12077
  await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
12078
12078
  }
12079
- log8.debug("Pruning stale worktree references");
12079
+ log7.debug("Pruning stale worktree references");
12080
12080
  await execGit(["worktree", "prune"], repoRoot);
12081
- log8.info("Worktree removed and pruned successfully");
12081
+ log7.info("Worktree removed and pruned successfully");
12082
12082
  }
12083
12083
  async function findWorktreeByBranch(repoRoot, branch) {
12084
12084
  const worktrees = await listWorktrees(repoRoot);
@@ -12119,14 +12119,14 @@ async function writeMetadataStore(store) {
12119
12119
  await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
12120
12120
  await fs.chmod(METADATA_STORE_PATH, 384);
12121
12121
  } catch (err) {
12122
- log8.warn(`Failed to write worktree metadata store: ${err}`);
12122
+ log7.warn(`Failed to write worktree metadata store: ${err}`);
12123
12123
  }
12124
12124
  }
12125
12125
  async function writeWorktreeMetadata(worktreePath, metadata) {
12126
12126
  const store = await readMetadataStore();
12127
12127
  store[worktreePath] = metadata;
12128
12128
  await writeMetadataStore(store);
12129
- log8.debug(`Wrote worktree metadata for: ${worktreePath}`);
12129
+ log7.debug(`Wrote worktree metadata for: ${worktreePath}`);
12130
12130
  }
12131
12131
  async function readWorktreeMetadata(worktreePath) {
12132
12132
  const store = await readMetadataStore();
@@ -12149,14 +12149,14 @@ async function removeWorktreeMetadata(worktreePath) {
12149
12149
  if (store[worktreePath]) {
12150
12150
  delete store[worktreePath];
12151
12151
  await writeMetadataStore(store);
12152
- log8.debug(`Removed worktree metadata for: ${worktreePath}`);
12152
+ log7.debug(`Removed worktree metadata for: ${worktreePath}`);
12153
12153
  }
12154
12154
  }
12155
- var log8, WORKTREES_DIR, METADATA_STORE_PATH;
12155
+ var log7, WORKTREES_DIR, METADATA_STORE_PATH;
12156
12156
  var init_worktree = __esm(() => {
12157
12157
  init_spawn();
12158
12158
  init_logger();
12159
- log8 = createLogger("git-wt");
12159
+ log7 = createLogger("git-wt");
12160
12160
  WORKTREES_DIR = path.join(homedir4(), ".claude-threads", "worktrees");
12161
12161
  METADATA_STORE_PATH = path.join(homedir4(), ".claude-threads", "worktree-metadata.json");
12162
12162
  });
@@ -19496,7 +19496,7 @@ var require_react_reconciler_development = __commonJS((exports, module) => {
19496
19496
  return hook.checkDCE ? true : false;
19497
19497
  }
19498
19498
  function setIsStrictModeForDevtools(newIsStrictMode) {
19499
- typeof log31 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
19499
+ typeof log30 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
19500
19500
  if (injectedHook && typeof injectedHook.setStrictMode === "function")
19501
19501
  try {
19502
19502
  injectedHook.setStrictMode(rendererID, newIsStrictMode);
@@ -27580,7 +27580,7 @@ Check the render method of %s.`, getComponentNameFromFiber(current) || "Unknown"
27580
27580
  var fiberStack = [];
27581
27581
  var index$jscomp$0 = -1, emptyContextObject = {};
27582
27582
  Object.freeze(emptyContextObject);
27583
- 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, log31 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
27583
+ 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, log30 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
27584
27584
  if (typeof performance === "object" && typeof performance.now === "function") {
27585
27585
  var localPerformance = performance;
27586
27586
  var getCurrentTime = function() {
@@ -47101,7 +47101,7 @@ var {
47101
47101
  Help
47102
47102
  } = import__.default;
47103
47103
 
47104
- // src/config/migration.ts
47104
+ // src/config/index.ts
47105
47105
  import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
47106
47106
  import { resolve, dirname } from "path";
47107
47107
  import { homedir } from "os";
@@ -49799,7 +49799,8 @@ var LIMITS_DEFAULTS = {
49799
49799
  cleanupIntervalMinutes: 60,
49800
49800
  maxWorktreeAgeHours: 24,
49801
49801
  cleanupWorktrees: true,
49802
- permissionTimeoutSeconds: 120
49802
+ permissionTimeoutSeconds: 120,
49803
+ flushDelayMs: 500
49803
49804
  };
49804
49805
  function resolveLimits(limits) {
49805
49806
  const envMaxSessions = process.env.MAX_SESSIONS ? parseInt(process.env.MAX_SESSIONS, 10) : undefined;
@@ -49811,11 +49812,52 @@ function resolveLimits(limits) {
49811
49812
  cleanupIntervalMinutes: limits?.cleanupIntervalMinutes ?? LIMITS_DEFAULTS.cleanupIntervalMinutes,
49812
49813
  maxWorktreeAgeHours: limits?.maxWorktreeAgeHours ?? LIMITS_DEFAULTS.maxWorktreeAgeHours,
49813
49814
  cleanupWorktrees: limits?.cleanupWorktrees ?? LIMITS_DEFAULTS.cleanupWorktrees,
49814
- permissionTimeoutSeconds: limits?.permissionTimeoutSeconds ?? LIMITS_DEFAULTS.permissionTimeoutSeconds
49815
+ permissionTimeoutSeconds: limits?.permissionTimeoutSeconds ?? LIMITS_DEFAULTS.permissionTimeoutSeconds,
49816
+ flushDelayMs: limits?.flushDelayMs ?? LIMITS_DEFAULTS.flushDelayMs
49815
49817
  };
49816
49818
  }
49819
+ function resolvePermissionMode(opts) {
49820
+ if (opts.permissionMode)
49821
+ return opts.permissionMode;
49822
+ if (opts.skipPermissions === true)
49823
+ return "bypass";
49824
+ if (opts.skipPermissions === false)
49825
+ return "default";
49826
+ return "default";
49827
+ }
49828
+ var MODE_INFO = {
49829
+ default: {
49830
+ icon: "\uD83D\uDD10",
49831
+ label: "Default",
49832
+ description: "Every tool-use prompts for approval."
49833
+ },
49834
+ auto: {
49835
+ icon: "⚡",
49836
+ label: "Auto",
49837
+ description: "Claude classifier auto-approves low-risk tools; high-risk still prompts."
49838
+ },
49839
+ bypass: {
49840
+ icon: "⚠️",
49841
+ label: "Bypass",
49842
+ description: "No prompts — every tool-use is allowed."
49843
+ }
49844
+ };
49845
+ function permissionModeDisplay(mode) {
49846
+ const info = MODE_INFO[mode];
49847
+ return { icon: info.icon, label: info.label, chip: `${info.icon} ${info.label}` };
49848
+ }
49849
+ function permissionModeDescription(mode) {
49850
+ return MODE_INFO[mode].description;
49851
+ }
49852
+ function effectivePermissionMode(input) {
49853
+ if (input.override)
49854
+ return input.override;
49855
+ if (input.sessionHasInteractiveOverride)
49856
+ return "default";
49857
+ return input.botWideMode;
49858
+ }
49817
49859
 
49818
- // src/config/migration.ts
49860
+ // src/config/index.ts
49819
49861
  var CONFIG_PATH = resolve(homedir(), ".config", "claude-threads", "config.yaml");
49820
49862
  function loadConfigWithMigration() {
49821
49863
  if (existsSync(CONFIG_PATH)) {
@@ -50034,6 +50076,26 @@ function validateClaudeCli() {
50034
50076
  }
50035
50077
 
50036
50078
  // src/onboarding.ts
50079
+ var PERMISSION_MODE_CHOICES = [
50080
+ {
50081
+ title: "Auto (recommended)",
50082
+ value: "auto",
50083
+ description: "Classifier auto-approves low-risk; high-risk tools still prompt via reactions"
50084
+ },
50085
+ {
50086
+ title: "Default",
50087
+ value: "default",
50088
+ description: "Every tool-use prompts — strictest mode, can be noisy for everyday work"
50089
+ },
50090
+ {
50091
+ title: "Bypass",
50092
+ value: "bypass",
50093
+ description: "No prompts, all tools allowed — use only in trusted environments"
50094
+ }
50095
+ ];
50096
+ function permissionModeChoiceIndex(mode) {
50097
+ return PERMISSION_MODE_CHOICES.findIndex((c) => c.value === mode);
50098
+ }
50037
50099
  var __dirname2 = dirname2(fileURLToPath(import.meta.url));
50038
50100
  var SLACK_MANIFEST_PATH = join2(__dirname2, "..", "docs", "slack-app-manifest.yaml");
50039
50101
  var onCancel = () => {
@@ -50600,14 +50662,14 @@ async function showConfigSummary(config) {
50600
50662
  console.log(dim(` Bot: @${mm.botName}`));
50601
50663
  const allowedUsers = mm.allowedUsers.length > 0 ? mm.allowedUsers.join(", ") : "ANYONE (⚠️ no restrictions)";
50602
50664
  console.log(dim(` Allowed Users: ${allowedUsers}`));
50603
- console.log(dim(` Auto-approve: ${mm.skipPermissions ? "Yes" : "No (interactive)"}`));
50665
+ console.log(dim(` Permission Mode: ${permissionModeDisplay(resolvePermissionMode({ permissionMode: mm.permissionMode, skipPermissions: mm.skipPermissions })).chip}`));
50604
50666
  } else {
50605
50667
  const slack = platform;
50606
50668
  console.log(dim(` Channel: ${slack.channelId}`));
50607
50669
  console.log(dim(` Bot: @${slack.botName}`));
50608
50670
  const allowedUsers = slack.allowedUsers.length > 0 ? slack.allowedUsers.join(", ") : "ANYONE (⚠️ no restrictions)";
50609
50671
  console.log(dim(` Allowed Users: ${allowedUsers}`));
50610
- console.log(dim(` Auto-approve: ${slack.skipPermissions ? "Yes" : "No (interactive)"}`));
50672
+ console.log(dim(` Permission Mode: ${permissionModeDisplay(resolvePermissionMode({ permissionMode: slack.permissionMode, skipPermissions: slack.skipPermissions })).chip}`));
50611
50673
  }
50612
50674
  }
50613
50675
  const hasAdvancedSettings = config.limits || config.threadLogs || config.keepAlive === false;
@@ -50799,7 +50861,10 @@ async function setupMattermostPlatform(id, existing) {
50799
50861
  let lastChannelId = existingMattermost?.channelId || "";
50800
50862
  let lastBotName = existingMattermost?.botName || "claude-code";
50801
50863
  let lastAllowedUsers = existingMattermost?.allowedUsers?.join(",") || "";
50802
- let lastRequireApproval = existingMattermost ? !existingMattermost.skipPermissions : true;
50864
+ let lastPermissionMode = existingMattermost ? resolvePermissionMode({
50865
+ permissionMode: existingMattermost.permissionMode,
50866
+ skipPermissions: existingMattermost.skipPermissions
50867
+ }) : "auto";
50803
50868
  while (true) {
50804
50869
  console.log("");
50805
50870
  console.log(dim(" Now enter your Mattermost credentials:"));
@@ -50897,12 +50962,12 @@ async function setupMattermostPlatform(id, existing) {
50897
50962
  allowedUsersConfirmed = true;
50898
50963
  }
50899
50964
  }
50900
- const { requireApproval } = await import_prompts.default({
50901
- type: "confirm",
50902
- name: "requireApproval",
50903
- message: "Require approval for Claude actions?",
50904
- initial: lastRequireApproval,
50905
- hint: "Yes = approve via reactions (recommended), No = auto-approve everything"
50965
+ const { permissionMode } = await import_prompts.default({
50966
+ type: "select",
50967
+ name: "permissionMode",
50968
+ message: "Permission mode for Claude tool-uses?",
50969
+ choices: PERMISSION_MODE_CHOICES,
50970
+ initial: permissionModeChoiceIndex(lastPermissionMode)
50906
50971
  }, { onCancel });
50907
50972
  lastUrl = basicSettings.url;
50908
50973
  lastDisplayName = basicSettings.displayName;
@@ -50910,7 +50975,7 @@ async function setupMattermostPlatform(id, existing) {
50910
50975
  lastChannelId = basicSettings.channelId;
50911
50976
  lastBotName = basicSettings.botName;
50912
50977
  lastAllowedUsers = allowedUsers.join(",");
50913
- lastRequireApproval = requireApproval;
50978
+ lastPermissionMode = permissionMode;
50914
50979
  console.log("");
50915
50980
  console.log(dim(" Validating credentials..."));
50916
50981
  const validationResult = await validateMattermostCredentials(basicSettings.url, finalToken, basicSettings.channelId);
@@ -50969,7 +51034,7 @@ async function setupMattermostPlatform(id, existing) {
50969
51034
  channelId: basicSettings.channelId,
50970
51035
  botName: basicSettings.botName,
50971
51036
  allowedUsers,
50972
- skipPermissions: !requireApproval
51037
+ permissionMode: lastPermissionMode
50973
51038
  };
50974
51039
  }
50975
51040
  }
@@ -51082,7 +51147,10 @@ async function setupSlackPlatform(id, existing) {
51082
51147
  let lastChannelId = existingSlack?.channelId || "";
51083
51148
  let lastBotName = existingSlack?.botName || "claude";
51084
51149
  let lastAllowedUsers = existingSlack?.allowedUsers?.join(",") || "";
51085
- let lastRequireApproval = existingSlack ? !existingSlack.skipPermissions : true;
51150
+ let lastPermissionMode = existingSlack ? resolvePermissionMode({
51151
+ permissionMode: existingSlack.permissionMode,
51152
+ skipPermissions: existingSlack.skipPermissions
51153
+ }) : "auto";
51086
51154
  while (true) {
51087
51155
  console.log("");
51088
51156
  console.log(dim(" Now enter your Slack credentials:"));
@@ -51191,12 +51259,12 @@ async function setupSlackPlatform(id, existing) {
51191
51259
  allowedUsersConfirmed = true;
51192
51260
  }
51193
51261
  }
51194
- const { requireApproval } = await import_prompts.default({
51195
- type: "confirm",
51196
- name: "requireApproval",
51197
- message: "Require approval for Claude actions?",
51198
- initial: lastRequireApproval,
51199
- hint: "Yes = approve via reactions (recommended), No = auto-approve everything"
51262
+ const { permissionMode } = await import_prompts.default({
51263
+ type: "select",
51264
+ name: "permissionMode",
51265
+ message: "Permission mode for Claude tool-uses?",
51266
+ choices: PERMISSION_MODE_CHOICES,
51267
+ initial: permissionModeChoiceIndex(lastPermissionMode)
51200
51268
  }, { onCancel });
51201
51269
  lastDisplayName = basicSettings.displayName;
51202
51270
  lastBotToken = finalBotToken;
@@ -51204,7 +51272,7 @@ async function setupSlackPlatform(id, existing) {
51204
51272
  lastChannelId = basicSettings.channelId;
51205
51273
  lastBotName = basicSettings.botName;
51206
51274
  lastAllowedUsers = allowedUsers.join(",");
51207
- lastRequireApproval = requireApproval;
51275
+ lastPermissionMode = permissionMode;
51208
51276
  console.log("");
51209
51277
  console.log(dim(" Validating credentials..."));
51210
51278
  const validationResult = await validateSlackCredentials(finalBotToken, finalAppToken, basicSettings.channelId);
@@ -51269,7 +51337,7 @@ async function setupSlackPlatform(id, existing) {
51269
51337
  channelId: basicSettings.channelId,
51270
51338
  botName: basicSettings.botName,
51271
51339
  allowedUsers,
51272
- skipPermissions: !requireApproval
51340
+ permissionMode: lastPermissionMode
51273
51341
  };
51274
51342
  }
51275
51343
  }
@@ -52290,7 +52358,7 @@ class SlackClient extends BasePlatformClient {
52290
52358
  this.channelId = platformConfig.channelId;
52291
52359
  this.botName = platformConfig.botName;
52292
52360
  this.allowedUsers = platformConfig.allowedUsers;
52293
- this.skipPermissions = platformConfig.skipPermissions;
52361
+ this.skipPermissions = platformConfig.skipPermissions ?? false;
52294
52362
  this.apiUrl = platformConfig.apiUrl || "https://slack.com/api";
52295
52363
  }
52296
52364
  normalizePlatformUser(slackUser) {
@@ -52914,12 +52982,12 @@ class SlackClient extends BasePlatformClient {
52914
52982
  return this.normalizePlatformFile(response.file);
52915
52983
  }
52916
52984
  }
52917
- // src/mattermost/api.ts
52985
+ // src/platform/mattermost/permission-api.ts
52918
52986
  init_logger();
52919
- var log4 = createLogger("mm-api");
52987
+ var apiLog = createLogger("mm-api");
52920
52988
  async function mattermostApi(config, method, path, body) {
52921
52989
  const url = `${config.url}/api/v4${path}`;
52922
- log4.debug(`API ${method} ${path}`);
52990
+ apiLog.debug(`API ${method} ${path}`);
52923
52991
  const response = await fetch(url, {
52924
52992
  method,
52925
52993
  headers: {
@@ -52930,10 +52998,10 @@ async function mattermostApi(config, method, path, body) {
52930
52998
  });
52931
52999
  if (!response.ok) {
52932
53000
  const text = await response.text();
52933
- log4.warn(`API ${method} ${path} failed: ${response.status} ${text.substring(0, 100)}`);
53001
+ apiLog.warn(`API ${method} ${path} failed: ${response.status} ${text.substring(0, 100)}`);
52934
53002
  throw new Error(`Mattermost API error ${response.status}: ${text}`);
52935
53003
  }
52936
- log4.debug(`API ${method} ${path} → ${response.status}`);
53004
+ apiLog.debug(`API ${method} ${path} → ${response.status}`);
52937
53005
  return response.json();
52938
53006
  }
52939
53007
  async function getMe(config) {
@@ -52943,7 +53011,7 @@ async function getUser(config, userId) {
52943
53011
  try {
52944
53012
  return await mattermostApi(config, "GET", `/users/${userId}`);
52945
53013
  } catch (err) {
52946
- log4.debug(`Failed to get user ${userId}: ${err}`);
53014
+ apiLog.debug(`Failed to get user ${userId}: ${err}`);
52947
53015
  return null;
52948
53016
  }
52949
53017
  }
@@ -52954,7 +53022,7 @@ async function createPost(config, channelId, message, rootId) {
52954
53022
  root_id: rootId
52955
53023
  });
52956
53024
  }
52957
- async function updatePost(config, postId, message) {
53025
+ async function updatePostRaw(config, postId, message) {
52958
53026
  return mattermostApi(config, "PUT", `/posts/${postId}`, {
52959
53027
  id: postId,
52960
53028
  message
@@ -52967,25 +53035,23 @@ async function addReaction(config, postId, userId, emojiName) {
52967
53035
  emoji_name: emojiName
52968
53036
  });
52969
53037
  }
52970
- function isUserAllowed(username, allowList) {
53038
+ function isUserInAllowList(username, allowList) {
52971
53039
  if (allowList.length === 0)
52972
53040
  return true;
52973
53041
  return allowList.includes(username);
52974
53042
  }
52975
- async function createInteractivePost(config, channelId, message, reactions, rootId, botUserId) {
53043
+ async function createInteractivePostInternal(config, channelId, message, reactions, rootId, botUserId) {
52976
53044
  const post = await createPost(config, channelId, message, rootId);
52977
53045
  for (const emoji of reactions) {
52978
53046
  try {
52979
53047
  await addReaction(config, post.id, botUserId, emoji);
52980
53048
  } catch (err) {
52981
- log4.warn(`Failed to add reaction ${emoji}: ${err}`);
53049
+ apiLog.warn(`Failed to add reaction ${emoji}: ${err}`);
52982
53050
  }
52983
53051
  }
52984
53052
  return post;
52985
53053
  }
52986
53054
 
52987
- // src/platform/mattermost/permission-api.ts
52988
- init_logger();
52989
53055
  class MattermostPermissionApi {
52990
53056
  apiConfig;
52991
53057
  config;
@@ -53026,18 +53092,18 @@ class MattermostPermissionApi {
53026
53092
  }
53027
53093
  }
53028
53094
  isUserAllowed(username) {
53029
- return isUserAllowed(username, this.config.allowedUsers);
53095
+ return isUserInAllowList(username, this.config.allowedUsers);
53030
53096
  }
53031
53097
  async createInteractivePost(message, reactions, threadId) {
53032
53098
  mcpLogger.debug(`Creating interactive post with ${reactions.length} reaction options`);
53033
53099
  const botUserId = await this.getBotUserId();
53034
- const post = await createInteractivePost(this.apiConfig, this.config.channelId, message, reactions, threadId, botUserId);
53100
+ const post = await createInteractivePostInternal(this.apiConfig, this.config.channelId, message, reactions, threadId, botUserId);
53035
53101
  mcpLogger.debug(`Created post ${formatShortId(post.id)}`);
53036
53102
  return { id: post.id };
53037
53103
  }
53038
53104
  async updatePost(postId, message) {
53039
53105
  mcpLogger.debug(`Updating post ${postId.substring(0, 8)}`);
53040
- await updatePost(this.apiConfig, postId, message);
53106
+ await updatePostRaw(this.apiConfig, postId, message);
53041
53107
  }
53042
53108
  async waitForReaction(postId, botUserId, timeoutMs) {
53043
53109
  return new Promise((resolve3) => {
@@ -53331,7 +53397,7 @@ init_logger();
53331
53397
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync, chmodSync as chmodSync2 } from "fs";
53332
53398
  import { homedir as homedir2 } from "os";
53333
53399
  import { join as join3 } from "path";
53334
- var log5 = createLogger("persist");
53400
+ var log4 = createLogger("persist");
53335
53401
  var STORE_VERSION = 2;
53336
53402
  var DEFAULT_CONFIG_DIR = join3(homedir2(), ".config", "claude-threads");
53337
53403
  var DEFAULT_SESSIONS_FILE = join3(DEFAULT_CONFIG_DIR, "sessions.json");
@@ -53356,13 +53422,13 @@ class SessionStore {
53356
53422
  load() {
53357
53423
  const sessions = new Map;
53358
53424
  if (!existsSync5(this.sessionsFile)) {
53359
- log5.debug("No sessions file found");
53425
+ log4.debug("No sessions file found");
53360
53426
  return sessions;
53361
53427
  }
53362
53428
  try {
53363
53429
  const data = this.loadRaw();
53364
53430
  if (data.version === 1) {
53365
- log5.info("Migrating sessions from v1 to v2 (adding platformId)");
53431
+ log4.info("Migrating sessions from v1 to v2 (adding platformId)");
53366
53432
  const newSessions = {};
53367
53433
  for (const [_oldKey, session] of Object.entries(data.sessions)) {
53368
53434
  const v1Session = session;
@@ -53376,7 +53442,7 @@ class SessionStore {
53376
53442
  data.version = 2;
53377
53443
  this.writeAtomic(data);
53378
53444
  } else if (data.version !== STORE_VERSION) {
53379
- log5.warn(`Sessions file version ${data.version} not supported, starting fresh`);
53445
+ log4.warn(`Sessions file version ${data.version} not supported, starting fresh`);
53380
53446
  return sessions;
53381
53447
  }
53382
53448
  for (const session of Object.values(data.sessions)) {
@@ -53385,9 +53451,9 @@ class SessionStore {
53385
53451
  const sessionId = `${session.platformId}:${session.threadId}`;
53386
53452
  sessions.set(sessionId, session);
53387
53453
  }
53388
- log5.debug(`Loaded ${sessions.size} active session(s)`);
53454
+ log4.debug(`Loaded ${sessions.size} active session(s)`);
53389
53455
  } catch (err) {
53390
- log5.error(`Failed to load sessions: ${err}`);
53456
+ log4.error(`Failed to load sessions: ${err}`);
53391
53457
  }
53392
53458
  return sessions;
53393
53459
  }
@@ -53396,7 +53462,7 @@ class SessionStore {
53396
53462
  data.sessions[sessionId] = session;
53397
53463
  this.writeAtomic(data);
53398
53464
  const shortId = sessionId.substring(0, 20);
53399
- log5.debug(`Saved session ${shortId}...`);
53465
+ log4.debug(`Saved session ${shortId}...`);
53400
53466
  }
53401
53467
  remove(sessionId) {
53402
53468
  const data = this.loadRaw();
@@ -53404,7 +53470,7 @@ class SessionStore {
53404
53470
  delete data.sessions[sessionId];
53405
53471
  this.writeAtomic(data);
53406
53472
  const shortId = sessionId.substring(0, 20);
53407
- log5.debug(`Removed session ${shortId}...`);
53473
+ log4.debug(`Removed session ${shortId}...`);
53408
53474
  }
53409
53475
  }
53410
53476
  softDelete(sessionId) {
@@ -53413,7 +53479,7 @@ class SessionStore {
53413
53479
  data.sessions[sessionId].cleanedAt = new Date().toISOString();
53414
53480
  this.writeAtomic(data);
53415
53481
  const shortId = sessionId.substring(0, 20);
53416
- log5.debug(`Soft-deleted session ${shortId}...`);
53482
+ log4.debug(`Soft-deleted session ${shortId}...`);
53417
53483
  }
53418
53484
  }
53419
53485
  cleanStale(maxAgeMs) {
@@ -53431,7 +53497,7 @@ class SessionStore {
53431
53497
  }
53432
53498
  if (staleIds.length > 0) {
53433
53499
  this.writeAtomic(data);
53434
- log5.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
53500
+ log4.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
53435
53501
  }
53436
53502
  return staleIds;
53437
53503
  }
@@ -53450,7 +53516,7 @@ class SessionStore {
53450
53516
  }
53451
53517
  if (removedCount > 0) {
53452
53518
  this.writeAtomic(data);
53453
- log5.debug(`Permanently removed ${removedCount} old session(s) from history`);
53519
+ log4.debug(`Permanently removed ${removedCount} old session(s) from history`);
53454
53520
  }
53455
53521
  return removedCount;
53456
53522
  }
@@ -53477,7 +53543,7 @@ class SessionStore {
53477
53543
  clear() {
53478
53544
  const data = this.loadRaw();
53479
53545
  this.writeAtomic({ version: STORE_VERSION, sessions: {}, stickyPostIds: data.stickyPostIds });
53480
- log5.debug("Cleared all sessions");
53546
+ log4.debug("Cleared all sessions");
53481
53547
  }
53482
53548
  saveStickyPostId(platformId, postId) {
53483
53549
  const data = this.loadRaw();
@@ -53486,7 +53552,7 @@ class SessionStore {
53486
53552
  }
53487
53553
  data.stickyPostIds[platformId] = postId;
53488
53554
  this.writeAtomic(data);
53489
- log5.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
53555
+ log4.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
53490
53556
  }
53491
53557
  getStickyPostIds() {
53492
53558
  const data = this.loadRaw();
@@ -53497,7 +53563,7 @@ class SessionStore {
53497
53563
  if (data.stickyPostIds && data.stickyPostIds[platformId]) {
53498
53564
  delete data.stickyPostIds[platformId];
53499
53565
  this.writeAtomic(data);
53500
- log5.debug(`Removed sticky post ID for ${platformId}`);
53566
+ log4.debug(`Removed sticky post ID for ${platformId}`);
53501
53567
  }
53502
53568
  }
53503
53569
  getPlatformEnabledState() {
@@ -53515,7 +53581,7 @@ class SessionStore {
53515
53581
  }
53516
53582
  data.platformEnabledState[platformId] = enabled;
53517
53583
  this.writeAtomic(data);
53518
- log5.debug(`Set platform ${platformId} enabled state to ${enabled}`);
53584
+ log4.debug(`Set platform ${platformId} enabled state to ${enabled}`);
53519
53585
  }
53520
53586
  findByThread(platformId, threadId) {
53521
53587
  const sessionId = `${platformId}:${threadId}`;
@@ -53566,9 +53632,10 @@ class SessionStore {
53566
53632
  chmodSync2(this.sessionsFile, 384);
53567
53633
  }
53568
53634
  }
53635
+
53569
53636
  // src/claude/account-pool.ts
53570
53637
  init_logger();
53571
- var log6 = createLogger("account-pool");
53638
+ var log5 = createLogger("account-pool");
53572
53639
 
53573
53640
  class AccountPool {
53574
53641
  accounts;
@@ -53580,11 +53647,11 @@ class AccountPool {
53580
53647
  this.accounts = (accounts ?? []).filter((acc) => {
53581
53648
  const hasAuth = !!acc.home || !!acc.apiKey;
53582
53649
  if (!hasAuth) {
53583
- log6.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
53650
+ log5.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
53584
53651
  return false;
53585
53652
  }
53586
53653
  if (acc.home && acc.apiKey) {
53587
- log6.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
53654
+ log5.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
53588
53655
  return false;
53589
53656
  }
53590
53657
  return true;
@@ -53609,7 +53676,7 @@ class AccountPool {
53609
53676
  this.incrementActive(preferred.id);
53610
53677
  return preferred;
53611
53678
  }
53612
- log6.warn(`Preferred account "${preferredId}" not in pool — falling back to round-robin`);
53679
+ log5.warn(`Preferred account "${preferredId}" not in pool — falling back to round-robin`);
53613
53680
  }
53614
53681
  const now = Date.now();
53615
53682
  const n = this.accounts.length;
@@ -53623,7 +53690,7 @@ class AccountPool {
53623
53690
  return candidate;
53624
53691
  }
53625
53692
  }
53626
- log6.warn(`All ${n} accounts are in rate-limit cooldown`);
53693
+ log5.warn(`All ${n} accounts are in rate-limit cooldown`);
53627
53694
  return null;
53628
53695
  }
53629
53696
  release(accountId) {
@@ -53634,14 +53701,14 @@ class AccountPool {
53634
53701
  }
53635
53702
  markCooling(accountId, untilEpochMs) {
53636
53703
  if (!this.byId.has(accountId)) {
53637
- log6.warn(`markCooling called for unknown account "${accountId}"`);
53704
+ log5.warn(`markCooling called for unknown account "${accountId}"`);
53638
53705
  return;
53639
53706
  }
53640
53707
  const existing = this.coolingUntil.get(accountId) ?? 0;
53641
53708
  if (untilEpochMs > existing) {
53642
53709
  this.coolingUntil.set(accountId, untilEpochMs);
53643
53710
  const minutes = Math.ceil((untilEpochMs - Date.now()) / 60000);
53644
- log6.info(`Account "${accountId}" cooling for ~${minutes}min`);
53711
+ log5.info(`Account "${accountId}" cooling for ~${minutes}min`);
53645
53712
  }
53646
53713
  }
53647
53714
  get(accountId) {
@@ -53678,7 +53745,7 @@ init_logger();
53678
53745
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, appendFileSync, readdirSync, statSync, unlinkSync, rmdirSync, readFileSync as readFileSync5, chmodSync as chmodSync3 } from "fs";
53679
53746
  import { homedir as homedir3 } from "os";
53680
53747
  import { join as join4, dirname as dirname4 } from "path";
53681
- var log7 = createLogger("thread-log");
53748
+ var log6 = createLogger("thread-log");
53682
53749
  var LOGS_BASE_DIR = join4(homedir3(), ".claude-threads", "logs");
53683
53750
 
53684
53751
  class ThreadLoggerImpl {
@@ -53708,7 +53775,7 @@ class ThreadLoggerImpl {
53708
53775
  this.flushTimer = setInterval(() => {
53709
53776
  this.flushSync();
53710
53777
  }, this.flushIntervalMs);
53711
- log7.debug(`Thread logger initialized: ${this.logPath}`);
53778
+ log6.debug(`Thread logger initialized: ${this.logPath}`);
53712
53779
  }
53713
53780
  }
53714
53781
  isEnabled() {
@@ -53822,7 +53889,7 @@ class ThreadLoggerImpl {
53822
53889
  this.flushTimer = null;
53823
53890
  }
53824
53891
  this.flushSync();
53825
- log7.debug(`Thread logger closed: ${this.logPath}`);
53892
+ log6.debug(`Thread logger closed: ${this.logPath}`);
53826
53893
  }
53827
53894
  addEntry(entry) {
53828
53895
  this.buffer.push(entry);
@@ -53844,7 +53911,7 @@ class ThreadLoggerImpl {
53844
53911
  }
53845
53912
  this.buffer = [];
53846
53913
  } catch (err) {
53847
- log7.error(`Failed to flush thread log: ${err}`);
53914
+ log6.error(`Failed to flush thread log: ${err}`);
53848
53915
  }
53849
53916
  }
53850
53917
  }
@@ -53895,25 +53962,25 @@ function cleanupOldLogs(retentionDays = 30) {
53895
53962
  if (fileStat.mtimeMs < cutoffMs) {
53896
53963
  unlinkSync(filePath);
53897
53964
  deletedCount++;
53898
- log7.debug(`Deleted old log file: ${filePath}`);
53965
+ log6.debug(`Deleted old log file: ${filePath}`);
53899
53966
  }
53900
53967
  } catch (err) {
53901
- log7.warn(`Failed to check/delete log file ${filePath}: ${err}`);
53968
+ log6.warn(`Failed to check/delete log file ${filePath}: ${err}`);
53902
53969
  }
53903
53970
  }
53904
53971
  try {
53905
53972
  const remaining = readdirSync(platformDir);
53906
53973
  if (remaining.length === 0) {
53907
53974
  rmdirSync(platformDir);
53908
- log7.debug(`Removed empty platform log directory: ${platformDir}`);
53975
+ log6.debug(`Removed empty platform log directory: ${platformDir}`);
53909
53976
  }
53910
53977
  } catch {}
53911
53978
  }
53912
53979
  if (deletedCount > 0) {
53913
- log7.info(`Cleaned up ${deletedCount} old log file(s)`);
53980
+ log6.info(`Cleaned up ${deletedCount} old log file(s)`);
53914
53981
  }
53915
53982
  } catch (err) {
53916
- log7.error(`Failed to clean up old logs: ${err}`);
53983
+ log6.error(`Failed to clean up old logs: ${err}`);
53917
53984
  }
53918
53985
  return deletedCount;
53919
53986
  }
@@ -53922,16 +53989,16 @@ function getLogFilePath(platformId, sessionId) {
53922
53989
  }
53923
53990
  function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
53924
53991
  const logPath = getLogFilePath(platformId, sessionId);
53925
- log7.debug(`Reading log entries from: ${logPath}`);
53992
+ log6.debug(`Reading log entries from: ${logPath}`);
53926
53993
  if (!existsSync6(logPath)) {
53927
- log7.debug(`Log file does not exist: ${logPath}`);
53994
+ log6.debug(`Log file does not exist: ${logPath}`);
53928
53995
  return [];
53929
53996
  }
53930
53997
  try {
53931
53998
  const content = readFileSync5(logPath, "utf8");
53932
53999
  const lines = content.trim().split(`
53933
54000
  `);
53934
- log7.debug(`Log file has ${lines.length} lines`);
54001
+ log6.debug(`Log file has ${lines.length} lines`);
53935
54002
  const recentLines = lines.slice(-maxLines);
53936
54003
  const entries = [];
53937
54004
  for (const line of recentLines) {
@@ -53941,17 +54008,17 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
53941
54008
  entries.push(JSON.parse(line));
53942
54009
  } catch {}
53943
54010
  }
53944
- log7.debug(`Parsed ${entries.length} log entries`);
54011
+ log6.debug(`Parsed ${entries.length} log entries`);
53945
54012
  return entries;
53946
54013
  } catch (err) {
53947
- log7.error(`Failed to read log file: ${err}`);
54014
+ log6.error(`Failed to read log file: ${err}`);
53948
54015
  return [];
53949
54016
  }
53950
54017
  }
53951
54018
 
53952
54019
  // src/cleanup/scheduler.ts
53953
54020
  init_worktree();
53954
- var log9 = createLogger("cleanup");
54021
+ var log8 = createLogger("cleanup");
53955
54022
  var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
53956
54023
  var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
53957
54024
 
@@ -53974,17 +54041,17 @@ class CleanupScheduler {
53974
54041
  }
53975
54042
  start() {
53976
54043
  if (this.isRunning) {
53977
- log9.debug("Cleanup scheduler already running");
54044
+ log8.debug("Cleanup scheduler already running");
53978
54045
  return;
53979
54046
  }
53980
54047
  this.isRunning = true;
53981
- log9.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
54048
+ log8.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
53982
54049
  this.runCleanup().catch((err) => {
53983
- log9.warn(`Initial cleanup failed: ${err}`);
54050
+ log8.warn(`Initial cleanup failed: ${err}`);
53984
54051
  });
53985
54052
  this.timer = setInterval(() => {
53986
54053
  this.runCleanup().catch((err) => {
53987
- log9.warn(`Periodic cleanup failed: ${err}`);
54054
+ log8.warn(`Periodic cleanup failed: ${err}`);
53988
54055
  });
53989
54056
  }, this.intervalMs);
53990
54057
  }
@@ -53994,11 +54061,11 @@ class CleanupScheduler {
53994
54061
  this.timer = null;
53995
54062
  }
53996
54063
  this.isRunning = false;
53997
- log9.debug("Cleanup scheduler stopped");
54064
+ log8.debug("Cleanup scheduler stopped");
53998
54065
  }
53999
54066
  async runCleanup() {
54000
54067
  const startTime = Date.now();
54001
- log9.debug("Running background cleanup...");
54068
+ log8.debug("Running background cleanup...");
54002
54069
  const stats = {
54003
54070
  logsDeleted: 0,
54004
54071
  worktreesCleaned: 0,
@@ -54024,9 +54091,9 @@ class CleanupScheduler {
54024
54091
  const elapsed = Date.now() - startTime;
54025
54092
  const totalCleaned = stats.logsDeleted + stats.worktreesCleaned + stats.metadataCleaned;
54026
54093
  if (totalCleaned > 0 || stats.errors.length > 0) {
54027
- log9.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
54094
+ log8.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
54028
54095
  } else {
54029
- log9.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
54096
+ log8.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
54030
54097
  }
54031
54098
  return stats;
54032
54099
  }
@@ -54039,7 +54106,7 @@ class CleanupScheduler {
54039
54106
  const deleted = cleanupOldLogs(this.logRetentionDays);
54040
54107
  resolve3(deleted);
54041
54108
  } catch (err) {
54042
- log9.warn(`Log cleanup error: ${err}`);
54109
+ log8.warn(`Log cleanup error: ${err}`);
54043
54110
  resolve3(0);
54044
54111
  }
54045
54112
  });
@@ -54048,7 +54115,7 @@ class CleanupScheduler {
54048
54115
  const worktreesDir = getWorktreesDir();
54049
54116
  const result = { cleaned: 0, metadata: 0 };
54050
54117
  if (!existsSync7(worktreesDir)) {
54051
- log9.debug("No worktrees directory exists, nothing to clean");
54118
+ log8.debug("No worktrees directory exists, nothing to clean");
54052
54119
  return result;
54053
54120
  }
54054
54121
  const persisted = this.sessionStore.load();
@@ -54066,7 +54133,7 @@ class CleanupScheduler {
54066
54133
  continue;
54067
54134
  const worktreePath = join6(worktreesDir, entry.name);
54068
54135
  if (activeWorktrees.has(worktreePath)) {
54069
- log9.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
54136
+ log8.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
54070
54137
  continue;
54071
54138
  }
54072
54139
  const meta = await readWorktreeMetadata(worktreePath);
@@ -54076,7 +54143,7 @@ class CleanupScheduler {
54076
54143
  const lastActivity = new Date(meta.lastActivityAt).getTime();
54077
54144
  const age = now - lastActivity;
54078
54145
  if (meta.sessionId && age < this.maxWorktreeAgeMs) {
54079
- log9.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
54146
+ log8.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
54080
54147
  continue;
54081
54148
  }
54082
54149
  const merged = age >= this.maxWorktreeAgeMs ? await isBranchMerged(meta.repoRoot, meta.branch).catch(() => false) : false;
@@ -54087,7 +54154,7 @@ class CleanupScheduler {
54087
54154
  shouldCleanup = true;
54088
54155
  cleanupReason = `inactive for ${Math.round(age / 3600000)}h`;
54089
54156
  } else {
54090
- log9.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
54157
+ log8.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
54091
54158
  continue;
54092
54159
  }
54093
54160
  } else {
@@ -54096,7 +54163,7 @@ class CleanupScheduler {
54096
54163
  }
54097
54164
  if (!shouldCleanup)
54098
54165
  continue;
54099
- log9.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
54166
+ log8.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
54100
54167
  try {
54101
54168
  if (meta?.repoRoot) {
54102
54169
  await removeWorktree(meta.repoRoot, worktreePath);
@@ -54107,19 +54174,19 @@ class CleanupScheduler {
54107
54174
  await removeWorktreeMetadata(worktreePath);
54108
54175
  result.metadata++;
54109
54176
  } catch (err) {
54110
- log9.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
54177
+ log8.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
54111
54178
  try {
54112
54179
  await rm(worktreePath, { recursive: true, force: true });
54113
54180
  result.cleaned++;
54114
54181
  await removeWorktreeMetadata(worktreePath);
54115
54182
  result.metadata++;
54116
54183
  } catch (rmErr) {
54117
- log9.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
54184
+ log8.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
54118
54185
  }
54119
54186
  }
54120
54187
  }
54121
54188
  } catch (err) {
54122
- log9.warn(`Failed to scan worktrees directory: ${err}`);
54189
+ log8.warn(`Failed to scan worktrees directory: ${err}`);
54123
54190
  }
54124
54191
  return result;
54125
54192
  }
@@ -54193,7 +54260,7 @@ init_logger();
54193
54260
  import { EventEmitter as EventEmitter2 } from "events";
54194
54261
  import { resolve as resolve3, dirname as dirname6 } from "path";
54195
54262
  import { fileURLToPath as fileURLToPath3 } from "url";
54196
- import { existsSync as existsSync8, readFileSync as readFileSync6, watchFile, unwatchFile, unlinkSync as unlinkSync2, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
54263
+ import { existsSync as existsSync8, readFileSync as readFileSync6, watchFile, unwatchFile, unlinkSync as unlinkSync2, statSync as statSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
54197
54264
  import { tmpdir } from "os";
54198
54265
  import { join as join7 } from "path";
54199
54266
 
@@ -54259,7 +54326,7 @@ function extractResetAt(text, now) {
54259
54326
  }
54260
54327
 
54261
54328
  // src/claude/cli.ts
54262
- var log10 = createLogger("claude");
54329
+ var log9 = createLogger("claude");
54263
54330
  function cleanupBrowserBridgeSockets() {
54264
54331
  try {
54265
54332
  const tempDir = tmpdir();
@@ -54271,13 +54338,13 @@ function cleanupBrowserBridgeSockets() {
54271
54338
  const stats = statSync2(filePath);
54272
54339
  if (stats.isSocket()) {
54273
54340
  unlinkSync2(filePath);
54274
- log10.debug(`Removed stale browser bridge socket: ${file}`);
54341
+ log9.debug(`Removed stale browser bridge socket: ${file}`);
54275
54342
  }
54276
54343
  } catch {}
54277
54344
  }
54278
54345
  }
54279
54346
  } catch (err) {
54280
- log10.debug(`Browser bridge cleanup failed: ${err}`);
54347
+ log9.debug(`Browser bridge cleanup failed: ${err}`);
54281
54348
  }
54282
54349
  }
54283
54350
  function buildClaudeChildEnv(parentEnv, account) {
@@ -54307,6 +54374,64 @@ function isErrorResultEvent(event) {
54307
54374
  return true;
54308
54375
  return false;
54309
54376
  }
54377
+ function materializeMcpConfig(config, sessionId, opts = {}) {
54378
+ if (opts.inline ?? process.env.CLAUDE_THREADS_MCP_CONFIG_INLINE === "1") {
54379
+ return { mode: "inline", value: JSON.stringify(config) };
54380
+ }
54381
+ const dir = opts.tmpDirOverride ?? tmpdir();
54382
+ const path2 = join7(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
54383
+ writeFileSync3(path2, JSON.stringify(config), { mode: 384 });
54384
+ return { mode: "file", path: path2 };
54385
+ }
54386
+ function buildPermissionArgs(opts) {
54387
+ const args = [];
54388
+ if (opts.permissionMode === "bypass") {
54389
+ args.push("--dangerously-skip-permissions");
54390
+ return { args, tempFile: null };
54391
+ }
54392
+ if (!opts.platformConfig) {
54393
+ throw new Error(`platformConfig is required when permissionMode is '${opts.permissionMode}'`);
54394
+ }
54395
+ const mcpEnv = {
54396
+ PLATFORM_TYPE: opts.platformConfig.type,
54397
+ PLATFORM_URL: opts.platformConfig.url,
54398
+ PLATFORM_TOKEN: opts.platformConfig.token,
54399
+ PLATFORM_CHANNEL_ID: opts.platformConfig.channelId,
54400
+ PLATFORM_THREAD_ID: opts.threadId || "",
54401
+ ALLOWED_USERS: opts.platformConfig.allowedUsers.join(","),
54402
+ DEBUG: opts.debug ? "1" : "",
54403
+ PERMISSION_TIMEOUT_MS: String(opts.permissionTimeoutMs)
54404
+ };
54405
+ if (opts.platformConfig.appToken) {
54406
+ mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
54407
+ }
54408
+ const mcpConfig = {
54409
+ mcpServers: {
54410
+ "claude-threads-permissions": {
54411
+ type: "stdio",
54412
+ command: "node",
54413
+ args: [opts.mcpServerPath],
54414
+ env: mcpEnv
54415
+ }
54416
+ }
54417
+ };
54418
+ const materialized = materializeMcpConfig(mcpConfig, opts.sessionId, { inline: opts.inline });
54419
+ let tempFile = null;
54420
+ if (materialized.mode === "file") {
54421
+ tempFile = materialized.path;
54422
+ args.push("--mcp-config", materialized.path);
54423
+ } else {
54424
+ args.push("--mcp-config", materialized.value);
54425
+ }
54426
+ args.push("--permission-prompt-tool", "mcp__claude-threads-permissions__permission_prompt");
54427
+ if (opts.permissionMode === "auto") {
54428
+ args.push("--permission-mode", "auto");
54429
+ }
54430
+ return { args, tempFile };
54431
+ }
54432
+ var STDERR_PER_INSTANCE_CAP = 10240;
54433
+ var STDERR_AGGREGATE_SOFT_CAP = 10 * 1024 * 1024;
54434
+ var totalStderrBytes = 0;
54310
54435
 
54311
54436
  class ClaudeCli extends EventEmitter2 {
54312
54437
  process = null;
@@ -54316,6 +54441,7 @@ class ClaudeCli extends EventEmitter2 {
54316
54441
  statusFilePath = null;
54317
54442
  lastStatusData = null;
54318
54443
  stderrBuffer = "";
54444
+ mcpConfigTempFile = null;
54319
54445
  lastEmittedRateLimitDeadline = 0;
54320
54446
  log;
54321
54447
  constructor(options2) {
@@ -54367,6 +54493,7 @@ class ClaudeCli extends EventEmitter2 {
54367
54493
  start() {
54368
54494
  if (this.process)
54369
54495
  throw new Error("Already running");
54496
+ totalStderrBytes -= this.stderrBuffer.length;
54370
54497
  this.stderrBuffer = "";
54371
54498
  this.lastEmittedRateLimitDeadline = 0;
54372
54499
  cleanupBrowserBridgeSockets();
@@ -54385,40 +54512,18 @@ class ClaudeCli extends EventEmitter2 {
54385
54512
  args.push("--session-id", this.options.sessionId);
54386
54513
  }
54387
54514
  }
54388
- if (this.options.skipPermissions) {
54389
- args.push("--dangerously-skip-permissions");
54390
- } else {
54391
- const mcpServerPath = this.getMcpServerPath();
54392
- const platformConfig = this.options.platformConfig;
54393
- if (!platformConfig) {
54394
- throw new Error("platformConfig is required when skipPermissions is false");
54395
- }
54396
- const mcpEnv = {
54397
- PLATFORM_TYPE: platformConfig.type,
54398
- PLATFORM_URL: platformConfig.url,
54399
- PLATFORM_TOKEN: platformConfig.token,
54400
- PLATFORM_CHANNEL_ID: platformConfig.channelId,
54401
- PLATFORM_THREAD_ID: this.options.threadId || "",
54402
- ALLOWED_USERS: platformConfig.allowedUsers.join(","),
54403
- DEBUG: this.debug ? "1" : "",
54404
- PERMISSION_TIMEOUT_MS: String(this.options.permissionTimeoutMs ?? 120000)
54405
- };
54406
- if (platformConfig.appToken) {
54407
- mcpEnv.PLATFORM_APP_TOKEN = platformConfig.appToken;
54408
- }
54409
- const mcpConfig = {
54410
- mcpServers: {
54411
- "claude-threads-permissions": {
54412
- type: "stdio",
54413
- command: "node",
54414
- args: [mcpServerPath],
54415
- env: mcpEnv
54416
- }
54417
- }
54418
- };
54419
- args.push("--mcp-config", JSON.stringify(mcpConfig));
54420
- args.push("--permission-prompt-tool", "mcp__claude-threads-permissions__permission_prompt");
54421
- }
54515
+ const permissionMode = this.options.permissionMode ?? "default";
54516
+ const permResult = buildPermissionArgs({
54517
+ permissionMode,
54518
+ mcpServerPath: this.getMcpServerPath(),
54519
+ platformConfig: this.options.platformConfig,
54520
+ threadId: this.options.threadId,
54521
+ sessionId: this.options.sessionId,
54522
+ permissionTimeoutMs: this.options.permissionTimeoutMs ?? 120000,
54523
+ debug: this.debug
54524
+ });
54525
+ args.push(...permResult.args);
54526
+ this.mcpConfigTempFile = permResult.tempFile;
54422
54527
  if (this.options.chrome) {
54423
54528
  args.push("--chrome");
54424
54529
  }
@@ -54453,10 +54558,13 @@ class ClaudeCli extends EventEmitter2 {
54453
54558
  });
54454
54559
  this.process.stderr?.on("data", (chunk) => {
54455
54560
  const text = chunk.toString();
54561
+ const before = this.stderrBuffer.length;
54456
54562
  this.stderrBuffer += text;
54457
- if (this.stderrBuffer.length > 10240) {
54458
- this.stderrBuffer = this.stderrBuffer.slice(-10240);
54563
+ const cap = totalStderrBytes > STDERR_AGGREGATE_SOFT_CAP ? 1024 : STDERR_PER_INSTANCE_CAP;
54564
+ if (this.stderrBuffer.length > cap) {
54565
+ this.stderrBuffer = this.stderrBuffer.slice(-cap);
54459
54566
  }
54567
+ totalStderrBytes += this.stderrBuffer.length - before;
54460
54568
  this.log.debug(`stderr: ${text.trim()}`);
54461
54569
  if (process.env.INTEGRATION_TEST === "1") {
54462
54570
  process.stderr.write(text);
@@ -54471,6 +54579,14 @@ class ClaudeCli extends EventEmitter2 {
54471
54579
  this.log.debug(`Exited ${code}`);
54472
54580
  this.process = null;
54473
54581
  this.buffer = "";
54582
+ totalStderrBytes -= this.stderrBuffer.length;
54583
+ if (this.mcpConfigTempFile) {
54584
+ const path2 = this.mcpConfigTempFile;
54585
+ this.mcpConfigTempFile = null;
54586
+ try {
54587
+ unlinkSync2(path2);
54588
+ } catch {}
54589
+ }
54474
54590
  this.emit("exit", code);
54475
54591
  });
54476
54592
  }
@@ -54716,8 +54832,8 @@ var COMMAND_REGISTRY = [
54716
54832
  },
54717
54833
  {
54718
54834
  command: "permissions",
54719
- description: "Toggle permission prompts",
54720
- args: "interactive|skip",
54835
+ description: "Set permission mode (default prompts; auto lets Claude classify; bypass skips all)",
54836
+ args: "default / auto / bypass",
54721
54837
  category: "settings",
54722
54838
  audience: "user",
54723
54839
  claudeNotes: "User decisions, not yours",
@@ -54832,7 +54948,7 @@ var COMMAND_PATTERNS = [
54832
54948
  ["worktree", /^!worktree\s+(\S+(?:\s+.*)?)$/i],
54833
54949
  ["invite", /^!invite\s+@?([\w.-]+)\s*$/i],
54834
54950
  ["kick", /^!kick\s+@?([\w.-]+)\s*$/i],
54835
- ["permissions", /^!permissions?\s+(interactive|auto)\s*$/i],
54951
+ ["permissions", /^!permissions?\s+(default|auto|bypass|interactive|skip)\s*$/i],
54836
54952
  ["update", /^!update(?:\s+(now|defer))?\s*$/i],
54837
54953
  ["context", /^!context\s*$/i],
54838
54954
  ["cost", /^!cost\s*$/i],
@@ -54897,7 +55013,7 @@ function removeCommandFromText(text, command) {
54897
55013
  }
54898
55014
  var STACKABLE_PATTERNS = [
54899
55015
  ["cd", /^!cd\s+(\S+)(?:\s+(.*))?$/i, 1, 2],
54900
- ["permissions", /^!permissions?\s+(interactive)(?:\s+(.*))?$/i, 1, 2],
55016
+ ["permissions", /^!permissions?\s+(default|auto|bypass|interactive|skip)(?:\s+(.*))?$/i, 1, 2],
54901
55017
  ["worktree", /^!worktree\s+(.+)$/i, 1, -1]
54902
55018
  ];
54903
55019
  var IMMEDIATE_PATTERNS = [
@@ -55175,22 +55291,39 @@ var handleCd = async (ctx, args) => {
55175
55291
  await ctx.sessionManager.changeDirectory(ctx.threadId, args, ctx.username);
55176
55292
  return { handled: true };
55177
55293
  };
55294
+ function parsePermissionMode(arg) {
55295
+ switch (arg?.toLowerCase()) {
55296
+ case "default":
55297
+ case "interactive":
55298
+ return "default";
55299
+ case "auto":
55300
+ return "auto";
55301
+ case "bypass":
55302
+ case "skip":
55303
+ return "bypass";
55304
+ default:
55305
+ return null;
55306
+ }
55307
+ }
55178
55308
  var handlePermissions = async (ctx, args) => {
55179
- const mode = args?.toLowerCase();
55309
+ const mode = parsePermissionMode(args);
55180
55310
  if (ctx.commandContext === "first-message") {
55181
- if (mode === "interactive") {
55311
+ if (mode) {
55182
55312
  return {
55183
- sessionOptions: { forceInteractivePermissions: true },
55313
+ sessionOptions: {
55314
+ permissionMode: mode,
55315
+ forceInteractivePermissions: mode === "default"
55316
+ },
55184
55317
  continueProcessing: true
55185
55318
  };
55186
55319
  }
55187
55320
  return { handled: false };
55188
55321
  }
55189
- if (mode === "interactive") {
55190
- await ctx.sessionManager.enableInteractivePermissions(ctx.threadId, ctx.username);
55191
- } else {
55192
- await ctx.client.createPost(`⚠️ Cannot upgrade to auto permissions - can only downgrade to interactive`, ctx.threadId);
55322
+ if (!mode) {
55323
+ await ctx.client.createPost(`⚠️ Unknown permission mode. Usage: \`!permissions default|auto|bypass\` (aliases: \`interactive\`, \`skip\`).`, ctx.threadId);
55324
+ return { handled: true };
55193
55325
  }
55326
+ await ctx.sessionManager.setSessionPermissionMode(ctx.threadId, ctx.username, mode);
55194
55327
  return { handled: true };
55195
55328
  };
55196
55329
  var handleWorktree = async (ctx, args) => {
@@ -55447,7 +55580,7 @@ import { existsSync as existsSync11 } from "fs";
55447
55580
  // src/utils/keep-alive.ts
55448
55581
  init_logger();
55449
55582
  import { spawn as spawn2 } from "child_process";
55450
- var log11 = createLogger("keepalive");
55583
+ var log10 = createLogger("keepalive");
55451
55584
 
55452
55585
  class KeepAliveManager {
55453
55586
  activeSessionCount = 0;
@@ -55462,7 +55595,7 @@ class KeepAliveManager {
55462
55595
  if (!enabled && this.keepAliveProcess) {
55463
55596
  this.stopKeepAlive();
55464
55597
  }
55465
- log11.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
55598
+ log10.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
55466
55599
  }
55467
55600
  isEnabled() {
55468
55601
  return this.enabled;
@@ -55472,7 +55605,7 @@ class KeepAliveManager {
55472
55605
  }
55473
55606
  sessionStarted() {
55474
55607
  this.activeSessionCount++;
55475
- log11.debug(`Session started (${this.activeSessionCount} active)`);
55608
+ log10.debug(`Session started (${this.activeSessionCount} active)`);
55476
55609
  if (this.activeSessionCount === 1) {
55477
55610
  this.startKeepAlive();
55478
55611
  }
@@ -55481,7 +55614,7 @@ class KeepAliveManager {
55481
55614
  if (this.activeSessionCount > 0) {
55482
55615
  this.activeSessionCount--;
55483
55616
  }
55484
- log11.debug(`Session ended (${this.activeSessionCount} active)`);
55617
+ log10.debug(`Session ended (${this.activeSessionCount} active)`);
55485
55618
  if (this.activeSessionCount === 0) {
55486
55619
  this.stopKeepAlive();
55487
55620
  }
@@ -55495,11 +55628,11 @@ class KeepAliveManager {
55495
55628
  }
55496
55629
  startKeepAlive() {
55497
55630
  if (!this.enabled) {
55498
- log11.debug("Keep-alive disabled, skipping");
55631
+ log10.debug("Keep-alive disabled, skipping");
55499
55632
  return;
55500
55633
  }
55501
55634
  if (this.keepAliveProcess) {
55502
- log11.debug("Keep-alive already running");
55635
+ log10.debug("Keep-alive already running");
55503
55636
  return;
55504
55637
  }
55505
55638
  switch (this.platform) {
@@ -55513,12 +55646,12 @@ class KeepAliveManager {
55513
55646
  this.startWindowsKeepAlive();
55514
55647
  break;
55515
55648
  default:
55516
- log11.warn(`Keep-alive not supported on ${this.platform}`);
55649
+ log10.warn(`Keep-alive not supported on ${this.platform}`);
55517
55650
  }
55518
55651
  }
55519
55652
  stopKeepAlive() {
55520
55653
  if (this.keepAliveProcess) {
55521
- log11.debug("Stopping keep-alive");
55654
+ log10.debug("Stopping keep-alive");
55522
55655
  this.keepAliveProcess.kill();
55523
55656
  this.keepAliveProcess = null;
55524
55657
  }
@@ -55530,18 +55663,18 @@ class KeepAliveManager {
55530
55663
  detached: false
55531
55664
  });
55532
55665
  this.keepAliveProcess.on("error", (err) => {
55533
- log11.error(`Failed to start caffeinate: ${err.message}`);
55666
+ log10.error(`Failed to start caffeinate: ${err.message}`);
55534
55667
  this.keepAliveProcess = null;
55535
55668
  });
55536
55669
  this.keepAliveProcess.on("exit", (code) => {
55537
55670
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
55538
- log11.debug(`caffeinate exited with code ${code}`);
55671
+ log10.debug(`caffeinate exited with code ${code}`);
55539
55672
  }
55540
55673
  this.keepAliveProcess = null;
55541
55674
  });
55542
- log11.info("Sleep prevention active (caffeinate)");
55675
+ log10.info("Sleep prevention active (caffeinate)");
55543
55676
  } catch (err) {
55544
- log11.error(`Failed to start caffeinate: ${err}`);
55677
+ log10.error(`Failed to start caffeinate: ${err}`);
55545
55678
  }
55546
55679
  }
55547
55680
  startLinuxKeepAlive() {
@@ -55557,19 +55690,19 @@ class KeepAliveManager {
55557
55690
  detached: false
55558
55691
  });
55559
55692
  this.keepAliveProcess.on("error", (err) => {
55560
- log11.debug(`systemd-inhibit not available: ${err.message}`);
55693
+ log10.debug(`systemd-inhibit not available: ${err.message}`);
55561
55694
  this.keepAliveProcess = null;
55562
55695
  this.startLinuxKeepAliveFallback();
55563
55696
  });
55564
55697
  this.keepAliveProcess.on("exit", (code) => {
55565
55698
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
55566
- log11.debug(`systemd-inhibit exited with code ${code}`);
55699
+ log10.debug(`systemd-inhibit exited with code ${code}`);
55567
55700
  }
55568
55701
  this.keepAliveProcess = null;
55569
55702
  });
55570
- log11.info("Sleep prevention active (systemd-inhibit)");
55703
+ log10.info("Sleep prevention active (systemd-inhibit)");
55571
55704
  } catch (err) {
55572
- log11.debug(`Failed to start systemd-inhibit: ${err}`);
55705
+ log10.debug(`Failed to start systemd-inhibit: ${err}`);
55573
55706
  this.startLinuxKeepAliveFallback();
55574
55707
  }
55575
55708
  }
@@ -55583,15 +55716,15 @@ class KeepAliveManager {
55583
55716
  detached: false
55584
55717
  });
55585
55718
  this.keepAliveProcess.on("error", (err) => {
55586
- log11.warn(`Linux keep-alive fallback not available: ${err.message}`);
55719
+ log10.warn(`Linux keep-alive fallback not available: ${err.message}`);
55587
55720
  this.keepAliveProcess = null;
55588
55721
  });
55589
55722
  this.keepAliveProcess.on("exit", () => {
55590
55723
  this.keepAliveProcess = null;
55591
55724
  });
55592
- log11.info("Sleep prevention active (xdg-screensaver)");
55725
+ log10.info("Sleep prevention active (xdg-screensaver)");
55593
55726
  } catch (err) {
55594
- log11.warn(`Linux keep-alive not available: ${err}`);
55727
+ log10.warn(`Linux keep-alive not available: ${err}`);
55595
55728
  }
55596
55729
  }
55597
55730
  startWindowsKeepAlive() {
@@ -55616,18 +55749,18 @@ class KeepAliveManager {
55616
55749
  windowsHide: true
55617
55750
  });
55618
55751
  this.keepAliveProcess.on("error", (err) => {
55619
- log11.warn(`Windows keep-alive not available: ${err.message}`);
55752
+ log10.warn(`Windows keep-alive not available: ${err.message}`);
55620
55753
  this.keepAliveProcess = null;
55621
55754
  });
55622
55755
  this.keepAliveProcess.on("exit", (code) => {
55623
55756
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
55624
- log11.debug(`PowerShell keep-alive exited with code ${code}`);
55757
+ log10.debug(`PowerShell keep-alive exited with code ${code}`);
55625
55758
  }
55626
55759
  this.keepAliveProcess = null;
55627
55760
  });
55628
- log11.info("Sleep prevention active (SetThreadExecutionState)");
55761
+ log10.info("Sleep prevention active (SetThreadExecutionState)");
55629
55762
  } catch (err) {
55630
- log11.warn(`Windows keep-alive not available: ${err}`);
55763
+ log10.warn(`Windows keep-alive not available: ${err}`);
55631
55764
  }
55632
55765
  }
55633
55766
  }
@@ -55635,7 +55768,7 @@ var keepAlive = new KeepAliveManager;
55635
55768
 
55636
55769
  // src/utils/error-handler/index.ts
55637
55770
  init_logger();
55638
- var log12 = createLogger("error");
55771
+ var log11 = createLogger("error");
55639
55772
 
55640
55773
  class SessionError extends Error {
55641
55774
  sessionId;
@@ -55661,19 +55794,19 @@ async function handleError(error, context, severity = "recoverable") {
55661
55794
  const sessionPart = sessionId ? ` (${formatShortId(sessionId)})` : "";
55662
55795
  const logMessage = `${context.action}${sessionPart}: ${message}`;
55663
55796
  if (severity === "recoverable") {
55664
- log12.warn(logMessage);
55797
+ log11.warn(logMessage);
55665
55798
  } else {
55666
- log12.error(logMessage, error instanceof Error ? error : undefined);
55799
+ log11.error(logMessage, error instanceof Error ? error : undefined);
55667
55800
  }
55668
55801
  if (context.details) {
55669
- log12.debugJson("Error details", context.details);
55802
+ log11.debugJson("Error details", context.details);
55670
55803
  }
55671
55804
  if (context.notifyUser && context.session) {
55672
55805
  try {
55673
55806
  const fmt = context.session.platform.getFormatter();
55674
55807
  await context.session.platform.createPost(`⚠️ ${fmt.formatBold("Error")}: ${context.action} failed - ${message}`, context.session.threadId);
55675
55808
  } catch (notifyError) {
55676
- log12.warn(`Could not notify user: ${notifyError}`);
55809
+ log11.warn(`Could not notify user: ${notifyError}`);
55677
55810
  }
55678
55811
  }
55679
55812
  if (severity === "session-fatal" || severity === "system-fatal") {
@@ -55700,7 +55833,7 @@ async function logAndNotify(error, context) {
55700
55833
  }
55701
55834
  function logSilentError(context, error) {
55702
55835
  const message = error instanceof Error ? error.message : String(error);
55703
- log12.debug(`[${context}] Silently caught: ${message}`);
55836
+ log11.debug(`[${context}] Silently caught: ${message}`);
55704
55837
  }
55705
55838
 
55706
55839
  // src/session/lifecycle.ts
@@ -55720,8 +55853,8 @@ function createSessionLog(baseLog) {
55720
55853
  init_logger();
55721
55854
  init_emoji();
55722
55855
  init_worktree();
55723
- var log13 = createLogger("helpers");
55724
- var sessionLog = createSessionLog(log13);
55856
+ var log12 = createLogger("helpers");
55857
+ var sessionLog = createSessionLog(log12);
55725
55858
  var POST_TYPES = {
55726
55859
  info: "",
55727
55860
  success: "✅",
@@ -55771,17 +55904,17 @@ async function postInteractiveAndRegister(session, message, reactions, registerP
55771
55904
  registerPost(post2.id, session.threadId);
55772
55905
  return post2;
55773
55906
  }
55774
- async function updatePost2(session, postId, message) {
55907
+ async function updatePost(session, postId, message) {
55775
55908
  await withErrorHandling(() => session.platform.updatePost(postId, message), { action: "Update post", session });
55776
55909
  }
55777
55910
  async function updatePostSuccess(session, postId, message) {
55778
- await updatePost2(session, postId, `✅ ${message}`);
55911
+ await updatePost(session, postId, `✅ ${message}`);
55779
55912
  }
55780
55913
  async function updatePostError(session, postId, message) {
55781
- await updatePost2(session, postId, `❌ ${message}`);
55914
+ await updatePost(session, postId, `❌ ${message}`);
55782
55915
  }
55783
55916
  async function updatePostCancelled(session, postId, message) {
55784
- await updatePost2(session, postId, `\uD83D\uDEAB ${message}`);
55917
+ await updatePost(session, postId, `\uD83D\uDEAB ${message}`);
55785
55918
  }
55786
55919
  async function removeReaction(session, postId, emoji) {
55787
55920
  await withErrorHandling(() => session.platform.removeReaction(postId, emoji), { action: `Remove ${emoji} reaction`, session });
@@ -55805,7 +55938,7 @@ function updateLastMessage(session, post2) {
55805
55938
  // src/claude/quick-query.ts
55806
55939
  init_spawn();
55807
55940
  init_logger();
55808
- var log14 = createLogger("query");
55941
+ var log13 = createLogger("query");
55809
55942
  async function quickQuery(options2) {
55810
55943
  const {
55811
55944
  prompt,
@@ -55821,7 +55954,7 @@ async function quickQuery(options2) {
55821
55954
  args.push("--system-prompt", systemPrompt);
55822
55955
  }
55823
55956
  args.push(prompt);
55824
- log14.debug(`Quick query: model=${model}, timeout=${timeout}ms, prompt="${prompt.substring(0, 50)}..."`);
55957
+ log13.debug(`Quick query: model=${model}, timeout=${timeout}ms, prompt="${prompt.substring(0, 50)}..."`);
55825
55958
  return new Promise((resolve5) => {
55826
55959
  let stdout = "";
55827
55960
  let stderr = "";
@@ -55835,7 +55968,7 @@ async function quickQuery(options2) {
55835
55968
  if (!resolved) {
55836
55969
  resolved = true;
55837
55970
  proc.kill("SIGTERM");
55838
- log14.debug(`Quick query timed out after ${timeout}ms`);
55971
+ log13.debug(`Quick query timed out after ${timeout}ms`);
55839
55972
  resolve5({
55840
55973
  success: false,
55841
55974
  error: "timeout",
@@ -55853,7 +55986,7 @@ async function quickQuery(options2) {
55853
55986
  if (!resolved) {
55854
55987
  resolved = true;
55855
55988
  clearTimeout(timeoutId);
55856
- log14.debug(`Quick query error: ${err.message}`);
55989
+ log13.debug(`Quick query error: ${err.message}`);
55857
55990
  resolve5({
55858
55991
  success: false,
55859
55992
  error: err.message,
@@ -55867,14 +56000,14 @@ async function quickQuery(options2) {
55867
56000
  clearTimeout(timeoutId);
55868
56001
  const durationMs = Date.now() - startTime;
55869
56002
  if (code === 0 && stdout.trim()) {
55870
- log14.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
56003
+ log13.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
55871
56004
  resolve5({
55872
56005
  success: true,
55873
56006
  response: stdout.trim(),
55874
56007
  durationMs
55875
56008
  });
55876
56009
  } else {
55877
- log14.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
56010
+ log13.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
55878
56011
  resolve5({
55879
56012
  success: false,
55880
56013
  error: stderr || `exit code ${code}`,
@@ -55889,7 +56022,7 @@ async function quickQuery(options2) {
55889
56022
 
55890
56023
  // src/operations/suggestions/title.ts
55891
56024
  init_logger();
55892
- var log15 = createLogger("title");
56025
+ var log14 = createLogger("title");
55893
56026
  var SUGGESTION_TIMEOUT = 15000;
55894
56027
  var MIN_TITLE_LENGTH = 3;
55895
56028
  var MAX_TITLE_LENGTH = 50;
@@ -55953,32 +56086,32 @@ function parseMetadata(response) {
55953
56086
  const titleMatch = response.match(/TITLE:\s*(.+)/i);
55954
56087
  const descMatch = response.match(/DESC:\s*(.+)/i);
55955
56088
  if (!titleMatch || !descMatch) {
55956
- log15.debug("Failed to parse title/description from response");
56089
+ log14.debug("Failed to parse title/description from response");
55957
56090
  return null;
55958
56091
  }
55959
56092
  let title = titleMatch[1].trim();
55960
56093
  let description = descMatch[1].trim();
55961
56094
  if (title.length < MIN_TITLE_LENGTH) {
55962
- log15.debug(`Title too short: ${title.length} chars`);
56095
+ log14.debug(`Title too short: ${title.length} chars`);
55963
56096
  return null;
55964
56097
  }
55965
56098
  if (title.length > MAX_TITLE_LENGTH) {
55966
- log15.debug(`Title too long (${title.length} chars), truncating`);
56099
+ log14.debug(`Title too long (${title.length} chars), truncating`);
55967
56100
  title = truncateAtWord(title, MAX_TITLE_LENGTH);
55968
56101
  }
55969
56102
  if (description.length < MIN_DESC_LENGTH) {
55970
- log15.debug(`Description too short: ${description.length} chars`);
56103
+ log14.debug(`Description too short: ${description.length} chars`);
55971
56104
  return null;
55972
56105
  }
55973
56106
  if (description.length > MAX_DESC_LENGTH) {
55974
- log15.debug(`Description too long (${description.length} chars), truncating`);
56107
+ log14.debug(`Description too long (${description.length} chars), truncating`);
55975
56108
  description = truncateAtWord(description, MAX_DESC_LENGTH);
55976
56109
  }
55977
56110
  return { title, description };
55978
56111
  }
55979
56112
  async function suggestSessionMetadata(context) {
55980
56113
  const logContext = typeof context === "string" ? context.substring(0, 50) : context.originalTask.substring(0, 50);
55981
- log15.debug(`Suggesting title for: "${logContext}..."`);
56114
+ log14.debug(`Suggesting title for: "${logContext}..."`);
55982
56115
  try {
55983
56116
  const result = await quickQuery({
55984
56117
  prompt: buildTitlePrompt(context),
@@ -55986,23 +56119,23 @@ async function suggestSessionMetadata(context) {
55986
56119
  timeout: SUGGESTION_TIMEOUT
55987
56120
  });
55988
56121
  if (!result.success || !result.response) {
55989
- log15.debug(`Title suggestion failed: ${result.error || "no response"}`);
56122
+ log14.debug(`Title suggestion failed: ${result.error || "no response"}`);
55990
56123
  return null;
55991
56124
  }
55992
56125
  const metadata = parseMetadata(result.response);
55993
56126
  if (metadata) {
55994
- log15.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
56127
+ log14.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
55995
56128
  }
55996
56129
  return metadata;
55997
56130
  } catch (err) {
55998
- log15.debug(`Title suggestion error: ${err}`);
56131
+ log14.debug(`Title suggestion error: ${err}`);
55999
56132
  return null;
56000
56133
  }
56001
56134
  }
56002
56135
 
56003
56136
  // src/operations/suggestions/tag.ts
56004
56137
  init_logger();
56005
- var log16 = createLogger("tags");
56138
+ var log15 = createLogger("tags");
56006
56139
  var SUGGESTION_TIMEOUT2 = 15000;
56007
56140
  var MAX_TAGS = 3;
56008
56141
  var VALID_TAGS = [
@@ -56034,7 +56167,7 @@ function parseTags(response) {
56034
56167
  return [...new Set(tags)].slice(0, MAX_TAGS);
56035
56168
  }
56036
56169
  async function suggestSessionTags(userMessage) {
56037
- log16.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
56170
+ log15.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
56038
56171
  try {
56039
56172
  const result = await quickQuery({
56040
56173
  prompt: buildTagPrompt(userMessage),
@@ -56042,14 +56175,14 @@ async function suggestSessionTags(userMessage) {
56042
56175
  timeout: SUGGESTION_TIMEOUT2
56043
56176
  });
56044
56177
  if (!result.success || !result.response) {
56045
- log16.debug(`Tag suggestion failed: ${result.error || "no response"}`);
56178
+ log15.debug(`Tag suggestion failed: ${result.error || "no response"}`);
56046
56179
  return [];
56047
56180
  }
56048
56181
  const tags = parseTags(result.response);
56049
- log16.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
56182
+ log15.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
56050
56183
  return tags;
56051
56184
  } catch (err) {
56052
- log16.debug(`Tag suggestion error: ${err}`);
56185
+ log15.debug(`Tag suggestion error: ${err}`);
56053
56186
  return [];
56054
56187
  }
56055
56188
  }
@@ -57968,6 +58101,18 @@ class ContentExecutor extends BaseExecutor {
57968
58101
  }
57969
58102
  this.state = this.getInitialState();
57970
58103
  }
58104
+ async tryUpdatePost(ctx, postId, content, logTag, successDetails, failureDetails, onSuccess, onFailure) {
58105
+ try {
58106
+ await ctx.platform.updatePost(postId, content);
58107
+ onSuccess();
58108
+ ctx.threadLogger?.logExecutor("content", "update", postId, successDetails, logTag);
58109
+ } catch (err) {
58110
+ ctx.logger.debug(`Update failed (${logTag}): ${err}`);
58111
+ const resolvedFailureDetails = typeof failureDetails === "function" ? failureDetails(err) : failureDetails;
58112
+ ctx.threadLogger?.logExecutor("content", "error", postId, resolvedFailureDetails, logTag);
58113
+ onFailure();
58114
+ }
58115
+ }
57971
58116
  closeCurrentPost(ctx) {
57972
58117
  const oldPostId = this.state.currentPostId;
57973
58118
  const contentLength = this.state.currentPostContent.length;
@@ -58066,23 +58211,13 @@ class ContentExecutor extends BaseExecutor {
58066
58211
  await this.createNewPost(ctx, content, pendingAtFlushStart);
58067
58212
  return;
58068
58213
  }
58069
- try {
58070
- await ctx.platform.updatePost(postId, combinedContent2);
58214
+ await this.tryUpdatePost(ctx, postId, combinedContent2, "flush", { newContentLength: content.length, combinedLength: combinedContent2.length }, (err) => ({ failedOp: "updatePost", error: String(err) }), () => {
58071
58215
  this.state.currentPostContent = combinedContent2;
58072
58216
  this.clearFlushedContent(pendingAtFlushStart);
58073
- ctx.threadLogger?.logExecutor("content", "update", postId, {
58074
- newContentLength: content.length,
58075
- combinedLength: combinedContent2.length
58076
- }, "flush");
58077
- } catch (err) {
58078
- ctx.logger.debug(`Update failed, will create new post on next flush: ${err}`);
58079
- ctx.threadLogger?.logExecutor("content", "error", postId, {
58080
- failedOp: "updatePost",
58081
- error: String(err)
58082
- }, "flush");
58217
+ }, () => {
58083
58218
  this.state.currentPostId = null;
58084
58219
  this.state.currentPostContent = "";
58085
- }
58220
+ });
58086
58221
  } else {
58087
58222
  const chunks = splitContentForHeight(content, ctx.contentBreaker);
58088
58223
  ctx.threadLogger?.logExecutor("content", "create_start", "none", {
@@ -58143,21 +58278,13 @@ class ContentExecutor extends BaseExecutor {
58143
58278
  breakPoint = bestBreakPoint;
58144
58279
  } else {
58145
58280
  if (this.state.currentPostId) {
58146
- try {
58147
- await ctx.platform.updatePost(this.state.currentPostId, content);
58281
+ const postId = this.state.currentPostId;
58282
+ await this.tryUpdatePost(ctx, postId, content, "handleSplit", { reason: "soft_break_no_breakpoint", contentLength: content.length }, { reason: "soft_break_no_breakpoint_failed" }, () => {
58148
58283
  this.state.currentPostContent = content;
58149
58284
  this.clearFlushedContent(pendingAtFlushStart);
58150
- ctx.threadLogger?.logExecutor("content", "update", this.state.currentPostId, {
58151
- reason: "soft_break_no_breakpoint",
58152
- contentLength: content.length
58153
- }, "handleSplit");
58154
- } catch {
58155
- ctx.logger.debug("Update failed (no breakpoint), will create new post on next flush");
58156
- ctx.threadLogger?.logExecutor("content", "error", this.state.currentPostId, {
58157
- reason: "soft_break_no_breakpoint_failed"
58158
- }, "handleSplit");
58285
+ }, () => {
58159
58286
  this.state.currentPostId = null;
58160
- }
58287
+ });
58161
58288
  }
58162
58289
  return;
58163
58290
  }
@@ -58165,22 +58292,14 @@ class ContentExecutor extends BaseExecutor {
58165
58292
  if (codeBlockOpenPosition !== undefined) {
58166
58293
  if (codeBlockOpenPosition === 0) {
58167
58294
  if (this.state.currentPostId) {
58168
- try {
58169
- await ctx.platform.updatePost(this.state.currentPostId, content);
58295
+ const postId = this.state.currentPostId;
58296
+ await this.tryUpdatePost(ctx, postId, content, "handleSplit", { reason: "code_block_at_start", contentLength: content.length }, { reason: "code_block_at_start_failed" }, () => {
58170
58297
  this.state.currentPostContent = content;
58171
58298
  this.clearFlushedContent(pendingAtFlushStart);
58172
- ctx.threadLogger?.logExecutor("content", "update", this.state.currentPostId, {
58173
- reason: "code_block_at_start",
58174
- contentLength: content.length
58175
- }, "handleSplit");
58176
- } catch {
58177
- ctx.logger.debug("Update failed (code block at start)");
58178
- ctx.threadLogger?.logExecutor("content", "error", this.state.currentPostId, {
58179
- reason: "code_block_at_start_failed"
58180
- }, "handleSplit");
58299
+ }, () => {
58181
58300
  this.state.currentPostId = null;
58182
58301
  this.state.currentPostContent = "";
58183
- }
58302
+ });
58184
58303
  }
58185
58304
  return;
58186
58305
  }
@@ -58190,22 +58309,14 @@ class ContentExecutor extends BaseExecutor {
58190
58309
  breakPoint = breakBeforeCodeBlock;
58191
58310
  } else {
58192
58311
  if (this.state.currentPostId) {
58193
- try {
58194
- await ctx.platform.updatePost(this.state.currentPostId, content);
58312
+ const postId = this.state.currentPostId;
58313
+ await this.tryUpdatePost(ctx, postId, content, "handleSplit", { reason: "no_break_before_code_block", contentLength: content.length }, { reason: "no_break_before_code_block_failed" }, () => {
58195
58314
  this.state.currentPostContent = content;
58196
58315
  this.clearFlushedContent(pendingAtFlushStart);
58197
- ctx.threadLogger?.logExecutor("content", "update", this.state.currentPostId, {
58198
- reason: "no_break_before_code_block",
58199
- contentLength: content.length
58200
- }, "handleSplit");
58201
- } catch {
58202
- ctx.logger.debug("Update failed (no break before code block)");
58203
- ctx.threadLogger?.logExecutor("content", "error", this.state.currentPostId, {
58204
- reason: "no_break_before_code_block_failed"
58205
- }, "handleSplit");
58316
+ }, () => {
58206
58317
  this.state.currentPostId = null;
58207
58318
  this.state.currentPostContent = "";
58208
- }
58319
+ });
58209
58320
  }
58210
58321
  return;
58211
58322
  }
@@ -58213,19 +58324,8 @@ class ContentExecutor extends BaseExecutor {
58213
58324
  const firstPart = content.substring(0, breakPoint).trim();
58214
58325
  const remainder = content.substring(breakPoint).trim();
58215
58326
  if (this.state.currentPostId) {
58216
- try {
58217
- await ctx.platform.updatePost(this.state.currentPostId, firstPart);
58218
- ctx.threadLogger?.logExecutor("content", "update", this.state.currentPostId, {
58219
- reason: "split_first_part",
58220
- firstPartLength: firstPart.length,
58221
- remainderLength: remainder.length
58222
- }, "handleSplit");
58223
- } catch {
58224
- ctx.logger.debug("Update failed during split, continuing with new post");
58225
- ctx.threadLogger?.logExecutor("content", "error", this.state.currentPostId, {
58226
- reason: "split_first_part_failed"
58227
- }, "handleSplit");
58228
- }
58327
+ const postId = this.state.currentPostId;
58328
+ await this.tryUpdatePost(ctx, postId, firstPart, "handleSplit", { reason: "split_first_part", firstPartLength: firstPart.length, remainderLength: remainder.length }, { reason: "split_first_part_failed" }, () => {}, () => {});
58229
58329
  }
58230
58330
  this.state.currentPostId = null;
58231
58331
  this.state.currentPostContent = "";
@@ -59591,7 +59691,7 @@ class BugReportExecutor extends BaseExecutor {
59591
59691
  // src/operations/executors/worktree-prompt.ts
59592
59692
  init_emoji();
59593
59693
  init_logger();
59594
- var log17 = createLogger("wt-prompt");
59694
+ var log16 = createLogger("wt-prompt");
59595
59695
  // src/operations/message-manager.ts
59596
59696
  init_logger();
59597
59697
 
@@ -59628,7 +59728,7 @@ var import_yauzl = __toESM(require_yauzl(), 1);
59628
59728
  import { createGunzip } from "zlib";
59629
59729
  import { pipeline } from "stream/promises";
59630
59730
  import { Readable, Writable } from "stream";
59631
- var log18 = createLogger("streaming");
59731
+ var log17 = createLogger("streaming");
59632
59732
  var MAX_PDF_SIZE = 32 * 1024 * 1024;
59633
59733
  var MAX_TEXT_FILE_SIZE = 1 * 1024 * 1024;
59634
59734
  var MAX_DECOMPRESSED_SIZE = 10 * 1024 * 1024;
@@ -59754,7 +59854,7 @@ async function processImageFile(file, platform, debug = false) {
59754
59854
  const buffer = await platform.downloadFile(file.id);
59755
59855
  const base64 = buffer.toString("base64");
59756
59856
  if (debug) {
59757
- log18.debug(`Attached image: ${file.name} (${file.mimeType}, ${Math.round(buffer.length / 1024)}KB)`);
59857
+ log17.debug(`Attached image: ${file.name} (${file.mimeType}, ${Math.round(buffer.length / 1024)}KB)`);
59758
59858
  }
59759
59859
  return {
59760
59860
  block: {
@@ -59767,7 +59867,7 @@ async function processImageFile(file, platform, debug = false) {
59767
59867
  }
59768
59868
  };
59769
59869
  } catch (err) {
59770
- log18.error(`Failed to download image ${file.name}: ${err}`);
59870
+ log17.error(`Failed to download image ${file.name}: ${err}`);
59771
59871
  return {
59772
59872
  skipped: {
59773
59873
  name: file.name,
@@ -59798,7 +59898,7 @@ async function processPdfFile(file, platform, debug = false) {
59798
59898
  }
59799
59899
  const base64 = buffer.toString("base64");
59800
59900
  if (debug) {
59801
- log18.debug(`Attached PDF: ${file.name} (${Math.round(buffer.length / 1024)}KB)`);
59901
+ log17.debug(`Attached PDF: ${file.name} (${Math.round(buffer.length / 1024)}KB)`);
59802
59902
  }
59803
59903
  return {
59804
59904
  block: {
@@ -59812,7 +59912,7 @@ async function processPdfFile(file, platform, debug = false) {
59812
59912
  }
59813
59913
  };
59814
59914
  } catch (err) {
59815
- log18.error(`Failed to process PDF ${file.name}: ${err}`);
59915
+ log17.error(`Failed to process PDF ${file.name}: ${err}`);
59816
59916
  return {
59817
59917
  skipped: {
59818
59918
  name: file.name,
@@ -59843,7 +59943,7 @@ async function processTextFile(file, platform, debug = false) {
59843
59943
  }
59844
59944
  const content = buffer.toString("utf-8");
59845
59945
  if (debug) {
59846
- log18.debug(`Attached text file: ${file.name} (${Math.round(buffer.length / 1024)}KB)`);
59946
+ log17.debug(`Attached text file: ${file.name} (${Math.round(buffer.length / 1024)}KB)`);
59847
59947
  }
59848
59948
  const wrappedContent = formatTextFileContent(file.name, content);
59849
59949
  return {
@@ -59853,7 +59953,7 @@ async function processTextFile(file, platform, debug = false) {
59853
59953
  }
59854
59954
  };
59855
59955
  } catch (err) {
59856
- log18.error(`Failed to process text file ${file.name}: ${err}`);
59956
+ log17.error(`Failed to process text file ${file.name}: ${err}`);
59857
59957
  return {
59858
59958
  skipped: {
59859
59959
  name: file.name,
@@ -59911,7 +60011,7 @@ async function processGzipFile(file, platform, debug = false) {
59911
60011
  compressedBuffer = await platform.downloadFile(file.id);
59912
60012
  } catch (err) {
59913
60013
  const errorMessage = err instanceof Error ? err.message : String(err);
59914
- log18.error(`Failed to download gzip file ${file.name}: ${errorMessage}`);
60014
+ log17.error(`Failed to download gzip file ${file.name}: ${errorMessage}`);
59915
60015
  return {
59916
60016
  skipped: {
59917
60017
  name: file.name,
@@ -59921,7 +60021,7 @@ async function processGzipFile(file, platform, debug = false) {
59921
60021
  };
59922
60022
  }
59923
60023
  if (file.size && compressedBuffer.length !== file.size) {
59924
- log18.warn(`Downloaded size mismatch for ${file.name}: expected ${file.size}, got ${compressedBuffer.length}`);
60024
+ log17.warn(`Downloaded size mismatch for ${file.name}: expected ${file.size}, got ${compressedBuffer.length}`);
59925
60025
  }
59926
60026
  let decompressedBuffer;
59927
60027
  try {
@@ -59957,7 +60057,7 @@ async function processGzipFile(file, platform, debug = false) {
59957
60057
  const innerFilename = file.name.toLowerCase().endsWith(".gz") ? file.name.slice(0, -3) : file.name;
59958
60058
  const contentType = detectDecompressedContentType(decompressedBuffer, innerFilename);
59959
60059
  if (debug) {
59960
- log18.debug(`Decompressed ${file.name}: ${Math.round(decompressedBuffer.length / 1024)}KB, detected type: ${contentType}`);
60060
+ log17.debug(`Decompressed ${file.name}: ${Math.round(decompressedBuffer.length / 1024)}KB, detected type: ${contentType}`);
59961
60061
  }
59962
60062
  if (contentType === "pdf") {
59963
60063
  const base64 = decompressedBuffer.toString("base64");
@@ -59992,7 +60092,7 @@ async function processGzipFile(file, platform, debug = false) {
59992
60092
  }
59993
60093
  } catch (err) {
59994
60094
  const errorMessage = err instanceof Error ? err.message : String(err);
59995
- log18.error(`Failed to process gzip file ${file.name}: ${errorMessage}`);
60095
+ log17.error(`Failed to process gzip file ${file.name}: ${errorMessage}`);
59996
60096
  return {
59997
60097
  skipped: {
59998
60098
  name: file.name,
@@ -60044,7 +60144,7 @@ async function processZipFile(file, platform, debug = false) {
60044
60144
  }
60045
60145
  const zipBuffer = await platform.downloadFile(file.id);
60046
60146
  if (debug) {
60047
- log18.debug(`Processing zip file ${file.name}: ${Math.round(zipBuffer.length / 1024)}KB`);
60147
+ log17.debug(`Processing zip file ${file.name}: ${Math.round(zipBuffer.length / 1024)}KB`);
60048
60148
  }
60049
60149
  const zipfile = await new Promise((resolve5, reject) => {
60050
60150
  import_yauzl.default.fromBuffer(zipBuffer, { lazyEntries: true }, (err, zf) => {
@@ -60118,7 +60218,7 @@ async function processZipFile(file, platform, debug = false) {
60118
60218
  const buffer = await extractZipEntry(zipfile2, entry);
60119
60219
  const contentType = detectDecompressedContentType(buffer, entry.fileName);
60120
60220
  if (debug) {
60121
- log18.debug(`Extracted ${entry.fileName}: ${Math.round(buffer.length / 1024)}KB, type: ${contentType}`);
60221
+ log17.debug(`Extracted ${entry.fileName}: ${Math.round(buffer.length / 1024)}KB, type: ${contentType}`);
60122
60222
  }
60123
60223
  if (contentType === "pdf") {
60124
60224
  const base64 = buffer.toString("base64");
@@ -60161,11 +60261,11 @@ async function processZipFile(file, platform, debug = false) {
60161
60261
  });
60162
60262
  zipfile2.close();
60163
60263
  if (debug) {
60164
- log18.debug(`Zip ${file.name}: processed ${processedCount} files, skipped ${skipped.length}`);
60264
+ log17.debug(`Zip ${file.name}: processed ${processedCount} files, skipped ${skipped.length}`);
60165
60265
  }
60166
60266
  return { blocks, skipped };
60167
60267
  } catch (err) {
60168
- log18.error(`Failed to process zip file ${file.name}: ${err}`);
60268
+ log17.error(`Failed to process zip file ${file.name}: ${err}`);
60169
60269
  return {
60170
60270
  blocks: [],
60171
60271
  skipped: [{
@@ -60262,7 +60362,7 @@ async function processFiles(platform, files, debug = false) {
60262
60362
  blocks.push(...zipResult.blocks);
60263
60363
  for (const s of zipResult.skipped) {
60264
60364
  skipped.push(s);
60265
- log18.warn(`Skipped file ${s.name}: ${s.reason}`);
60365
+ log17.warn(`Skipped file ${s.name}: ${s.reason}`);
60266
60366
  }
60267
60367
  continue;
60268
60368
  }
@@ -60296,7 +60396,7 @@ async function processFiles(platform, files, debug = false) {
60296
60396
  }
60297
60397
  if (result.skipped) {
60298
60398
  skipped.push(result.skipped);
60299
- log18.warn(`Skipped file ${result.skipped.name}: ${result.skipped.reason}`);
60399
+ log17.warn(`Skipped file ${result.skipped.name}: ${result.skipped.reason}`);
60300
60400
  }
60301
60401
  }
60302
60402
  return { blocks, skipped };
@@ -60329,7 +60429,7 @@ function stopTyping(session) {
60329
60429
  }
60330
60430
 
60331
60431
  // src/operations/message-manager.ts
60332
- var log19 = createLogger("msg-mgr");
60432
+ var log18 = createLogger("msg-mgr");
60333
60433
 
60334
60434
  class MessageManager {
60335
60435
  platform;
@@ -60355,7 +60455,8 @@ class MessageManager {
60355
60455
  emitSessionUpdateCallback;
60356
60456
  toolStartTimes = new Map;
60357
60457
  flushTimer = null;
60358
- static FLUSH_DELAY_MS = 500;
60458
+ static DEFAULT_FLUSH_DELAY_MS = 500;
60459
+ flushDelayMs;
60359
60460
  events;
60360
60461
  constructor(options2) {
60361
60462
  this.session = options2.session;
@@ -60370,6 +60471,7 @@ class MessageManager {
60370
60471
  this.buildMessageContentCallback = options2.buildMessageContent;
60371
60472
  this.startTypingCallback = options2.startTyping;
60372
60473
  this.emitSessionUpdateCallback = options2.emitSessionUpdate;
60474
+ this.flushDelayMs = options2.flushDelayMs ?? MessageManager.DEFAULT_FLUSH_DELAY_MS;
60373
60475
  this.events = createMessageManagerEvents();
60374
60476
  this.contentBreaker = new DefaultContentBreaker;
60375
60477
  this.contentExecutor = new ContentExecutor({
@@ -60417,7 +60519,7 @@ class MessageManager {
60417
60519
  });
60418
60520
  }
60419
60521
  async handleEvent(event) {
60420
- const logger = log19.forSession(this.sessionId);
60522
+ const logger = log18.forSession(this.sessionId);
60421
60523
  const transformCtx = {
60422
60524
  sessionId: this.sessionId,
60423
60525
  formatter: this.platform.getFormatter(),
@@ -60467,7 +60569,7 @@ class MessageManager {
60467
60569
  }
60468
60570
  }
60469
60571
  async executeOperation(op) {
60470
- const logger = log19.forSession(this.sessionId);
60572
+ const logger = log18.forSession(this.sessionId);
60471
60573
  const ctx = this.getExecutorContext();
60472
60574
  try {
60473
60575
  if (isContentOp(op)) {
@@ -60516,7 +60618,7 @@ class MessageManager {
60516
60618
  this.flushTimer = null;
60517
60619
  const flushOp = createFlushOp(this.sessionId, "soft_threshold");
60518
60620
  await this.contentExecutor.executeFlush(flushOp, ctx);
60519
- }, MessageManager.FLUSH_DELAY_MS);
60621
+ }, this.flushDelayMs);
60520
60622
  }
60521
60623
  cancelScheduledFlush() {
60522
60624
  if (this.flushTimer) {
@@ -60535,7 +60637,7 @@ class MessageManager {
60535
60637
  threadId: this.threadId,
60536
60638
  platform: this.platform,
60537
60639
  formatter: this.platform.getFormatter(),
60538
- logger: log19.forSession(this.sessionId),
60640
+ logger: log18.forSession(this.sessionId),
60539
60641
  postTracker: this.postTracker,
60540
60642
  contentBreaker: this.contentBreaker,
60541
60643
  threadLogger: this.session.threadLogger,
@@ -60746,13 +60848,13 @@ class MessageManager {
60746
60848
  return this.systemExecutor.postSuccess(message, this.getExecutorContext());
60747
60849
  }
60748
60850
  async prepareForUserMessage() {
60749
- const logger = log19.forSession(this.sessionId);
60851
+ const logger = log18.forSession(this.sessionId);
60750
60852
  logger.debug("Preparing for new user message");
60751
60853
  await this.closeCurrentPost();
60752
60854
  await this.bumpTaskList();
60753
60855
  }
60754
60856
  async handleUserMessage(message, files, username, displayName) {
60755
- const logger = log19.forSession(this.sessionId);
60857
+ const logger = log18.forSession(this.sessionId);
60756
60858
  if (!this.session.claude.isRunning()) {
60757
60859
  logger.debug("Claude not running, ignoring user message");
60758
60860
  return false;
@@ -60779,7 +60881,7 @@ class MessageManager {
60779
60881
  return this.session;
60780
60882
  }
60781
60883
  async handleReaction(postId, emoji, user, action) {
60782
- const logger = log19.forSession(this.sessionId);
60884
+ const logger = log18.forSession(this.sessionId);
60783
60885
  const ctx = this.getExecutorContext();
60784
60886
  logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
60785
60887
  if (await this.questionApprovalExecutor.handleReaction(postId, emoji, user, action, ctx)) {
@@ -60975,7 +61077,7 @@ function formatPullRequestLink(url, formatter) {
60975
61077
  }
60976
61078
 
60977
61079
  // src/operations/sticky-message/handler.ts
60978
- var log20 = createLogger("sticky");
61080
+ var log19 = createLogger("sticky");
60979
61081
  var botStartedAt = new Date;
60980
61082
  function getPendingPrompts(session) {
60981
61083
  const prompts2 = [];
@@ -61050,21 +61152,21 @@ function initialize(store) {
61050
61152
  stickyPostIds.set(platformId, postId);
61051
61153
  }
61052
61154
  if (persistedIds.size > 0) {
61053
- log20.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
61155
+ log19.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
61054
61156
  }
61055
61157
  }
61056
61158
  function setPlatformPaused(platformId, paused) {
61057
61159
  if (paused) {
61058
61160
  pausedPlatforms.set(platformId, true);
61059
- log20.debug(`Platform ${platformId} marked as paused`);
61161
+ log19.debug(`Platform ${platformId} marked as paused`);
61060
61162
  } else {
61061
61163
  pausedPlatforms.delete(platformId);
61062
- log20.debug(`Platform ${platformId} marked as active`);
61164
+ log19.debug(`Platform ${platformId} marked as active`);
61063
61165
  }
61064
61166
  }
61065
61167
  function setShuttingDown(shuttingDown) {
61066
61168
  isShuttingDown = shuttingDown;
61067
- log20.debug(`Bot shutdown state: ${shuttingDown}`);
61169
+ log19.debug(`Bot shutdown state: ${shuttingDown}`);
61068
61170
  }
61069
61171
  function getTaskContent(session) {
61070
61172
  const taskState = session.messageManager?.getTaskListState();
@@ -61168,8 +61270,7 @@ async function buildStatusBar(sessionCount, config, formatter, platformId) {
61168
61270
  const label = cooling > 0 ? `\uD83D\uDD11 ${available}/${total} accounts (${cooling} cooling)` : `\uD83D\uDD11 ${total} account${total === 1 ? "" : "s"}`;
61169
61271
  items.push(formatter.formatCode(label));
61170
61272
  }
61171
- const permMode = config.skipPermissions ? "⚡ Auto" : "\uD83D\uDD10 Interactive";
61172
- items.push(formatter.formatCode(permMode));
61273
+ items.push(formatter.formatCode(permissionModeDisplay(config.permissionMode).chip));
61173
61274
  if (config.worktreeMode === "require") {
61174
61275
  items.push(formatter.formatCode("\uD83C\uDF3F Worktree: require"));
61175
61276
  } else if (config.worktreeMode === "off") {
@@ -61376,12 +61477,12 @@ async function validateLastMessageIds(platform, sessions) {
61376
61477
  try {
61377
61478
  const post2 = await platform.getPost(lastMessageId);
61378
61479
  if (!post2) {
61379
- log20.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
61480
+ log19.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
61380
61481
  session.lastMessageId = undefined;
61381
61482
  session.lastMessageTs = undefined;
61382
61483
  }
61383
61484
  } catch (err) {
61384
- log20.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
61485
+ log19.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
61385
61486
  session.lastMessageId = undefined;
61386
61487
  session.lastMessageTs = undefined;
61387
61488
  }
@@ -61390,63 +61491,63 @@ async function validateLastMessageIds(platform, sessions) {
61390
61491
  }
61391
61492
  async function updateStickyMessageImpl(platform, sessions, config) {
61392
61493
  const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
61393
- log20.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
61494
+ log19.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
61394
61495
  for (const s of platformSessions) {
61395
- log20.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
61496
+ log19.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
61396
61497
  }
61397
61498
  await validateLastMessageIds(platform, platformSessions);
61398
61499
  const formatter = platform.getFormatter();
61399
61500
  const content = await buildStickyMessage(sessions, platform.platformId, config, formatter, (threadId) => platform.getThreadLink(threadId));
61400
61501
  const existingPostId = stickyPostIds.get(platform.platformId);
61401
61502
  const shouldBump = needsBump.get(platform.platformId) ?? false;
61402
- log20.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
61503
+ log19.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
61403
61504
  try {
61404
61505
  if (existingPostId && !shouldBump) {
61405
- log20.debug(`Updating existing post in place...`);
61506
+ log19.debug(`Updating existing post in place...`);
61406
61507
  try {
61407
61508
  await platform.updatePost(existingPostId, content);
61408
61509
  try {
61409
61510
  await platform.pinPost(existingPostId);
61410
- log20.debug(`Re-pinned post`);
61511
+ log19.debug(`Re-pinned post`);
61411
61512
  } catch (pinErr) {
61412
- log20.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
61513
+ log19.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
61413
61514
  }
61414
- log20.debug(`Updated successfully`);
61515
+ log19.debug(`Updated successfully`);
61415
61516
  return;
61416
61517
  } catch (err) {
61417
- log20.debug(`Update failed, will create new: ${err}`);
61518
+ log19.debug(`Update failed, will create new: ${err}`);
61418
61519
  }
61419
61520
  }
61420
61521
  needsBump.set(platform.platformId, false);
61421
61522
  if (existingPostId) {
61422
- log20.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
61523
+ log19.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
61423
61524
  try {
61424
61525
  await platform.unpinPost(existingPostId);
61425
- log20.debug(`Unpinned successfully`);
61526
+ log19.debug(`Unpinned successfully`);
61426
61527
  } catch (err) {
61427
- log20.debug(`Unpin failed (probably already unpinned): ${err}`);
61528
+ log19.debug(`Unpin failed (probably already unpinned): ${err}`);
61428
61529
  }
61429
61530
  try {
61430
61531
  await platform.deletePost(existingPostId);
61431
- log20.debug(`Deleted successfully`);
61532
+ log19.debug(`Deleted successfully`);
61432
61533
  } catch (err) {
61433
- log20.debug(`Delete failed (probably already deleted): ${err}`);
61534
+ log19.debug(`Delete failed (probably already deleted): ${err}`);
61434
61535
  }
61435
61536
  stickyPostIds.delete(platform.platformId);
61436
61537
  }
61437
- log20.debug(`Creating new post...`);
61538
+ log19.debug(`Creating new post...`);
61438
61539
  const post2 = await platform.createPost(content);
61439
61540
  stickyPostIds.set(platform.platformId, post2.id);
61440
61541
  try {
61441
61542
  await platform.pinPost(post2.id);
61442
- log20.debug(`Pinned post successfully`);
61543
+ log19.debug(`Pinned post successfully`);
61443
61544
  } catch (err) {
61444
- log20.debug(`Failed to pin post: ${err}`);
61545
+ log19.debug(`Failed to pin post: ${err}`);
61445
61546
  }
61446
61547
  if (sessionStore) {
61447
61548
  sessionStore.saveStickyPostId(platform.platformId, post2.id);
61448
61549
  }
61449
- log20.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
61550
+ log19.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
61450
61551
  const excludePostIds = new Set;
61451
61552
  if (sessionStore) {
61452
61553
  for (const session of sessionStore.load().values()) {
@@ -61462,10 +61563,10 @@ async function updateStickyMessageImpl(platform, sessions, config) {
61462
61563
  }
61463
61564
  const botUser = await platform.getBotUser();
61464
61565
  cleanupOldStickyMessages(platform, botUser.id, false, excludePostIds).catch((err) => {
61465
- log20.debug(`Background cleanup failed: ${err}`);
61566
+ log19.debug(`Background cleanup failed: ${err}`);
61466
61567
  });
61467
61568
  } catch (err) {
61468
- log20.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
61569
+ log19.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
61469
61570
  }
61470
61571
  }
61471
61572
  async function updateAllStickyMessages(platforms, sessions, config) {
@@ -61490,7 +61591,7 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
61490
61591
  if (!forceRun) {
61491
61592
  const lastRun = lastCleanupTime.get(platformId) || 0;
61492
61593
  if (now - lastRun < CLEANUP_THROTTLE_MS) {
61493
- log20.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
61594
+ log19.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
61494
61595
  return;
61495
61596
  }
61496
61597
  }
@@ -61500,36 +61601,36 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
61500
61601
  const pinnedPostIds = await platform.getPinnedPosts();
61501
61602
  const recentPinnedIds = pinnedPostIds.filter((id) => id !== currentStickyId && !excludePostIds?.has(id) && isRecentPost(id));
61502
61603
  if (recentPinnedIds.length === 0) {
61503
- log20.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
61604
+ log19.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
61504
61605
  return;
61505
61606
  }
61506
- log20.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
61607
+ log19.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
61507
61608
  for (const postId of recentPinnedIds) {
61508
61609
  try {
61509
61610
  const post2 = await platform.getPost(postId);
61510
61611
  if (!post2)
61511
61612
  continue;
61512
61613
  if (post2.userId === botUserId) {
61513
- log20.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
61614
+ log19.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
61514
61615
  try {
61515
61616
  await platform.unpinPost(postId);
61516
61617
  await platform.deletePost(postId);
61517
- log20.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
61618
+ log19.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
61518
61619
  } catch (err) {
61519
- log20.debug(`Failed to cleanup ${postId}: ${err}`);
61620
+ log19.debug(`Failed to cleanup ${postId}: ${err}`);
61520
61621
  }
61521
61622
  }
61522
61623
  } catch (err) {
61523
- log20.debug(`Could not check post ${postId}: ${err}`);
61624
+ log19.debug(`Could not check post ${postId}: ${err}`);
61524
61625
  }
61525
61626
  }
61526
61627
  } catch (err) {
61527
- log20.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
61628
+ log19.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
61528
61629
  }
61529
61630
  }
61530
61631
  // src/operations/bug-report/handler.ts
61531
61632
  import { execSync as execSync2 } from "child_process";
61532
- import { writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
61633
+ import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
61533
61634
  import { tmpdir as tmpdir2 } from "os";
61534
61635
  import { join as join8 } from "path";
61535
61636
 
@@ -62113,7 +62214,7 @@ async function createGitHubIssue(title, body, workingDir) {
62113
62214
  }
62114
62215
  const bodyFile = join8(tmpdir2(), `bug-body-${Date.now()}.md`);
62115
62216
  try {
62116
- writeFileSync3(bodyFile, body, "utf-8");
62217
+ writeFileSync4(bodyFile, body, "utf-8");
62117
62218
  const cmd = `gh issue create --repo "${GITHUB_REPO}" --title "${escapeShell(title)}" --body-file "${bodyFile}"`;
62118
62219
  const result = execSync2(cmd, {
62119
62220
  cwd: workingDir,
@@ -62518,9 +62619,9 @@ node_default(Temp.purgeSyncAll);
62518
62619
  var temp_default = Temp;
62519
62620
 
62520
62621
  // node_modules/atomically/dist/index.js
62521
- function writeFileSync4(filePath, data, options2 = DEFAULT_WRITE_OPTIONS) {
62622
+ function writeFileSync5(filePath, data, options2 = DEFAULT_WRITE_OPTIONS) {
62522
62623
  if (isString(options2))
62523
- return writeFileSync4(filePath, data, { encoding: options2 });
62624
+ return writeFileSync5(filePath, data, { encoding: options2 });
62524
62625
  const timeout = options2.timeout ?? DEFAULT_TIMEOUT_SYNC;
62525
62626
  const retryOptions = { timeout };
62526
62627
  let tempDisposer = null;
@@ -62848,7 +62949,7 @@ class Configstore {
62848
62949
  }
62849
62950
  if (error.name === "SyntaxError") {
62850
62951
  if (this._clearInvalidConfig) {
62851
- writeFileSync4(this._path, "", writeFileOptions);
62952
+ writeFileSync5(this._path, "", writeFileOptions);
62852
62953
  return {};
62853
62954
  }
62854
62955
  throw error;
@@ -62860,7 +62961,7 @@ class Configstore {
62860
62961
  set all(value) {
62861
62962
  try {
62862
62963
  import_graceful_fs.default.mkdirSync(path5.dirname(this._path), mkdirOptions);
62863
- writeFileSync4(this._path, JSON.stringify(value, undefined, "\t"), writeFileOptions);
62964
+ writeFileSync5(this._path, JSON.stringify(value, undefined, "\t"), writeFileOptions);
62864
62965
  } catch (error) {
62865
62966
  handlePermissionError(error);
62866
62967
  }
@@ -65587,8 +65688,8 @@ function getUpdateInfo() {
65587
65688
  init_emoji();
65588
65689
  init_logger();
65589
65690
  init_worktree();
65590
- var log21 = createLogger("commands");
65591
- var sessionLog2 = createSessionLog(log21);
65691
+ var log20 = createLogger("commands");
65692
+ var sessionLog2 = createSessionLog(log20);
65592
65693
  function sessionAccountOption(session, ctx) {
65593
65694
  if (!session.claudeAccountId)
65594
65695
  return;
@@ -65751,7 +65852,11 @@ ${CHAT_PLATFORM_PROMPT}`;
65751
65852
  const cliOptions = {
65752
65853
  workingDir: absoluteDir,
65753
65854
  threadId: session.threadId,
65754
- skipPermissions: ctx.config.skipPermissions || !session.forceInteractivePermissions,
65855
+ permissionMode: effectivePermissionMode({
65856
+ override: session.permissionModeOverride,
65857
+ sessionHasInteractiveOverride: session.forceInteractivePermissions,
65858
+ botWideMode: ctx.config.permissionMode
65859
+ }),
65755
65860
  sessionId: newSessionId,
65756
65861
  resume: false,
65757
65862
  chrome: ctx.config.chromeEnabled,
@@ -65828,45 +65933,37 @@ async function kickUser(session, kickedUser, kickedBy, ctx) {
65828
65933
  sessionLog2(session).warn(`\uD83D\uDEAB @${kickedUser} was not in session`);
65829
65934
  }
65830
65935
  }
65831
- async function enableInteractivePermissions(session, username, ctx) {
65936
+ async function setSessionPermissionMode(session, username, mode, ctx) {
65832
65937
  if (!await requireSessionOwner(session, username, "change permissions")) {
65833
65938
  return;
65834
65939
  }
65835
- if (!ctx.config.skipPermissions) {
65836
- await post(session, "info", `Permissions are already interactive for this session`);
65837
- sessionLog2(session).debug(`\uD83D\uDD10 Permissions already interactive (global setting)`);
65838
- return;
65839
- }
65840
- if (session.forceInteractivePermissions) {
65841
- await post(session, "info", `Interactive permissions already enabled for this session`);
65842
- sessionLog2(session).debug(`\uD83D\uDD10 Permissions already interactive (session override)`);
65843
- return;
65844
- }
65845
- session.forceInteractivePermissions = true;
65846
- sessionLog2(session).info(`\uD83D\uDD10 Enabling interactive permissions`);
65847
- session.threadLogger?.logCommand("permissions", "interactive", username);
65940
+ session.permissionModeOverride = mode;
65941
+ session.forceInteractivePermissions = mode === "default";
65942
+ sessionLog2(session).info(`\uD83D\uDD10 Setting permission mode to "${mode}"`);
65943
+ session.threadLogger?.logCommand("permissions", mode, username);
65944
+ const canResume = session.lifecycle.hasClaudeResponded;
65848
65945
  const cliOptions = {
65849
65946
  workingDir: session.workingDir,
65850
65947
  threadId: session.threadId,
65851
- skipPermissions: false,
65948
+ permissionMode: mode,
65852
65949
  sessionId: session.claudeSessionId,
65853
- resume: true,
65950
+ resume: canResume,
65854
65951
  chrome: ctx.config.chromeEnabled,
65855
65952
  platformConfig: session.platform.getMcpConfig(),
65856
65953
  logSessionId: session.sessionId,
65857
65954
  permissionTimeoutMs: ctx.config.permissionTimeoutMs,
65858
65955
  account: sessionAccountOption(session, ctx)
65859
65956
  };
65860
- const success = await restartClaudeSession(session, cliOptions, ctx, "Enable interactive permissions");
65957
+ const success = await restartClaudeSession(session, cliOptions, ctx, `Set permission mode to ${mode}`);
65861
65958
  if (!success)
65862
65959
  return;
65863
65960
  await updateSessionHeader(session, ctx);
65864
65961
  const formatter = session.platform.getFormatter();
65865
- await post(session, "secure", `${formatter.formatBold("Interactive permissions enabled")} for this session by ${formatter.formatUserMention(username)}
65866
- ${formatter.formatItalic("Claude Code restarted with permission prompts")}`);
65867
- sessionLog2(session).info(`\uD83D\uDD10 Interactive permissions enabled by @${username}`);
65868
- resetSessionActivity(session);
65869
- ctx.ops.persistSession(session);
65962
+ const display = permissionModeDisplay(mode);
65963
+ await post(session, "secure", `${display.icon} ${formatter.formatBold(`Permission mode: ${display.label}`)} set for this session by ${formatter.formatUserMention(username)}
65964
+ ` + `${formatter.formatItalic(permissionModeDescription(mode))}
65965
+ ` + `${formatter.formatItalic("Claude Code restarted.")}`);
65966
+ sessionLog2(session).info(`\uD83D\uDD10 Permission mode set to "${mode}" by @${username}`);
65870
65967
  }
65871
65968
  async function requestMessageApproval(session, username, message, ctx) {
65872
65969
  if (session.messageManager?.getPendingMessageApproval()) {
@@ -65892,8 +65989,12 @@ async function updateSessionHeader(session, ctx) {
65892
65989
  const formatter = session.platform.getFormatter();
65893
65990
  const worktreeContext = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
65894
65991
  const shortDir = shortenPath(session.workingDir, undefined, worktreeContext);
65895
- const isInteractive = !ctx.config.skipPermissions || session.forceInteractivePermissions;
65896
- const permMode = isInteractive ? "\uD83D\uDD10 Interactive" : "⚡ Auto";
65992
+ const effectiveMode = effectivePermissionMode({
65993
+ override: session.permissionModeOverride,
65994
+ sessionHasInteractiveOverride: session.forceInteractivePermissions,
65995
+ botWideMode: ctx.config.permissionMode
65996
+ });
65997
+ const permMode = permissionModeDisplay(effectiveMode).chip;
65897
65998
  const otherParticipants = [...session.sessionAllowedUsers].filter((u) => u !== session.startedBy).map((u) => formatter.formatUserMention(u)).join(", ");
65898
65999
  const statusItems = [];
65899
66000
  const versionStr = formatVersionString();
@@ -65982,7 +66083,7 @@ async function updateSessionHeader(session, ctx) {
65982
66083
  ].filter((item) => item !== null && item !== undefined).join(`
65983
66084
  `);
65984
66085
  const postId = session.sessionStartPostId;
65985
- await updatePost2(session, postId, msg);
66086
+ await updatePost(session, postId, msg);
65986
66087
  }
65987
66088
  async function showUpdateStatus(session, updateManager, ctx) {
65988
66089
  const formatter = session.platform.getFormatter();
@@ -66122,7 +66223,7 @@ init_logger();
66122
66223
  import { exec as exec3 } from "child_process";
66123
66224
  import { promisify as promisify3 } from "util";
66124
66225
  var execAsync2 = promisify3(exec3);
66125
- var log22 = createLogger("branch");
66226
+ var log21 = createLogger("branch");
66126
66227
  var SUGGESTION_TIMEOUT3 = 15000;
66127
66228
  var MAX_SUGGESTIONS = 3;
66128
66229
  async function getCurrentBranch3(workingDir) {
@@ -66171,7 +66272,7 @@ function parseBranchSuggestions(response) {
66171
66272
  return lines.slice(0, MAX_SUGGESTIONS);
66172
66273
  }
66173
66274
  async function suggestBranchNames(workingDir, userMessage) {
66174
- log22.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
66275
+ log21.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
66175
66276
  try {
66176
66277
  const [currentBranch, recentCommits] = await Promise.all([
66177
66278
  getCurrentBranch3(workingDir),
@@ -66185,14 +66286,14 @@ async function suggestBranchNames(workingDir, userMessage) {
66185
66286
  workingDir
66186
66287
  });
66187
66288
  if (!result.success || !result.response) {
66188
- log22.debug(`Branch suggestion failed: ${result.error || "no response"}`);
66289
+ log21.debug(`Branch suggestion failed: ${result.error || "no response"}`);
66189
66290
  return [];
66190
66291
  }
66191
66292
  const suggestions = parseBranchSuggestions(result.response);
66192
- log22.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
66293
+ log21.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
66193
66294
  return suggestions;
66194
66295
  } catch (err) {
66195
- log22.debug(`Branch suggestion error: ${err}`);
66296
+ log21.debug(`Branch suggestion error: ${err}`);
66196
66297
  return [];
66197
66298
  }
66198
66299
  }
@@ -66201,8 +66302,8 @@ async function suggestBranchNames(workingDir, userMessage) {
66201
66302
  init_worktree();
66202
66303
  import { randomUUID as randomUUID3 } from "crypto";
66203
66304
  init_logger();
66204
- var log23 = createLogger("worktree");
66205
- var sessionLog3 = createSessionLog(log23);
66305
+ var log22 = createLogger("worktree");
66306
+ var sessionLog3 = createSessionLog(log22);
66206
66307
  function parseWorktreeError(error) {
66207
66308
  const message = error instanceof Error ? error.message : String(error);
66208
66309
  const lowerMessage = message.toLowerCase();
@@ -66441,7 +66542,11 @@ async function createAndSwitchToWorktree(session, branch, username, options2) {
66441
66542
  const cliOptions = {
66442
66543
  workingDir: existing.path,
66443
66544
  threadId: session.threadId,
66444
- skipPermissions: options2.skipPermissions || !session.forceInteractivePermissions,
66545
+ permissionMode: effectivePermissionMode({
66546
+ override: session.permissionModeOverride,
66547
+ sessionHasInteractiveOverride: session.forceInteractivePermissions,
66548
+ botWideMode: options2.permissionMode
66549
+ }),
66445
66550
  sessionId: newSessionId,
66446
66551
  resume: false,
66447
66552
  chrome: options2.chromeEnabled,
@@ -66530,7 +66635,11 @@ ${fmt.formatItalic("Claude Code restarted in the worktree")}`);
66530
66635
  const cliOptions = {
66531
66636
  workingDir: worktreePath,
66532
66637
  threadId: session.threadId,
66533
- skipPermissions: options2.skipPermissions || !session.forceInteractivePermissions,
66638
+ permissionMode: effectivePermissionMode({
66639
+ override: session.permissionModeOverride,
66640
+ sessionHasInteractiveOverride: session.forceInteractivePermissions,
66641
+ botWideMode: options2.permissionMode
66642
+ }),
66534
66643
  sessionId: newSessionId,
66535
66644
  resume: false,
66536
66645
  chrome: options2.chromeEnabled,
@@ -66576,7 +66685,7 @@ ${fmt.formatItalic("Claude Code restarted in the new worktree")}`);
66576
66685
  const { summary, suggestion } = parseWorktreeError(err);
66577
66686
  const worktreePromptId = session.worktreePromptPostId;
66578
66687
  if (worktreePromptId) {
66579
- await updatePost2(session, worktreePromptId, `❌ ${fmt.formatBold(summary)}: ${fmt.formatCode(branch)}`);
66688
+ await updatePost(session, worktreePromptId, `❌ ${fmt.formatBold(summary)}: ${fmt.formatCode(branch)}`);
66580
66689
  await removeReaction(session, worktreePromptId, "x");
66581
66690
  }
66582
66691
  if (options2.worktreeMode === "require") {
@@ -66753,8 +66862,8 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
66753
66862
  }
66754
66863
  // src/operations/events/handler.ts
66755
66864
  init_logger();
66756
- var log24 = createLogger("events");
66757
- var sessionLog4 = createSessionLog(log24);
66865
+ var log23 = createLogger("events");
66866
+ var sessionLog4 = createSessionLog(log23);
66758
66867
  function detectAndExecuteClaudeCommands(text, session, ctx) {
66759
66868
  const parsed = parseClaudeCommand(text);
66760
66869
  if (parsed && isClaudeAllowedCommand(parsed.command)) {
@@ -66887,7 +66996,7 @@ async function handleCompactionComplete(session, compactMetadata, _ctx) {
66887
66996
  const formatter = session.platform.getFormatter();
66888
66997
  const completionMessage = `✅ ${formatter.formatBold("Context compacted")} ${formatter.formatItalic(`(${info})`)}`;
66889
66998
  if (session.compactionPostId) {
66890
- await updatePost2(session, session.compactionPostId, completionMessage);
66999
+ await updatePost(session, session.compactionPostId, completionMessage);
66891
67000
  session.compactionPostId = undefined;
66892
67001
  } else {
66893
67002
  await withErrorHandling(() => post(session, "info", completionMessage), { action: "Post compaction complete", session });
@@ -67002,8 +67111,8 @@ function createSessionContext(config, state, ops) {
67002
67111
  // src/operations/context-prompt/handler.ts
67003
67112
  init_emoji();
67004
67113
  init_logger();
67005
- var log25 = createLogger("context");
67006
- var sessionLog5 = createSessionLog(log25);
67114
+ var log24 = createLogger("context");
67115
+ var sessionLog5 = createSessionLog(log24);
67007
67116
  var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
67008
67117
  var CONTEXT_OPTIONS = [3, 5, 10];
67009
67118
  var contextPromptTimeouts = new Map;
@@ -67253,8 +67362,8 @@ function formatRelativeTime(date) {
67253
67362
  }
67254
67363
  // src/session/lifecycle.ts
67255
67364
  init_worktree();
67256
- var log26 = createLogger("lifecycle");
67257
- var sessionLog6 = createSessionLog(log26);
67365
+ var log25 = createLogger("lifecycle");
67366
+ var sessionLog6 = createSessionLog(log25);
67258
67367
  function mutableSessions(ctx) {
67259
67368
  return ctx.state.sessions;
67260
67369
  }
@@ -67366,7 +67475,8 @@ function createMessageManager(session, ctx) {
67366
67475
  },
67367
67476
  emitSessionUpdate: (updates) => {
67368
67477
  ctx.ops.emitSessionUpdate(session.sessionId, updates);
67369
- }
67478
+ },
67479
+ flushDelayMs: ctx.config.flushDelayMs
67370
67480
  });
67371
67481
  messageManager.events.on("question:complete", ({ toolUseId: _toolUseId, answers }) => {
67372
67482
  const answerJson = JSON.stringify(answers);
@@ -67618,8 +67728,9 @@ async function startSession(options2, username, displayName, replyToPostId, plat
67618
67728
  platform.sendTyping(actualThreadId);
67619
67729
  const claudeSessionId = randomUUID4();
67620
67730
  let workingDir = ctx.config.workingDir;
67621
- let skipPermissions = ctx.config.skipPermissions;
67731
+ let permissionMode = ctx.config.permissionMode;
67622
67732
  let forceInteractivePermissions = false;
67733
+ let sessionPermissionModeOverride;
67623
67734
  const formatter = platform.getFormatter();
67624
67735
  if (initialOptions?.workingDir) {
67625
67736
  const { resolve: resolve6 } = await import("path");
@@ -67637,12 +67748,17 @@ async function startSession(options2, username, displayName, replyToPostId, plat
67637
67748
  return;
67638
67749
  }
67639
67750
  workingDir = resolvedDir;
67640
- log26.info(`Starting session in directory: ${workingDir} (from !cd command)`);
67641
- }
67642
- if (initialOptions?.forceInteractivePermissions) {
67751
+ log25.info(`Starting session in directory: ${workingDir} (from !cd command)`);
67752
+ }
67753
+ if (initialOptions?.permissionMode) {
67754
+ permissionMode = initialOptions.permissionMode;
67755
+ forceInteractivePermissions = permissionMode === "default";
67756
+ sessionPermissionModeOverride = permissionMode;
67757
+ log25.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
67758
+ } else if (initialOptions?.forceInteractivePermissions) {
67643
67759
  forceInteractivePermissions = true;
67644
- skipPermissions = false;
67645
- log26.info(`Starting session with interactive permissions (from !permissions command)`);
67760
+ permissionMode = "default";
67761
+ log25.info(`Starting session with interactive permissions (from !permissions command)`);
67646
67762
  }
67647
67763
  const sessionContext = buildSessionContext(platform, workingDir);
67648
67764
  const systemPrompt = `${sessionContext}
@@ -67651,12 +67767,12 @@ ${CHAT_PLATFORM_PROMPT}`;
67651
67767
  const platformMcpConfig = platform.getMcpConfig();
67652
67768
  const claudeAccount = ctx.ops.acquireClaudeAccount();
67653
67769
  if (claudeAccount) {
67654
- log26.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
67770
+ log25.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
67655
67771
  }
67656
67772
  const cliOptions = {
67657
67773
  workingDir,
67658
67774
  threadId: actualThreadId,
67659
- skipPermissions,
67775
+ permissionMode,
67660
67776
  sessionId: claudeSessionId,
67661
67777
  resume: false,
67662
67778
  chrome: ctx.config.chromeEnabled,
@@ -67684,6 +67800,7 @@ ${CHAT_PLATFORM_PROMPT}`;
67684
67800
  planApproved: false,
67685
67801
  sessionAllowedUsers: new Set([username]),
67686
67802
  forceInteractivePermissions,
67803
+ permissionModeOverride: sessionPermissionModeOverride,
67687
67804
  sessionStartPostId: startPost.id,
67688
67805
  timers: createSessionTimers(),
67689
67806
  lifecycle: createSessionLifecycle(),
@@ -67755,28 +67872,28 @@ async function resumeSession(state, ctx) {
67755
67872
  !state.claudeSessionId && "claudeSessionId",
67756
67873
  !state.workingDir && "workingDir"
67757
67874
  ].filter(Boolean).join(", ");
67758
- log26.warn(`Skipping session with missing required fields: ${missing}`);
67875
+ log25.warn(`Skipping session with missing required fields: ${missing}`);
67759
67876
  return;
67760
67877
  }
67761
67878
  const shortId = state.threadId.substring(0, 8);
67762
67879
  const platforms = ctx.state.platforms;
67763
67880
  const platform = platforms.get(state.platformId);
67764
67881
  if (!platform) {
67765
- log26.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
67882
+ log25.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
67766
67883
  return;
67767
67884
  }
67768
67885
  const threadPost = await platform.getPost(state.threadId);
67769
67886
  if (!threadPost) {
67770
- log26.warn(`Thread ${shortId}... deleted, skipping resume`);
67887
+ log25.warn(`Thread ${shortId}... deleted, skipping resume`);
67771
67888
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
67772
67889
  return;
67773
67890
  }
67774
67891
  if (ctx.state.sessions.size >= ctx.config.maxSessions) {
67775
- log26.warn(`Max sessions reached, skipping resume for ${shortId}...`);
67892
+ log25.warn(`Max sessions reached, skipping resume for ${shortId}...`);
67776
67893
  return;
67777
67894
  }
67778
67895
  if (!existsSync11(state.workingDir)) {
67779
- log26.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
67896
+ log25.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
67780
67897
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
67781
67898
  const resumeFormatter = platform.getFormatter();
67782
67899
  const tempSession = {
@@ -67792,7 +67909,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
67792
67909
  }
67793
67910
  const platformId = state.platformId;
67794
67911
  const sessionId = ctx.ops.getSessionId(platformId, state.threadId);
67795
- const skipPerms = ctx.config.skipPermissions && !state.forceInteractivePermissions;
67912
+ const resumePermissionMode = state.forceInteractivePermissions ? "default" : ctx.config.permissionMode;
67796
67913
  const platformMcpConfig = platform.getMcpConfig();
67797
67914
  const sessionContext = buildSessionContext(platform, state.workingDir);
67798
67915
  const appendSystemPrompt = `${sessionContext}
@@ -67800,12 +67917,12 @@ Please start a new session.`), { action: "Post resume failure notification" });
67800
67917
  ${CHAT_PLATFORM_PROMPT}`;
67801
67918
  const claudeAccount = ctx.ops.acquireClaudeAccount(state.claudeAccountId);
67802
67919
  if (state.claudeAccountId && !claudeAccount) {
67803
- log26.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
67920
+ log25.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
67804
67921
  }
67805
67922
  const cliOptions = {
67806
67923
  workingDir: state.workingDir,
67807
67924
  threadId: state.threadId,
67808
- skipPermissions: skipPerms,
67925
+ permissionMode: resumePermissionMode,
67809
67926
  sessionId: state.claudeSessionId,
67810
67927
  resume: true,
67811
67928
  chrome: ctx.config.chromeEnabled,
@@ -67865,7 +67982,7 @@ ${CHAT_PLATFORM_PROMPT}`;
67865
67982
  worktreePath: detected.worktreePath,
67866
67983
  branch: detected.branch
67867
67984
  };
67868
- log26.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
67985
+ log25.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
67869
67986
  }
67870
67987
  }
67871
67988
  session.messageManager = createMessageManager(session, ctx);
@@ -67921,7 +68038,7 @@ ${sessionFormatter.formatItalic("Reconnected to Claude session. You can continue
67921
68038
  await ctx.ops.updateStickyMessage();
67922
68039
  ctx.ops.persistSession(session);
67923
68040
  } catch (err) {
67924
- log26.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
68041
+ log25.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
67925
68042
  ctx.ops.emitSessionRemove(sessionId);
67926
68043
  mutableSessions(ctx).delete(sessionId);
67927
68044
  ctx.state.sessionStore.remove(sessionId);
@@ -67961,18 +68078,18 @@ async function resumePausedSession(threadId, message, files, ctx) {
67961
68078
  const persisted = ctx.state.sessionStore.load();
67962
68079
  const state = findPersistedByThreadId(persisted, threadId);
67963
68080
  if (!state) {
67964
- log26.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
68081
+ log25.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
67965
68082
  return;
67966
68083
  }
67967
68084
  const shortId = threadId.substring(0, 8);
67968
- log26.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
68085
+ log25.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
67969
68086
  await resumeSession(state, ctx);
67970
68087
  const session = ctx.ops.findSessionByThreadId(threadId);
67971
68088
  if (session && session.claude.isRunning() && session.messageManager) {
67972
68089
  session.messageCount++;
67973
68090
  await session.messageManager.handleUserMessage(message, files, state.startedBy);
67974
68091
  } else {
67975
- log26.warn(`Failed to resume session ${shortId}..., could not send message`);
68092
+ log25.warn(`Failed to resume session ${shortId}..., could not send message`);
67976
68093
  }
67977
68094
  }
67978
68095
  async function handleExit(sessionId, code, ctx) {
@@ -67980,7 +68097,7 @@ async function handleExit(sessionId, code, ctx) {
67980
68097
  const shortId = sessionId.substring(0, 8);
67981
68098
  sessionLog6(session).debug(`handleExit called code=${code} isShuttingDown=${ctx.state.isShuttingDown}`);
67982
68099
  if (!session) {
67983
- log26.debug(`Session ${shortId}... not found (already cleaned up)`);
68100
+ log25.debug(`Session ${shortId}... not found (already cleaned up)`);
67984
68101
  return;
67985
68102
  }
67986
68103
  if (isSessionRestarting(session)) {
@@ -68173,7 +68290,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
68173
68290
  }
68174
68291
 
68175
68292
  // src/operations/monitor/handler.ts
68176
- var log27 = createLogger("monitor");
68293
+ var log26 = createLogger("monitor");
68177
68294
  var DEFAULT_INTERVAL_MS = 60 * 1000;
68178
68295
 
68179
68296
  class SessionMonitor {
@@ -68195,14 +68312,14 @@ class SessionMonitor {
68195
68312
  }
68196
68313
  start() {
68197
68314
  if (this.isRunning) {
68198
- log27.debug("Session monitor already running");
68315
+ log26.debug("Session monitor already running");
68199
68316
  return;
68200
68317
  }
68201
68318
  this.isRunning = true;
68202
- log27.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
68319
+ log26.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
68203
68320
  this.timer = setInterval(() => {
68204
68321
  this.runCheck().catch((err) => {
68205
- log27.error(`Error during session monitoring: ${err}`);
68322
+ log26.error(`Error during session monitoring: ${err}`);
68206
68323
  });
68207
68324
  }, this.intervalMs);
68208
68325
  }
@@ -68212,7 +68329,7 @@ class SessionMonitor {
68212
68329
  this.timer = null;
68213
68330
  }
68214
68331
  this.isRunning = false;
68215
- log27.debug("Session monitor stopped");
68332
+ log26.debug("Session monitor stopped");
68216
68333
  }
68217
68334
  async runCheck() {
68218
68335
  await cleanupIdleSessions(this.sessionTimeoutMs, this.sessionWarningMs, this.getContext());
@@ -68224,8 +68341,8 @@ class SessionMonitor {
68224
68341
  // src/operations/plugin/handler.ts
68225
68342
  init_spawn();
68226
68343
  init_logger();
68227
- var log28 = createLogger("plugin");
68228
- var sessionLog7 = createSessionLog(log28);
68344
+ var log27 = createLogger("plugin");
68345
+ var sessionLog7 = createSessionLog(log27);
68229
68346
  async function runPluginCommand(args, cwd, timeout2 = 60000) {
68230
68347
  return new Promise((resolve6) => {
68231
68348
  const claudePath = process.env.CLAUDE_PATH || "claude";
@@ -68246,7 +68363,7 @@ async function runPluginCommand(args, cwd, timeout2 = 60000) {
68246
68363
  });
68247
68364
  proc.on("error", (err) => {
68248
68365
  resolve6({ stdout, stderr, exitCode: 1 });
68249
- log28.error(`Plugin command error: ${err.message}`);
68366
+ log27.error(`Plugin command error: ${err.message}`);
68250
68367
  });
68251
68368
  });
68252
68369
  }
@@ -68282,9 +68399,13 @@ ${formatter.formatCodeBlock(errorMsg, "text")}`);
68282
68399
  const cliOptions = {
68283
68400
  workingDir: session.workingDir,
68284
68401
  threadId: session.threadId,
68285
- skipPermissions: ctx.config.skipPermissions || !session.forceInteractivePermissions,
68402
+ permissionMode: effectivePermissionMode({
68403
+ override: session.permissionModeOverride,
68404
+ sessionHasInteractiveOverride: session.forceInteractivePermissions,
68405
+ botWideMode: ctx.config.permissionMode
68406
+ }),
68286
68407
  sessionId: session.claudeSessionId,
68287
- resume: true,
68408
+ resume: session.lifecycle.hasClaudeResponded,
68288
68409
  chrome: ctx.config.chromeEnabled,
68289
68410
  platformConfig: session.platform.getMcpConfig(),
68290
68411
  logSessionId: session.sessionId,
@@ -68315,9 +68436,13 @@ ${formatter.formatCodeBlock(errorMsg, "text")}`);
68315
68436
  const cliOptions = {
68316
68437
  workingDir: session.workingDir,
68317
68438
  threadId: session.threadId,
68318
- skipPermissions: ctx.config.skipPermissions || !session.forceInteractivePermissions,
68439
+ permissionMode: effectivePermissionMode({
68440
+ override: session.permissionModeOverride,
68441
+ sessionHasInteractiveOverride: session.forceInteractivePermissions,
68442
+ botWideMode: ctx.config.permissionMode
68443
+ }),
68319
68444
  sessionId: session.claudeSessionId,
68320
- resume: true,
68445
+ resume: session.lifecycle.hasClaudeResponded,
68321
68446
  chrome: ctx.config.chromeEnabled,
68322
68447
  platformConfig: session.platform.getMcpConfig(),
68323
68448
  logSessionId: session.sessionId,
@@ -68439,12 +68564,12 @@ class SessionRegistry {
68439
68564
 
68440
68565
  // src/session/manager.ts
68441
68566
  init_logger();
68442
- var log29 = createLogger("manager");
68567
+ var log28 = createLogger("manager");
68443
68568
 
68444
68569
  class SessionManager extends EventEmitter4 {
68445
68570
  platforms = new Map;
68446
68571
  workingDir;
68447
- skipPermissions;
68572
+ permissionMode;
68448
68573
  chromeEnabled;
68449
68574
  worktreeMode;
68450
68575
  threadLogsEnabled;
@@ -68463,10 +68588,10 @@ class SessionManager extends EventEmitter4 {
68463
68588
  customFooter;
68464
68589
  autoUpdateManager = null;
68465
68590
  accountPool;
68466
- constructor(workingDir, skipPermissions = false, chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts) {
68591
+ constructor(workingDir, permissionModeOrSkipFlag = "default", chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts) {
68467
68592
  super();
68468
68593
  this.workingDir = workingDir;
68469
- this.skipPermissions = skipPermissions;
68594
+ this.permissionMode = typeof permissionModeOrSkipFlag === "boolean" ? permissionModeOrSkipFlag ? "bypass" : "default" : permissionModeOrSkipFlag;
68470
68595
  this.chromeEnabled = chromeEnabled;
68471
68596
  this.worktreeMode = worktreeMode;
68472
68597
  this.threadLogsEnabled = threadLogsEnabled;
@@ -68508,7 +68633,7 @@ class SessionManager extends EventEmitter4 {
68508
68633
  markNeedsBump(platformId);
68509
68634
  this.updateStickyMessage();
68510
68635
  });
68511
- log29.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
68636
+ log28.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
68512
68637
  }
68513
68638
  removePlatform(platformId) {
68514
68639
  this.platforms.delete(platformId);
@@ -68524,7 +68649,7 @@ class SessionManager extends EventEmitter4 {
68524
68649
  if (users) {
68525
68650
  users.add(sessionId);
68526
68651
  }
68527
- log29.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
68652
+ log28.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
68528
68653
  }
68529
68654
  unregisterWorktreeUser(worktreePath, sessionId) {
68530
68655
  const users = this.worktreeUsers.get(worktreePath);
@@ -68544,13 +68669,14 @@ class SessionManager extends EventEmitter4 {
68544
68669
  getContext() {
68545
68670
  const config = {
68546
68671
  workingDir: this.workingDir,
68547
- skipPermissions: this.skipPermissions,
68672
+ permissionMode: this.permissionMode,
68548
68673
  chromeEnabled: this.chromeEnabled,
68549
68674
  debug: this.debug,
68550
68675
  maxSessions: this.limits.maxSessions,
68551
68676
  threadLogsEnabled: this.threadLogsEnabled,
68552
68677
  threadLogsRetentionDays: this.threadLogsRetentionDays,
68553
- permissionTimeoutMs: this.limits.permissionTimeoutSeconds * 1000
68678
+ permissionTimeoutMs: this.limits.permissionTimeoutSeconds * 1000,
68679
+ flushDelayMs: this.limits.flushDelayMs
68554
68680
  };
68555
68681
  const state = {
68556
68682
  sessions: this.registry.getSessions(),
@@ -68649,6 +68775,15 @@ class SessionManager extends EventEmitter4 {
68649
68775
  if (session.platformId !== platformId)
68650
68776
  return;
68651
68777
  if (!session.sessionAllowedUsers.has(username) && !session.platform.isUserAllowed(username)) {
68778
+ log28.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
68779
+ event: "reaction.rejected",
68780
+ platformId,
68781
+ sessionId: session.sessionId,
68782
+ postId,
68783
+ emoji: normalizedEmoji,
68784
+ action,
68785
+ user: username
68786
+ });
68652
68787
  return;
68653
68788
  }
68654
68789
  await this.handleSessionReaction(session, postId, normalizedEmoji, username, action);
@@ -68676,7 +68811,7 @@ class SessionManager extends EventEmitter4 {
68676
68811
  return false;
68677
68812
  }
68678
68813
  const shortId = persistedSession.threadId.substring(0, 8);
68679
- log29.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
68814
+ log28.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
68680
68815
  await resumeSession(persistedSession, this.getContext());
68681
68816
  return true;
68682
68817
  }
@@ -68706,7 +68841,7 @@ class SessionManager extends EventEmitter4 {
68706
68841
  }
68707
68842
  if (session.lastError?.postId === postId && isBugReportEmoji(emojiName)) {
68708
68843
  if (session.startedBy === username || session.platform.isUserAllowed(username) || session.sessionAllowedUsers.has(username)) {
68709
- log29.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
68844
+ log28.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
68710
68845
  await reportBug(session, undefined, username, this.getContext(), session.lastError);
68711
68846
  return;
68712
68847
  }
@@ -68843,7 +68978,7 @@ class SessionManager extends EventEmitter4 {
68843
68978
  await updateAllStickyMessages(this.platforms, this.registry.getSessions(), {
68844
68979
  maxSessions: this.limits.maxSessions,
68845
68980
  chromeEnabled: this.chromeEnabled,
68846
- skipPermissions: this.skipPermissions,
68981
+ permissionMode: this.permissionMode,
68847
68982
  worktreeMode: this.worktreeMode,
68848
68983
  workingDir: this.workingDir,
68849
68984
  debug: this.debug,
@@ -68859,8 +68994,14 @@ class SessionManager extends EventEmitter4 {
68859
68994
  this.customDescription = description;
68860
68995
  this.customFooter = footer;
68861
68996
  }
68997
+ setPermissionMode(mode) {
68998
+ this.permissionMode = mode;
68999
+ }
68862
69000
  setSkipPermissions(value) {
68863
- this.skipPermissions = value;
69001
+ this.permissionMode = value ? "bypass" : "default";
69002
+ }
69003
+ getPermissionMode() {
69004
+ return this.permissionMode;
68864
69005
  }
68865
69006
  setChromeEnabled(value) {
68866
69007
  this.chromeEnabled = value;
@@ -68874,11 +69015,11 @@ class SessionManager extends EventEmitter4 {
68874
69015
  }
68875
69016
  }
68876
69017
  if (sessionsToKill.length === 0) {
68877
- log29.info(`No active sessions to pause for platform ${platformId}`);
69018
+ log28.info(`No active sessions to pause for platform ${platformId}`);
68878
69019
  await this.updateStickyMessage();
68879
69020
  return;
68880
69021
  }
68881
- log29.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
69022
+ log28.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
68882
69023
  for (const session of sessionsToKill) {
68883
69024
  try {
68884
69025
  const fmt = session.platform.getFormatter();
@@ -68894,9 +69035,9 @@ class SessionManager extends EventEmitter4 {
68894
69035
  session.claude.kill();
68895
69036
  this.registry.unregister(session.sessionId);
68896
69037
  this.emitSessionRemove(session.sessionId);
68897
- log29.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
69038
+ log28.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
68898
69039
  } catch (err) {
68899
- log29.warn(`Failed to pause session ${session.threadId}: ${err}`);
69040
+ log28.warn(`Failed to pause session ${session.threadId}: ${err}`);
68900
69041
  }
68901
69042
  }
68902
69043
  for (const session of sessionsToKill) {
@@ -68917,17 +69058,17 @@ class SessionManager extends EventEmitter4 {
68917
69058
  sessionsToResume.push(state);
68918
69059
  }
68919
69060
  if (sessionsToResume.length === 0) {
68920
- log29.info(`No paused sessions to resume for platform ${platformId}`);
69061
+ log28.info(`No paused sessions to resume for platform ${platformId}`);
68921
69062
  await this.updateStickyMessage();
68922
69063
  return;
68923
69064
  }
68924
- log29.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
69065
+ log28.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
68925
69066
  for (const state of sessionsToResume) {
68926
69067
  try {
68927
69068
  await resumeSession(state, this.getContext());
68928
- log29.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
69069
+ log28.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
68929
69070
  } catch (err) {
68930
- log29.warn(`Failed to resume session ${state.threadId}: ${err}`);
69071
+ log28.warn(`Failed to resume session ${state.threadId}: ${err}`);
68931
69072
  }
68932
69073
  }
68933
69074
  await this.updateStickyMessage();
@@ -68939,14 +69080,14 @@ class SessionManager extends EventEmitter4 {
68939
69080
  const sessionTimeoutMs = this.limits.sessionTimeoutMinutes * 60 * 1000;
68940
69081
  const staleIds = this.sessionStore.cleanStale(sessionTimeoutMs * 2);
68941
69082
  if (staleIds.length > 0) {
68942
- log29.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
69083
+ log28.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
68943
69084
  }
68944
69085
  const removedCount = this.sessionStore.cleanHistory();
68945
69086
  if (removedCount > 0) {
68946
- log29.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
69087
+ log28.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
68947
69088
  }
68948
69089
  const persisted = this.sessionStore.load();
68949
- log29.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
69090
+ log28.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
68950
69091
  const excludePostIdsByPlatform = new Map;
68951
69092
  for (const session of persisted.values()) {
68952
69093
  const platformId = session.platformId;
@@ -68966,10 +69107,10 @@ class SessionManager extends EventEmitter4 {
68966
69107
  const excludePostIds = excludePostIdsByPlatform.get(platform.platformId);
68967
69108
  platform.getBotUser().then((botUser) => {
68968
69109
  cleanupOldStickyMessages(platform, botUser.id, true, excludePostIds).catch((err) => {
68969
- log29.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
69110
+ log28.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
68970
69111
  });
68971
69112
  }).catch((err) => {
68972
- log29.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
69113
+ log28.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
68973
69114
  });
68974
69115
  }
68975
69116
  if (persisted.size > 0) {
@@ -68983,10 +69124,10 @@ class SessionManager extends EventEmitter4 {
68983
69124
  }
68984
69125
  }
68985
69126
  if (pausedToSkip.length > 0) {
68986
- log29.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
69127
+ log28.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
68987
69128
  }
68988
69129
  if (activeToResume.length > 0) {
68989
- log29.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
69130
+ log28.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
68990
69131
  for (const state of activeToResume) {
68991
69132
  await resumeSession(state, this.getContext());
68992
69133
  }
@@ -69081,11 +69222,14 @@ class SessionManager extends EventEmitter4 {
69081
69222
  return;
69082
69223
  await kickUser(session, kickedUser, kickedBy, this.getContext());
69083
69224
  }
69084
- async enableInteractivePermissions(threadId, username) {
69225
+ async setSessionPermissionMode(threadId, username, mode) {
69085
69226
  const session = this.findSessionByThreadId(threadId);
69086
69227
  if (!session)
69087
69228
  return;
69088
- await enableInteractivePermissions(session, username, this.getContext());
69229
+ await setSessionPermissionMode(session, username, mode, this.getContext());
69230
+ }
69231
+ async enableInteractivePermissions(threadId, username) {
69232
+ await this.setSessionPermissionMode(threadId, username, "default");
69089
69233
  }
69090
69234
  async reportBug(threadId, description, username, files) {
69091
69235
  const session = this.findSessionByThreadId(threadId);
@@ -69168,10 +69312,13 @@ class SessionManager extends EventEmitter4 {
69168
69312
  isSessionInteractive(threadId) {
69169
69313
  const session = this.findSessionByThreadId(threadId);
69170
69314
  if (!session)
69171
- return !this.skipPermissions;
69172
- if (!this.skipPermissions)
69173
- return true;
69174
- return session.forceInteractivePermissions;
69315
+ return this.permissionMode !== "bypass";
69316
+ const effective = effectivePermissionMode({
69317
+ override: session.permissionModeOverride,
69318
+ sessionHasInteractiveOverride: session.forceInteractivePermissions,
69319
+ botWideMode: this.permissionMode
69320
+ });
69321
+ return effective !== "bypass";
69175
69322
  }
69176
69323
  async requestMessageApproval(threadId, username, message) {
69177
69324
  const session = this.findSessionByThreadId(threadId);
@@ -69196,7 +69343,7 @@ class SessionManager extends EventEmitter4 {
69196
69343
  if (!session)
69197
69344
  return;
69198
69345
  await createAndSwitchToWorktree(session, branch, username, {
69199
- skipPermissions: this.skipPermissions,
69346
+ permissionMode: this.permissionMode,
69200
69347
  chromeEnabled: this.chromeEnabled,
69201
69348
  worktreeMode: this.worktreeMode,
69202
69349
  permissionTimeoutMs: this.limits.permissionTimeoutSeconds * 1000,
@@ -69405,7 +69552,7 @@ Mention me to start a session in this worktree.`, threadId);
69405
69552
  const message = messageBuilder(formatter);
69406
69553
  await post(session, "info", message);
69407
69554
  } catch (err) {
69408
- log29.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
69555
+ log28.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
69409
69556
  }
69410
69557
  }
69411
69558
  }
@@ -69424,7 +69571,7 @@ Mention me to start a session in this worktree.`, threadId);
69424
69571
  session.messageManager?.setPendingUpdatePrompt({ postId: post2.id });
69425
69572
  this.registerPost(post2.id, session.threadId);
69426
69573
  } catch (err) {
69427
- log29.warn(`Failed to post ask message to ${threadId}: ${err}`);
69574
+ log28.warn(`Failed to post ask message to ${threadId}: ${err}`);
69428
69575
  }
69429
69576
  }
69430
69577
  }
@@ -77020,29 +77167,29 @@ function SessionLog({ logs, maxLines = 20 }) {
77020
77167
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
77021
77168
  flexDirection: "column",
77022
77169
  flexShrink: 0,
77023
- children: displayLogs.map((log30) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
77170
+ children: displayLogs.map((log29) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
77024
77171
  flexShrink: 0,
77025
77172
  children: [
77026
77173
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
77027
- color: getColorForLevel(log30.level),
77174
+ color: getColorForLevel(log29.level),
77028
77175
  dimColor: true,
77029
77176
  wrap: "truncate",
77030
77177
  children: [
77031
77178
  "[",
77032
- padComponent(log30.component),
77179
+ padComponent(log29.component),
77033
77180
  "]"
77034
77181
  ]
77035
77182
  }, undefined, true, undefined, this),
77036
77183
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
77037
- color: getColorForLevel(log30.level),
77184
+ color: getColorForLevel(log29.level),
77038
77185
  wrap: "truncate",
77039
77186
  children: [
77040
77187
  " ",
77041
- log30.message
77188
+ log29.message
77042
77189
  ]
77043
77190
  }, undefined, true, undefined, this)
77044
77191
  ]
77045
- }, log30.id, true, undefined, this))
77192
+ }, log29.id, true, undefined, this))
77046
77193
  }, undefined, false, undefined, this);
77047
77194
  }
77048
77195
  // src/ui/components/Footer.tsx
@@ -77072,6 +77219,34 @@ function ToggleKey({ keyChar, label, enabled, color }) {
77072
77219
  ]
77073
77220
  }, undefined, true, undefined, this);
77074
77221
  }
77222
+ function PermissionModeKey({ mode }) {
77223
+ const color = mode === "default" ? "green" : mode === "auto" ? "yellow" : "red";
77224
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
77225
+ gap: 0,
77226
+ children: [
77227
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
77228
+ dimColor: true,
77229
+ children: "["
77230
+ }, undefined, false, undefined, this),
77231
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
77232
+ color,
77233
+ bold: true,
77234
+ children: "p"
77235
+ }, undefined, false, undefined, this),
77236
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
77237
+ dimColor: true,
77238
+ children: "]"
77239
+ }, undefined, false, undefined, this),
77240
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
77241
+ color,
77242
+ children: [
77243
+ "erms:",
77244
+ permissionModeDisplay(mode).label.toLowerCase()
77245
+ ]
77246
+ }, undefined, true, undefined, this)
77247
+ ]
77248
+ }, undefined, true, undefined, this);
77249
+ }
77075
77250
  function PlatformToggle({ index, platform: platform2 }) {
77076
77251
  let color;
77077
77252
  if (!platform2.enabled) {
@@ -77172,10 +77347,8 @@ function Footer({
77172
77347
  label: "ebug",
77173
77348
  enabled: toggles.debugMode
77174
77349
  }, undefined, false, undefined, this),
77175
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(ToggleKey, {
77176
- keyChar: "p",
77177
- label: "erms",
77178
- enabled: !toggles.skipPermissions
77350
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PermissionModeKey, {
77351
+ mode: toggles.permissionMode
77179
77352
  }, undefined, false, undefined, this),
77180
77353
  /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(ToggleKey, {
77181
77354
  keyChar: "c",
@@ -77540,7 +77713,7 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
77540
77713
  const scrollRef = import_react59.default.useRef(null);
77541
77714
  const { stdout } = use_stdout_default();
77542
77715
  const isDebug = process.env.DEBUG === "1";
77543
- const displayLogs = logs.filter((log30) => isDebug || log30.level !== "debug");
77716
+ const displayLogs = logs.filter((log29) => isDebug || log29.level !== "debug");
77544
77717
  const visibleLogs = displayLogs.slice(-Math.max(maxLines * 3, 100));
77545
77718
  import_react59.default.useEffect(() => {
77546
77719
  const handleResize = () => scrollRef.current?.remeasure();
@@ -77580,25 +77753,25 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
77580
77753
  overflow: "hidden",
77581
77754
  children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ScrollView, {
77582
77755
  ref: scrollRef,
77583
- children: visibleLogs.map((log30) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
77756
+ children: visibleLogs.map((log29) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
77584
77757
  children: [
77585
77758
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
77586
77759
  dimColor: true,
77587
77760
  children: [
77588
77761
  "[",
77589
- padComponent2(log30.component),
77762
+ padComponent2(log29.component),
77590
77763
  "]"
77591
77764
  ]
77592
77765
  }, undefined, true, undefined, this),
77593
77766
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
77594
- color: getLevelColor(log30.level),
77767
+ color: getLevelColor(log29.level),
77595
77768
  children: [
77596
77769
  " ",
77597
- log30.message
77770
+ log29.message
77598
77771
  ]
77599
77772
  }, undefined, true, undefined, this)
77600
77773
  ]
77601
- }, log30.id, true, undefined, this))
77774
+ }, log29.id, true, undefined, this))
77602
77775
  }, undefined, false, undefined, this)
77603
77776
  }, undefined, false, undefined, this);
77604
77777
  }
@@ -78115,10 +78288,10 @@ function useAppState(initialConfig) {
78115
78288
  });
78116
78289
  }, []);
78117
78290
  const getLogsForSession = import_react60.useCallback((sessionId) => {
78118
- return state.logs.filter((log30) => log30.sessionId === sessionId);
78291
+ return state.logs.filter((log29) => log29.sessionId === sessionId);
78119
78292
  }, [state.logs]);
78120
78293
  const getGlobalLogs = import_react60.useCallback(() => {
78121
- return state.logs.filter((log30) => !log30.sessionId);
78294
+ return state.logs.filter((log29) => !log29.sessionId);
78122
78295
  }, [state.logs]);
78123
78296
  const togglePlatformEnabled = import_react60.useCallback((platformId) => {
78124
78297
  let newEnabled = false;
@@ -78239,6 +78412,19 @@ function useKeyboard({
78239
78412
  });
78240
78413
  }
78241
78414
 
78415
+ // src/config/permission-mode-cycle.ts
78416
+ var PERMISSION_MODE_CYCLE = [
78417
+ "default",
78418
+ "auto",
78419
+ "bypass"
78420
+ ];
78421
+ function nextPermissionMode(current) {
78422
+ const idx = PERMISSION_MODE_CYCLE.indexOf(current);
78423
+ if (idx === -1)
78424
+ return PERMISSION_MODE_CYCLE[0];
78425
+ return PERMISSION_MODE_CYCLE[(idx + 1) % PERMISSION_MODE_CYCLE.length];
78426
+ }
78427
+
78242
78428
  // src/ui/App.tsx
78243
78429
  var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
78244
78430
  function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks }) {
@@ -78261,7 +78447,7 @@ function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks })
78261
78447
  const [resizeCount, setResizeCount] = import_react61.default.useState(0);
78262
78448
  const [toggles, setToggles] = import_react61.default.useState({
78263
78449
  debugMode: process.env.DEBUG === "1",
78264
- skipPermissions: config.skipPermissions,
78450
+ permissionMode: config.permissionMode,
78265
78451
  chromeEnabled: config.chromeEnabled,
78266
78452
  keepAliveEnabled: config.keepAliveEnabled,
78267
78453
  updateModalVisible: false,
@@ -78282,9 +78468,9 @@ function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks })
78282
78468
  }, [toggleCallbacks]);
78283
78469
  const handlePermissionsToggle = import_react61.default.useCallback(() => {
78284
78470
  setToggles((prev) => {
78285
- const newValue = !prev.skipPermissions;
78286
- toggleCallbacks?.onPermissionsToggle?.(newValue);
78287
- return { ...prev, skipPermissions: newValue };
78471
+ const newMode = nextPermissionMode(prev.permissionMode);
78472
+ toggleCallbacks?.onPermissionsToggle?.(newMode);
78473
+ return { ...prev, permissionMode: newMode };
78288
78474
  });
78289
78475
  }, [toggleCallbacks]);
78290
78476
  const handleChromeToggle = import_react61.default.useCallback(() => {
@@ -78749,7 +78935,7 @@ class HeadlessProvider {
78749
78935
  this.options = options2;
78750
78936
  this.toggles = {
78751
78937
  debugMode: process.env.DEBUG === "1",
78752
- skipPermissions: options2.config.skipPermissions,
78938
+ permissionMode: options2.config.permissionMode,
78753
78939
  chromeEnabled: options2.config.chromeEnabled,
78754
78940
  keepAliveEnabled: options2.config.keepAliveEnabled,
78755
78941
  updateModalVisible: false,
@@ -79112,7 +79298,7 @@ import { EventEmitter as EventEmitter9 } from "events";
79112
79298
  // src/auto-update/checker.ts
79113
79299
  init_logger();
79114
79300
  import { EventEmitter as EventEmitter7 } from "events";
79115
- var log30 = createLogger("checker");
79301
+ var log29 = createLogger("checker");
79116
79302
  var PACKAGE_NAME = "claude-threads";
79117
79303
  function compareVersions(a, b) {
79118
79304
  const partsA = a.replace(/^v/, "").split(".").map(Number);
@@ -79135,13 +79321,13 @@ async function fetchLatestVersion() {
79135
79321
  }
79136
79322
  });
79137
79323
  if (!response.ok) {
79138
- log30.warn(`Failed to fetch latest version: HTTP ${response.status}`);
79324
+ log29.warn(`Failed to fetch latest version: HTTP ${response.status}`);
79139
79325
  return null;
79140
79326
  }
79141
79327
  const data = await response.json();
79142
79328
  return data.version ?? null;
79143
79329
  } catch (err) {
79144
- log30.warn(`Failed to fetch latest version: ${err}`);
79330
+ log29.warn(`Failed to fetch latest version: ${err}`);
79145
79331
  return null;
79146
79332
  }
79147
79333
  }
@@ -79158,38 +79344,38 @@ class UpdateChecker extends EventEmitter7 {
79158
79344
  }
79159
79345
  start() {
79160
79346
  if (!this.config.enabled) {
79161
- log30.debug("Auto-update disabled, not starting checker");
79347
+ log29.debug("Auto-update disabled, not starting checker");
79162
79348
  return;
79163
79349
  }
79164
79350
  setTimeout(() => {
79165
79351
  this.check().catch((err) => {
79166
- log30.warn(`Initial update check failed: ${err}`);
79352
+ log29.warn(`Initial update check failed: ${err}`);
79167
79353
  });
79168
79354
  }, 5000);
79169
79355
  const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
79170
79356
  this.checkInterval = setInterval(() => {
79171
79357
  this.check().catch((err) => {
79172
- log30.warn(`Periodic update check failed: ${err}`);
79358
+ log29.warn(`Periodic update check failed: ${err}`);
79173
79359
  });
79174
79360
  }, intervalMs);
79175
- log30.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
79361
+ log29.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
79176
79362
  }
79177
79363
  stop() {
79178
79364
  if (this.checkInterval) {
79179
79365
  clearInterval(this.checkInterval);
79180
79366
  this.checkInterval = null;
79181
79367
  }
79182
- log30.debug("Update checker stopped");
79368
+ log29.debug("Update checker stopped");
79183
79369
  }
79184
79370
  async check() {
79185
79371
  if (this.isChecking) {
79186
- log30.debug("Check already in progress, skipping");
79372
+ log29.debug("Check already in progress, skipping");
79187
79373
  return this.lastUpdateInfo;
79188
79374
  }
79189
79375
  this.isChecking = true;
79190
79376
  this.emit("check:start");
79191
79377
  try {
79192
- log30.debug("Checking for updates...");
79378
+ log29.debug("Checking for updates...");
79193
79379
  const latestVersion2 = await fetchLatestVersion();
79194
79380
  if (!latestVersion2) {
79195
79381
  this.emit("check:complete", false);
@@ -79206,18 +79392,18 @@ class UpdateChecker extends EventEmitter7 {
79206
79392
  detectedAt: new Date
79207
79393
  };
79208
79394
  if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
79209
- log30.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
79395
+ log29.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
79210
79396
  this.lastUpdateInfo = updateInfo;
79211
79397
  this.emit("update", updateInfo);
79212
79398
  }
79213
79399
  this.emit("check:complete", true);
79214
79400
  return updateInfo;
79215
79401
  }
79216
- log30.debug(`Up to date (v${currentVersion})`);
79402
+ log29.debug(`Up to date (v${currentVersion})`);
79217
79403
  this.emit("check:complete", false);
79218
79404
  return null;
79219
79405
  } catch (err) {
79220
- log30.warn(`Update check failed: ${err}`);
79406
+ log29.warn(`Update check failed: ${err}`);
79221
79407
  this.emit("check:error", err);
79222
79408
  return null;
79223
79409
  } finally {
@@ -79288,7 +79474,7 @@ function isInScheduledWindow(window2) {
79288
79474
  }
79289
79475
 
79290
79476
  // src/auto-update/scheduler.ts
79291
- var log31 = createLogger("scheduler");
79477
+ var log30 = createLogger("scheduler");
79292
79478
 
79293
79479
  class UpdateScheduler extends EventEmitter8 {
79294
79480
  config;
@@ -79312,7 +79498,7 @@ class UpdateScheduler extends EventEmitter8 {
79312
79498
  scheduleUpdate(updateInfo) {
79313
79499
  this.pendingUpdate = updateInfo;
79314
79500
  if (this.config.autoRestartMode === "immediate") {
79315
- log31.info("Immediate mode: triggering update now");
79501
+ log30.info("Immediate mode: triggering update now");
79316
79502
  this.emit("ready", updateInfo);
79317
79503
  return;
79318
79504
  }
@@ -79325,19 +79511,19 @@ class UpdateScheduler extends EventEmitter8 {
79325
79511
  this.scheduledRestartAt = null;
79326
79512
  this.askApprovals.clear();
79327
79513
  this.askStartTime = null;
79328
- log31.debug("Update schedule cancelled");
79514
+ log30.debug("Update schedule cancelled");
79329
79515
  }
79330
79516
  deferUpdate(minutes) {
79331
79517
  const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
79332
79518
  this.scheduledRestartAt = null;
79333
79519
  this.idleStartTime = null;
79334
79520
  this.emit("deferred", deferUntil);
79335
- log31.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
79521
+ log30.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
79336
79522
  return deferUntil;
79337
79523
  }
79338
79524
  recordAskResponse(threadId, approved) {
79339
79525
  this.askApprovals.set(threadId, approved);
79340
- log31.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
79526
+ log30.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
79341
79527
  this.checkAskCondition();
79342
79528
  }
79343
79529
  getScheduledRestartAt() {
@@ -79358,7 +79544,7 @@ class UpdateScheduler extends EventEmitter8 {
79358
79544
  return;
79359
79545
  this.checkCondition();
79360
79546
  this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
79361
- log31.debug(`Started checking for ${this.config.autoRestartMode} condition`);
79547
+ log30.debug(`Started checking for ${this.config.autoRestartMode} condition`);
79362
79548
  }
79363
79549
  stopChecking() {
79364
79550
  if (this.checkTimer) {
@@ -79389,17 +79575,17 @@ class UpdateScheduler extends EventEmitter8 {
79389
79575
  if (activity.activeSessionCount === 0) {
79390
79576
  if (!this.idleStartTime) {
79391
79577
  this.idleStartTime = new Date;
79392
- log31.debug("No active sessions, starting idle timer");
79578
+ log30.debug("No active sessions, starting idle timer");
79393
79579
  }
79394
79580
  const idleMs = Date.now() - this.idleStartTime.getTime();
79395
79581
  const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
79396
79582
  if (idleMs >= requiredMs) {
79397
- log31.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
79583
+ log30.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
79398
79584
  this.triggerCountdown();
79399
79585
  }
79400
79586
  } else {
79401
79587
  if (this.idleStartTime) {
79402
- log31.debug("Sessions became active, resetting idle timer");
79588
+ log30.debug("Sessions became active, resetting idle timer");
79403
79589
  this.idleStartTime = null;
79404
79590
  }
79405
79591
  }
@@ -79410,7 +79596,7 @@ class UpdateScheduler extends EventEmitter8 {
79410
79596
  const quietMs = Date.now() - activity.lastActivityAt.getTime();
79411
79597
  const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
79412
79598
  if (quietMs >= requiredMs && !activity.anySessionBusy) {
79413
- log31.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
79599
+ log30.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
79414
79600
  this.triggerCountdown();
79415
79601
  }
79416
79602
  } else if (activity.activeSessionCount === 0) {
@@ -79420,7 +79606,7 @@ class UpdateScheduler extends EventEmitter8 {
79420
79606
  const idleMs = Date.now() - this.idleStartTime.getTime();
79421
79607
  const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
79422
79608
  if (idleMs >= requiredMs) {
79423
- log31.info("No sessions and quiet timeout reached, triggering update");
79609
+ log30.info("No sessions and quiet timeout reached, triggering update");
79424
79610
  this.triggerCountdown();
79425
79611
  }
79426
79612
  }
@@ -79431,13 +79617,13 @@ class UpdateScheduler extends EventEmitter8 {
79431
79617
  }
79432
79618
  const activity = this.getSessionActivity();
79433
79619
  if (activity.activeSessionCount === 0) {
79434
- log31.info("Within scheduled window and no active sessions, triggering update");
79620
+ log30.info("Within scheduled window and no active sessions, triggering update");
79435
79621
  this.triggerCountdown();
79436
79622
  } else if (activity.lastActivityAt) {
79437
79623
  const quietMs = Date.now() - activity.lastActivityAt.getTime();
79438
79624
  const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
79439
79625
  if (quietMs >= requiredMs && !activity.anySessionBusy) {
79440
- log31.info("Within scheduled window and sessions quiet, triggering update");
79626
+ log30.info("Within scheduled window and sessions quiet, triggering update");
79441
79627
  this.triggerCountdown();
79442
79628
  }
79443
79629
  }
@@ -79445,14 +79631,14 @@ class UpdateScheduler extends EventEmitter8 {
79445
79631
  checkAskCondition() {
79446
79632
  const threadIds = this.getActiveThreadIds();
79447
79633
  if (threadIds.length === 0) {
79448
- log31.info("No active threads, proceeding with update");
79634
+ log30.info("No active threads, proceeding with update");
79449
79635
  this.triggerCountdown();
79450
79636
  return;
79451
79637
  }
79452
79638
  if (!this.askStartTime && this.pendingUpdate) {
79453
79639
  this.askStartTime = new Date;
79454
79640
  this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
79455
- log31.warn(`Failed to post ask message: ${err}`);
79641
+ log30.warn(`Failed to post ask message: ${err}`);
79456
79642
  });
79457
79643
  return;
79458
79644
  }
@@ -79465,12 +79651,12 @@ class UpdateScheduler extends EventEmitter8 {
79465
79651
  denials++;
79466
79652
  }
79467
79653
  if (approvals > threadIds.length / 2) {
79468
- log31.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
79654
+ log30.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
79469
79655
  this.triggerCountdown();
79470
79656
  return;
79471
79657
  }
79472
79658
  if (denials > threadIds.length / 2) {
79473
- log31.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
79659
+ log30.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
79474
79660
  this.deferUpdate(60);
79475
79661
  return;
79476
79662
  }
@@ -79478,7 +79664,7 @@ class UpdateScheduler extends EventEmitter8 {
79478
79664
  const elapsedMs = Date.now() - this.askStartTime.getTime();
79479
79665
  const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
79480
79666
  if (elapsedMs >= timeoutMs) {
79481
- log31.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
79667
+ log30.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
79482
79668
  this.triggerCountdown();
79483
79669
  }
79484
79670
  }
@@ -79498,7 +79684,7 @@ class UpdateScheduler extends EventEmitter8 {
79498
79684
  this.emit("ready", this.pendingUpdate);
79499
79685
  }
79500
79686
  }, 1000);
79501
- log31.info("Update countdown started (60 seconds)");
79687
+ log30.info("Update countdown started (60 seconds)");
79502
79688
  }
79503
79689
  stopCountdown() {
79504
79690
  if (this.countdownTimer) {
@@ -79511,27 +79697,27 @@ class UpdateScheduler extends EventEmitter8 {
79511
79697
  // src/auto-update/installer.ts
79512
79698
  init_logger();
79513
79699
  import { spawn as spawn4, spawnSync } from "child_process";
79514
- import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "fs";
79700
+ import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "fs";
79515
79701
  import { dirname as dirname8, resolve as resolve6 } from "path";
79516
79702
  import { homedir as homedir5 } from "os";
79517
- var log32 = createLogger("installer");
79703
+ var log31 = createLogger("installer");
79518
79704
  function detectPackageManager() {
79519
79705
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
79520
79706
  const originalInstaller = detectOriginalInstaller();
79521
79707
  if (originalInstaller) {
79522
- log32.debug(`Detected original installer: ${originalInstaller}`);
79708
+ log31.debug(`Detected original installer: ${originalInstaller}`);
79523
79709
  if (originalInstaller === "bun") {
79524
79710
  const bunCheck2 = spawnSync("bun", ["--version"], { stdio: "ignore" });
79525
79711
  if (bunCheck2.status === 0) {
79526
79712
  return { cmd: "bun", isBun: true };
79527
79713
  }
79528
- log32.warn("Originally installed with bun, but bun not found. Falling back to npm.");
79714
+ log31.warn("Originally installed with bun, but bun not found. Falling back to npm.");
79529
79715
  } else {
79530
79716
  const npmCheck2 = spawnSync(npmCmd, ["--version"], { stdio: "ignore" });
79531
79717
  if (npmCheck2.status === 0) {
79532
79718
  return { cmd: npmCmd, isBun: false };
79533
79719
  }
79534
- log32.warn("Originally installed with npm, but npm not found. Falling back to bun.");
79720
+ log31.warn("Originally installed with npm, but npm not found. Falling back to bun.");
79535
79721
  }
79536
79722
  }
79537
79723
  const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore" });
@@ -79582,7 +79768,7 @@ function loadUpdateState() {
79582
79768
  return JSON.parse(content);
79583
79769
  }
79584
79770
  } catch (err) {
79585
- log32.warn(`Failed to load update state: ${err}`);
79771
+ log31.warn(`Failed to load update state: ${err}`);
79586
79772
  }
79587
79773
  return {};
79588
79774
  }
@@ -79592,19 +79778,19 @@ function saveUpdateState(state) {
79592
79778
  if (!existsSync13(dir)) {
79593
79779
  mkdirSync4(dir, { recursive: true });
79594
79780
  }
79595
- writeFileSync5(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
79596
- log32.debug("Update state saved");
79781
+ writeFileSync6(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
79782
+ log31.debug("Update state saved");
79597
79783
  } catch (err) {
79598
- log32.warn(`Failed to save update state: ${err}`);
79784
+ log31.warn(`Failed to save update state: ${err}`);
79599
79785
  }
79600
79786
  }
79601
79787
  function clearUpdateState() {
79602
79788
  try {
79603
79789
  if (existsSync13(STATE_PATH)) {
79604
- writeFileSync5(STATE_PATH, "{}", "utf-8");
79790
+ writeFileSync6(STATE_PATH, "{}", "utf-8");
79605
79791
  }
79606
79792
  } catch (err) {
79607
- log32.warn(`Failed to clear update state: ${err}`);
79793
+ log31.warn(`Failed to clear update state: ${err}`);
79608
79794
  }
79609
79795
  }
79610
79796
  function checkJustUpdated() {
@@ -79636,11 +79822,11 @@ function clearRuntimeSettings() {
79636
79822
  }
79637
79823
  }
79638
79824
  async function installVersion(version) {
79639
- log32.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
79825
+ log31.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
79640
79826
  const pm = detectPackageManager();
79641
79827
  if (!pm) {
79642
79828
  const error = "Neither bun nor npm found in PATH. Cannot install update.";
79643
- log32.error(`❌ ${error}`);
79829
+ log31.error(`❌ ${error}`);
79644
79830
  return { success: false, error };
79645
79831
  }
79646
79832
  saveUpdateState({
@@ -79652,7 +79838,7 @@ async function installVersion(version) {
79652
79838
  return new Promise((resolve7) => {
79653
79839
  const { cmd, isBun: isBun3 } = pm;
79654
79840
  const args = ["install", "-g", `${PACKAGE_NAME2}@${version}`];
79655
- log32.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
79841
+ log31.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
79656
79842
  const child = spawn4(cmd, args, {
79657
79843
  stdio: ["ignore", "pipe", "pipe"],
79658
79844
  env: {
@@ -79670,7 +79856,7 @@ async function installVersion(version) {
79670
79856
  });
79671
79857
  child.on("close", (code) => {
79672
79858
  if (code === 0) {
79673
- log32.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
79859
+ log31.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
79674
79860
  saveUpdateState({
79675
79861
  previousVersion: VERSION,
79676
79862
  targetVersion: version,
@@ -79680,20 +79866,20 @@ async function installVersion(version) {
79680
79866
  resolve7({ success: true });
79681
79867
  } else {
79682
79868
  const errorMsg = stderr || stdout || `Exit code: ${code}`;
79683
- log32.error(`❌ Installation failed: ${errorMsg}`);
79869
+ log31.error(`❌ Installation failed: ${errorMsg}`);
79684
79870
  clearUpdateState();
79685
79871
  resolve7({ success: false, error: errorMsg });
79686
79872
  }
79687
79873
  });
79688
79874
  child.on("error", (err) => {
79689
- log32.error(`❌ Failed to spawn npm: ${err}`);
79875
+ log31.error(`❌ Failed to spawn npm: ${err}`);
79690
79876
  clearUpdateState();
79691
79877
  resolve7({ success: false, error: err.message });
79692
79878
  });
79693
79879
  setTimeout(() => {
79694
79880
  if (child.exitCode === null) {
79695
79881
  child.kill();
79696
- log32.error("❌ Installation timed out");
79882
+ log31.error("❌ Installation timed out");
79697
79883
  clearUpdateState();
79698
79884
  resolve7({ success: false, error: "Installation timed out" });
79699
79885
  }
@@ -79735,7 +79921,7 @@ class UpdateInstaller {
79735
79921
  }
79736
79922
 
79737
79923
  // src/auto-update/manager.ts
79738
- var log33 = createLogger("updater");
79924
+ var log32 = createLogger("updater");
79739
79925
 
79740
79926
  class AutoUpdateManager extends EventEmitter9 {
79741
79927
  config;
@@ -79758,23 +79944,23 @@ class AutoUpdateManager extends EventEmitter9 {
79758
79944
  }
79759
79945
  start() {
79760
79946
  if (!this.config.enabled) {
79761
- log33.info("Auto-update is disabled");
79947
+ log32.info("Auto-update is disabled");
79762
79948
  return;
79763
79949
  }
79764
79950
  const updateResult = this.installer.checkJustUpdated();
79765
79951
  if (updateResult) {
79766
- log33.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
79952
+ log32.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
79767
79953
  this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
79768
- log33.warn(`Failed to broadcast update notification: ${err}`);
79954
+ log32.warn(`Failed to broadcast update notification: ${err}`);
79769
79955
  });
79770
79956
  }
79771
79957
  this.checker.start();
79772
- log33.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
79958
+ log32.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
79773
79959
  }
79774
79960
  stop() {
79775
79961
  this.checker.stop();
79776
79962
  this.scheduler.stop();
79777
- log33.debug("Auto-update manager stopped");
79963
+ log32.debug("Auto-update manager stopped");
79778
79964
  }
79779
79965
  getState() {
79780
79966
  return { ...this.state };
@@ -79788,10 +79974,10 @@ class AutoUpdateManager extends EventEmitter9 {
79788
79974
  async forceUpdate() {
79789
79975
  const updateInfo = this.state.updateInfo || await this.checker.check();
79790
79976
  if (!updateInfo) {
79791
- log33.info("No update available");
79977
+ log32.info("No update available");
79792
79978
  return;
79793
79979
  }
79794
- log33.info("Forcing immediate update");
79980
+ log32.info("Forcing immediate update");
79795
79981
  await this.performUpdate(updateInfo);
79796
79982
  }
79797
79983
  deferUpdate(minutes = 60) {
@@ -79846,7 +80032,7 @@ class AutoUpdateManager extends EventEmitter9 {
79846
80032
  await this.callbacks.broadcastUpdate((fmt) => `✅ ${fmt.formatBold("Update installed")} - restarting now. ${fmt.formatItalic("Sessions will resume automatically.")}`).catch(() => {});
79847
80033
  await new Promise((resolve7) => setTimeout(resolve7, 1000));
79848
80034
  await this.callbacks.prepareForRestart();
79849
- log33.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
80035
+ log32.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
79850
80036
  process.stdout.write("\x1B[2J\x1B[H");
79851
80037
  process.stdout.write("\x1B[?25h");
79852
80038
  process.exit(RESTART_EXIT_CODE);
@@ -79906,7 +80092,7 @@ function wirePlatformEvents(platformId, client, session, ui) {
79906
80092
  ui.addLog({ level: "error", component: platformId, message: String(e) });
79907
80093
  });
79908
80094
  }
79909
- program.name("claude-threads").version(VERSION).description("Share Claude Code sessions in Mattermost").option("--url <url>", "Mattermost server URL").option("--token <token>", "Mattermost bot token").option("--channel <id>", "Mattermost channel ID").option("--bot-name <name>", "Bot mention name (default: claude-code)").option("--allowed-users <users>", "Comma-separated allowed usernames").option("--skip-permissions", "Skip interactive permission prompts").option("--no-skip-permissions", "Enable interactive permission prompts (override env)").option("--chrome", "Enable Claude in Chrome integration").option("--no-chrome", "Disable Claude in Chrome integration").option("--worktree-mode <mode>", "Git worktree mode: off, prompt, require (default: prompt)").option("--keep-alive", "Enable system sleep prevention (default: enabled)").option("--no-keep-alive", "Disable system sleep prevention").option("--setup", "Run interactive setup wizard (reconfigure existing settings)").option("--debug", "Enable debug logging").option("--skip-version-check", "Skip Claude CLI version compatibility check").option("--auto-restart", "Enable auto-restart on updates (default when autoUpdate enabled)").option("--no-auto-restart", "Disable auto-restart on updates").option("--headless", "Run without interactive UI (logs to stdout)").parse();
80095
+ program.name("claude-threads").version(VERSION).description("Share Claude Code sessions in Mattermost").option("--url <url>", "Mattermost server URL").option("--token <token>", "Mattermost bot token").option("--channel <id>", "Mattermost channel ID").option("--bot-name <name>", "Bot mention name (default: claude-code)").option("--allowed-users <users>", "Comma-separated allowed usernames").option("--permission-mode <mode>", "Permission mode: default | auto | bypass (default: from config)").option("--skip-permissions", "[deprecated] Alias for --permission-mode bypass").option("--no-skip-permissions", "[deprecated] Alias for --permission-mode default").option("--chrome", "Enable Claude in Chrome integration").option("--no-chrome", "Disable Claude in Chrome integration").option("--worktree-mode <mode>", "Git worktree mode: off, prompt, require (default: prompt)").option("--keep-alive", "Enable system sleep prevention (default: enabled)").option("--no-keep-alive", "Disable system sleep prevention").option("--setup", "Run interactive setup wizard (reconfigure existing settings)").option("--debug", "Enable debug logging").option("--skip-version-check", "Skip Claude CLI version compatibility check").option("--auto-restart", "Enable auto-restart on updates (default when autoUpdate enabled)").option("--no-auto-restart", "Disable auto-restart on updates").option("--headless", "Run without interactive UI (logs to stdout)").parse();
79910
80096
  var opts = program.opts();
79911
80097
  var forcedInteractive = !!process.env.CLAUDE_THREADS_INTERACTIVE;
79912
80098
  var isHeadless = opts.headless || !forcedInteractive && (!process.stdout.isTTY || !process.stdin.isTTY);
@@ -79990,6 +80176,10 @@ async function startWithoutDaemon() {
79990
80176
  if (opts.debug) {
79991
80177
  process.env.DEBUG = "1";
79992
80178
  }
80179
+ if (opts.permissionMode !== undefined && !["default", "auto", "bypass"].includes(opts.permissionMode)) {
80180
+ console.error(red(` ❌ Invalid --permission-mode: "${opts.permissionMode}". Must be one of: default, auto, bypass.`));
80181
+ process.exit(1);
80182
+ }
79993
80183
  const cliArgs = {
79994
80184
  url: opts.url,
79995
80185
  token: opts.token,
@@ -79997,6 +80187,7 @@ async function startWithoutDaemon() {
79997
80187
  botName: opts.botName,
79998
80188
  allowedUsers: opts.allowedUsers,
79999
80189
  skipPermissions: opts.skipPermissions,
80190
+ permissionMode: opts.permissionMode,
80000
80191
  chrome: opts.chrome,
80001
80192
  worktreeMode: opts.worktreeMode,
80002
80193
  keepAlive: opts.keepAlive
@@ -80026,7 +80217,10 @@ async function startWithoutDaemon() {
80026
80217
  }
80027
80218
  const config = newConfig;
80028
80219
  const firstPlatformConfig = config.platforms[0];
80029
- const initialSkipPermissions = firstPlatformConfig.skipPermissions ?? false;
80220
+ const initialPermissionMode = resolvePermissionMode({
80221
+ permissionMode: cliArgs.permissionMode ?? firstPlatformConfig.permissionMode,
80222
+ skipPermissions: cliArgs.skipPermissions ?? firstPlatformConfig.skipPermissions
80223
+ });
80030
80224
  const claudeValidation = validateClaudeCli();
80031
80225
  if (!claudeValidation.compatible && !opts.skipVersionCheck) {
80032
80226
  console.error(red(` ❌ ${claudeValidation.message}`));
@@ -80053,8 +80247,10 @@ async function startWithoutDaemon() {
80053
80247
  process.env.DEBUG = "1";
80054
80248
  }
80055
80249
  }
80250
+ const restoredPermissionMode = restoredSettings?.permissionMode ?? (restoredSettings?.skipPermissions === true ? "bypass" : restoredSettings?.skipPermissions === false ? "default" : initialPermissionMode);
80056
80251
  const runtimeConfig = {
80057
- skipPermissions: restoredSettings?.skipPermissions ?? initialSkipPermissions,
80252
+ permissionMode: restoredPermissionMode,
80253
+ skipPermissions: restoredPermissionMode === "bypass",
80058
80254
  chromeEnabled: restoredSettings?.chromeEnabled ?? (config.chrome ?? false),
80059
80255
  keepAliveEnabled: restoredSettings?.keepAliveEnabled ?? keepAliveEnabled
80060
80256
  };
@@ -80067,7 +80263,7 @@ async function startWithoutDaemon() {
80067
80263
  workingDir,
80068
80264
  claudeVersion: claudeValidation.version || "unknown",
80069
80265
  claudeCompatible: claudeValidation.compatible,
80070
- skipPermissions: runtimeConfig.skipPermissions,
80266
+ permissionMode: runtimeConfig.permissionMode,
80071
80267
  chromeEnabled: runtimeConfig.chromeEnabled,
80072
80268
  keepAliveEnabled: runtimeConfig.keepAliveEnabled
80073
80269
  },
@@ -80082,14 +80278,20 @@ async function startWithoutDaemon() {
80082
80278
  ui.addLog({ level: "info", component: "toggle", message: `Debug mode ${enabled ? "enabled" : "disabled"}` });
80083
80279
  sessionManager?.updateAllStickyMessages();
80084
80280
  },
80085
- onPermissionsToggle: (skipPermissions) => {
80086
- runtimeConfig.skipPermissions = skipPermissions;
80087
- saveRuntimeSettings({ ...getRuntimeSettings(), skipPermissions });
80281
+ onPermissionsToggle: (mode) => {
80282
+ runtimeConfig.permissionMode = mode;
80283
+ saveRuntimeSettings({
80284
+ ...getRuntimeSettings(),
80285
+ permissionMode: mode,
80286
+ skipPermissions: mode === "bypass"
80287
+ });
80088
80288
  for (const platformConfig of config.platforms) {
80089
- platformConfig.skipPermissions = skipPermissions;
80289
+ const pc = platformConfig;
80290
+ pc.permissionMode = mode;
80291
+ pc.skipPermissions = mode === "bypass";
80090
80292
  }
80091
- sessionManager?.setSkipPermissions(skipPermissions);
80092
- ui.addLog({ level: "info", component: "toggle", message: `Permissions ${skipPermissions ? "auto (skip prompts)" : "interactive"}` });
80293
+ sessionManager?.setPermissionMode(mode);
80294
+ ui.addLog({ level: "info", component: "toggle", message: `Permission mode: ${mode}` });
80093
80295
  sessionManager?.updateAllStickyMessages();
80094
80296
  },
80095
80297
  onChromeToggle: (enabled) => {
@@ -80152,7 +80354,7 @@ async function startWithoutDaemon() {
80152
80354
  keepAlive.setEnabled(keepAliveEnabled);
80153
80355
  const threadLogsEnabled = config.threadLogs?.enabled ?? true;
80154
80356
  const threadLogsRetentionDays = config.threadLogs?.retentionDays ?? 30;
80155
- const session = new SessionManager(workingDir, initialSkipPermissions, config.chrome, config.worktreeMode, undefined, threadLogsEnabled, threadLogsRetentionDays, config.limits, config.claudeAccounts);
80357
+ const session = new SessionManager(workingDir, initialPermissionMode, config.chrome, config.worktreeMode, undefined, threadLogsEnabled, threadLogsRetentionDays, config.limits, config.claudeAccounts);
80156
80358
  if (config.stickyMessage) {
80157
80359
  session.setStickyMessageCustomization(config.stickyMessage.description, config.stickyMessage.footer);
80158
80360
  }