claude-threads 1.27.0 → 1.28.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
@@ -4153,10 +4153,10 @@ __export(exports_worktree, {
4153
4153
  import { randomUUID as randomUUID2 } from "crypto";
4154
4154
  import * as path from "path";
4155
4155
  import * as fs from "fs/promises";
4156
- import { homedir as homedir3 } from "os";
4156
+ import { homedir as homedir4 } from "os";
4157
4157
  async function execGit(args, cwd) {
4158
4158
  const cmd = `git ${args.join(" ")}`;
4159
- log7.debug(`Executing: ${cmd}`);
4159
+ log8.debug(`Executing: ${cmd}`);
4160
4160
  return new Promise((resolve6, reject) => {
4161
4161
  const proc = crossSpawn("git", args, { cwd });
4162
4162
  let stdout = "";
@@ -4169,15 +4169,15 @@ async function execGit(args, cwd) {
4169
4169
  });
4170
4170
  proc.on("close", (code) => {
4171
4171
  if (code === 0) {
4172
- log7.debug(`${cmd} → success`);
4172
+ log8.debug(`${cmd} → success`);
4173
4173
  resolve6(stdout.trim());
4174
4174
  } else {
4175
- log7.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
4175
+ log8.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
4176
4176
  reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
4177
4177
  }
4178
4178
  });
4179
4179
  proc.on("error", (err) => {
4180
- log7.warn(`${cmd} → error: ${err}`);
4180
+ log8.warn(`${cmd} → error: ${err}`);
4181
4181
  reject(err);
4182
4182
  });
4183
4183
  });
@@ -4187,7 +4187,7 @@ async function isGitRepository(dir) {
4187
4187
  await execGit(["rev-parse", "--git-dir"], dir);
4188
4188
  return true;
4189
4189
  } catch (err) {
4190
- log7.debug(`Not a git repository: ${dir} (${err})`);
4190
+ log8.debug(`Not a git repository: ${dir} (${err})`);
4191
4191
  return false;
4192
4192
  }
4193
4193
  }
@@ -4337,49 +4337,49 @@ async function detectWorktreeInfo(workingDir) {
4337
4337
  const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
4338
4338
  const branch = branchOutput?.trim();
4339
4339
  if (!branch) {
4340
- log7.debug(`Could not detect branch for worktree at ${workingDir}`);
4340
+ log8.debug(`Could not detect branch for worktree at ${workingDir}`);
4341
4341
  return null;
4342
4342
  }
4343
4343
  const repoRoot = await getMainRepositoryRoot(workingDir);
4344
- log7.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
4344
+ log8.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
4345
4345
  return {
4346
4346
  worktreePath: workingDir,
4347
4347
  branch,
4348
4348
  repoRoot: repoRoot || workingDir
4349
4349
  };
4350
4350
  } catch (err) {
4351
- log7.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
4351
+ log8.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
4352
4352
  return null;
4353
4353
  }
4354
4354
  }
4355
4355
  async function createWorktree(repoRoot, branch, targetDir) {
4356
- log7.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
4356
+ log8.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
4357
4357
  const parentDir = path.dirname(targetDir);
4358
- log7.debug(`Creating parent directory: ${parentDir}`);
4358
+ log8.debug(`Creating parent directory: ${parentDir}`);
4359
4359
  await fs.mkdir(parentDir, { recursive: true });
4360
4360
  const exists = await branchExists(repoRoot, branch);
4361
4361
  if (exists) {
4362
- log7.debug(`Branch '${branch}' exists, adding worktree`);
4362
+ log8.debug(`Branch '${branch}' exists, adding worktree`);
4363
4363
  await execGit(["worktree", "add", targetDir, branch], repoRoot);
4364
4364
  } else {
4365
- log7.debug(`Branch '${branch}' does not exist, creating with worktree`);
4365
+ log8.debug(`Branch '${branch}' does not exist, creating with worktree`);
4366
4366
  await execGit(["worktree", "add", "-b", branch, targetDir], repoRoot);
4367
4367
  }
4368
- log7.info(`Worktree created successfully: ${targetDir}`);
4368
+ log8.info(`Worktree created successfully: ${targetDir}`);
4369
4369
  return targetDir;
4370
4370
  }
4371
4371
  async function removeWorktree(repoRoot, worktreePath) {
4372
- log7.info(`Removing worktree: ${worktreePath}`);
4372
+ log8.info(`Removing worktree: ${worktreePath}`);
4373
4373
  try {
4374
4374
  await execGit(["worktree", "remove", worktreePath], repoRoot);
4375
- log7.debug("Worktree removed cleanly");
4375
+ log8.debug("Worktree removed cleanly");
4376
4376
  } catch (err) {
4377
- log7.debug(`Clean remove failed (${err}), trying force remove`);
4377
+ log8.debug(`Clean remove failed (${err}), trying force remove`);
4378
4378
  await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
4379
4379
  }
4380
- log7.debug("Pruning stale worktree references");
4380
+ log8.debug("Pruning stale worktree references");
4381
4381
  await execGit(["worktree", "prune"], repoRoot);
4382
- log7.info("Worktree removed and pruned successfully");
4382
+ log8.info("Worktree removed and pruned successfully");
4383
4383
  }
4384
4384
  async function findWorktreeByBranch(repoRoot, branch) {
4385
4385
  const worktrees = await listWorktrees(repoRoot);
@@ -4420,14 +4420,14 @@ async function writeMetadataStore(store) {
4420
4420
  await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
4421
4421
  await fs.chmod(METADATA_STORE_PATH, 384);
4422
4422
  } catch (err) {
4423
- log7.warn(`Failed to write worktree metadata store: ${err}`);
4423
+ log8.warn(`Failed to write worktree metadata store: ${err}`);
4424
4424
  }
4425
4425
  }
4426
4426
  async function writeWorktreeMetadata(worktreePath, metadata) {
4427
4427
  const store = await readMetadataStore();
4428
4428
  store[worktreePath] = metadata;
4429
4429
  await writeMetadataStore(store);
4430
- log7.debug(`Wrote worktree metadata for: ${worktreePath}`);
4430
+ log8.debug(`Wrote worktree metadata for: ${worktreePath}`);
4431
4431
  }
4432
4432
  async function readWorktreeMetadata(worktreePath) {
4433
4433
  const store = await readMetadataStore();
@@ -4450,16 +4450,16 @@ async function removeWorktreeMetadata(worktreePath) {
4450
4450
  if (store[worktreePath]) {
4451
4451
  delete store[worktreePath];
4452
4452
  await writeMetadataStore(store);
4453
- log7.debug(`Removed worktree metadata for: ${worktreePath}`);
4453
+ log8.debug(`Removed worktree metadata for: ${worktreePath}`);
4454
4454
  }
4455
4455
  }
4456
- var log7, WORKTREES_DIR, METADATA_STORE_PATH;
4456
+ var log8, WORKTREES_DIR, METADATA_STORE_PATH;
4457
4457
  var init_worktree = __esm(() => {
4458
4458
  init_spawn();
4459
4459
  init_logger();
4460
- log7 = createLogger("git-wt");
4461
- WORKTREES_DIR = path.join(homedir3(), ".claude-threads", "worktrees");
4462
- METADATA_STORE_PATH = path.join(homedir3(), ".claude-threads", "worktree-metadata.json");
4460
+ log8 = createLogger("git-wt");
4461
+ WORKTREES_DIR = path.join(homedir4(), ".claude-threads", "worktrees");
4462
+ METADATA_STORE_PATH = path.join(homedir4(), ".claude-threads", "worktree-metadata.json");
4463
4463
  });
4464
4464
 
4465
4465
  // node_modules/graceful-fs/polyfills.js
@@ -4935,14 +4935,14 @@ GFS4: `);
4935
4935
  return close;
4936
4936
  }(fs2.close);
4937
4937
  fs2.closeSync = function(fs$closeSync) {
4938
- function closeSync(fd) {
4938
+ function closeSync2(fd) {
4939
4939
  fs$closeSync.apply(fs2, arguments);
4940
4940
  resetQueue();
4941
4941
  }
4942
- Object.defineProperty(closeSync, previousSymbol, {
4942
+ Object.defineProperty(closeSync2, previousSymbol, {
4943
4943
  value: fs$closeSync
4944
4944
  });
4945
- return closeSync;
4945
+ return closeSync2;
4946
4946
  }(fs2.closeSync);
4947
4947
  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
4948
4948
  process.on("exit", function() {
@@ -6366,7 +6366,7 @@ var require_minimist = __commonJS((exports, module) => {
6366
6366
  // node_modules/rc/index.js
6367
6367
  var require_rc = __commonJS((exports, module) => {
6368
6368
  var cc = require_utils();
6369
- var join7 = __require("path").join;
6369
+ var join8 = __require("path").join;
6370
6370
  var deepExtend = require_deep_extend();
6371
6371
  var etc = "/etc";
6372
6372
  var win = process.platform === "win32";
@@ -6392,15 +6392,15 @@ var require_rc = __commonJS((exports, module) => {
6392
6392
  }
6393
6393
  if (!win)
6394
6394
  [
6395
- join7(etc, name, "config"),
6396
- join7(etc, name + "rc")
6395
+ join8(etc, name, "config"),
6396
+ join8(etc, name + "rc")
6397
6397
  ].forEach(addConfigFile);
6398
6398
  if (home)
6399
6399
  [
6400
- join7(home, ".config", name, "config"),
6401
- join7(home, ".config", name),
6402
- join7(home, "." + name, "config"),
6403
- join7(home, "." + name + "rc")
6400
+ join8(home, ".config", name, "config"),
6401
+ join8(home, ".config", name),
6402
+ join8(home, "." + name, "config"),
6403
+ join8(home, "." + name + "rc")
6404
6404
  ].forEach(addConfigFile);
6405
6405
  addConfigFile(cc.find("." + name + "rc"));
6406
6406
  if (env3.config)
@@ -6888,14 +6888,14 @@ GFS4: `);
6888
6888
  return close;
6889
6889
  }(fs4.close);
6890
6890
  fs4.closeSync = function(fs$closeSync) {
6891
- function closeSync(fd) {
6891
+ function closeSync2(fd) {
6892
6892
  fs$closeSync.apply(fs4, arguments);
6893
6893
  resetQueue();
6894
6894
  }
6895
- Object.defineProperty(closeSync, previousSymbol, {
6895
+ Object.defineProperty(closeSync2, previousSymbol, {
6896
6896
  value: fs$closeSync
6897
6897
  });
6898
- return closeSync;
6898
+ return closeSync2;
6899
6899
  }(fs4.closeSync);
6900
6900
  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
6901
6901
  process.on("exit", function() {
@@ -20451,7 +20451,7 @@ var require_react_reconciler_development = __commonJS((exports, module) => {
20451
20451
  return hook.checkDCE ? true : false;
20452
20452
  }
20453
20453
  function setIsStrictModeForDevtools(newIsStrictMode) {
20454
- typeof log43 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
20454
+ typeof log44 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
20455
20455
  if (injectedHook && typeof injectedHook.setStrictMode === "function")
20456
20456
  try {
20457
20457
  injectedHook.setStrictMode(rendererID, newIsStrictMode);
@@ -28535,7 +28535,7 @@ Check the render method of %s.`, getComponentNameFromFiber(current) || "Unknown"
28535
28535
  var fiberStack = [];
28536
28536
  var index$jscomp$0 = -1, emptyContextObject = {};
28537
28537
  Object.freeze(emptyContextObject);
28538
- 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, log43 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
28538
+ 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, log44 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
28539
28539
  if (typeof performance === "object" && typeof performance.now === "function") {
28540
28540
  var localPerformance = performance;
28541
28541
  var getCurrentTime = function() {
@@ -48059,6 +48059,107 @@ var {
48059
48059
  Help
48060
48060
  } = import__.default;
48061
48061
 
48062
+ // src/persistence/audit-log.ts
48063
+ init_logger();
48064
+ import { chmodSync, closeSync, constants as fsConstants, fchmodSync, lstatSync, mkdirSync, openSync, writeSync } from "fs";
48065
+ import { join } from "path";
48066
+ import { homedir } from "os";
48067
+ var log = createLogger("audit");
48068
+ var DETAIL_MAX = 500;
48069
+ var enabledPlatforms = new Set;
48070
+ var preparedDirs = new Set;
48071
+ var openFds = new Map;
48072
+ function auditDir() {
48073
+ return process.env.CLAUDE_THREADS_AUDIT_DIR || join(homedir(), ".claude-threads", "audit");
48074
+ }
48075
+ function configureAuditLog(platformId, enabled) {
48076
+ if (enabled)
48077
+ enabledPlatforms.add(platformId);
48078
+ else
48079
+ enabledPlatforms.delete(platformId);
48080
+ }
48081
+ function isAuditEnabled(platformId) {
48082
+ return enabledPlatforms.has(platformId);
48083
+ }
48084
+ function openAuditFd(platformId) {
48085
+ const cached = openFds.get(platformId);
48086
+ if (cached !== undefined)
48087
+ return cached;
48088
+ const dir = auditDir();
48089
+ if (!preparedDirs.has(dir)) {
48090
+ mkdirSync(dir, { recursive: true, mode: 448 });
48091
+ chmodSync(dir, 448);
48092
+ preparedDirs.add(dir);
48093
+ }
48094
+ const file = join(dir, `${encodeURIComponent(platformId)}.jsonl`);
48095
+ try {
48096
+ const st = lstatSync(file);
48097
+ if (st.isSymbolicLink() || !st.isFile()) {
48098
+ throw new Error(`audit path exists but is not a regular file: ${file}`);
48099
+ }
48100
+ } catch (err) {
48101
+ if (err.code !== "ENOENT")
48102
+ throw err;
48103
+ }
48104
+ const noFollow = typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
48105
+ const fd = openSync(file, fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT | noFollow, 384);
48106
+ try {
48107
+ fchmodSync(fd, 384);
48108
+ } catch {}
48109
+ openFds.set(platformId, fd);
48110
+ return fd;
48111
+ }
48112
+ function auditLog(platformId, entry) {
48113
+ if (!enabledPlatforms.has(platformId))
48114
+ return;
48115
+ try {
48116
+ const full = {
48117
+ ts: new Date().toISOString(),
48118
+ platformId,
48119
+ ...entry,
48120
+ ...entry.detail !== undefined ? { detail: entry.detail.slice(0, DETAIL_MAX) } : {}
48121
+ };
48122
+ writeSync(openAuditFd(platformId), JSON.stringify(full) + `
48123
+ `);
48124
+ } catch (err) {
48125
+ const fd = openFds.get(platformId);
48126
+ if (fd !== undefined) {
48127
+ openFds.delete(platformId);
48128
+ try {
48129
+ closeSync(fd);
48130
+ } catch {}
48131
+ }
48132
+ log.warn(`audit write failed for ${platformId}: ${err}`);
48133
+ }
48134
+ }
48135
+ function auditDetailForTool(tool, input) {
48136
+ if (!input)
48137
+ return;
48138
+ const str = (v) => typeof v === "string" ? v : undefined;
48139
+ switch (tool) {
48140
+ case "Bash":
48141
+ return str(input.command);
48142
+ case "Edit":
48143
+ case "Write":
48144
+ case "Read":
48145
+ case "NotebookEdit":
48146
+ return str(input.file_path);
48147
+ case "Grep":
48148
+ case "Glob":
48149
+ return str(input.pattern);
48150
+ case "WebFetch":
48151
+ case "WebSearch":
48152
+ return str(input.url) ?? str(input.query);
48153
+ default: {
48154
+ try {
48155
+ return JSON.stringify(input);
48156
+ } catch {
48157
+ return;
48158
+ }
48159
+ }
48160
+ }
48161
+ }
48162
+
48062
48163
  // src/platform/dm-discovery.ts
48063
48164
  var DM_PLATFORM_SEP = "--dm-";
48064
48165
  function dmPlatformId(parentId, channelId) {
@@ -48079,7 +48180,7 @@ function deriveDmPlatformConfig(parent, channelId, partnerUsernames) {
48079
48180
 
48080
48181
  // src/session/lifecycle-fsm.ts
48081
48182
  init_logger();
48082
- var log = createLogger("fsm");
48183
+ var log2 = createLogger("fsm");
48083
48184
  var ALLOWED_TRANSITIONS = {
48084
48185
  starting: new Set(["active", "paused", "interrupted", "cancelling", "restarting"]),
48085
48186
  active: new Set([
@@ -48118,7 +48219,7 @@ function checkTransition(from, to, sessionId) {
48118
48219
  if (process.env.CLAUDE_THREADS_FSM_STRICT === "1") {
48119
48220
  throw new Error(`${msg} (sessionId=${sessionId})`);
48120
48221
  }
48121
- log.warn(msg, payload);
48222
+ log2.warn(msg, payload);
48122
48223
  }
48123
48224
 
48124
48225
  // src/session/timer-manager.ts
@@ -48183,9 +48284,9 @@ function getSessionStatus(session) {
48183
48284
  }
48184
48285
 
48185
48286
  // src/config/index.ts
48186
- import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
48287
+ import { existsSync, readFileSync, writeFileSync, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "fs";
48187
48288
  import { resolve, dirname } from "path";
48188
- import { homedir } from "os";
48289
+ import { homedir as homedir2 } from "os";
48189
48290
 
48190
48291
  // node_modules/js-yaml/dist/js-yaml.mjs
48191
48292
  function getDefaultExportFromCjs(x) {
@@ -51357,6 +51458,14 @@ function resolveRoutinesEnabled(value, fieldPath) {
51357
51458
  console.warn(`Invalid ${fieldPath ?? "routines"} config: expected boolean, got ${JSON.stringify(value)} — routines stay enabled`);
51358
51459
  return true;
51359
51460
  }
51461
+ function resolveAuditLogEnabled(value, fieldPath) {
51462
+ if (value === true)
51463
+ return true;
51464
+ if (value === undefined || value === null || value === false)
51465
+ return false;
51466
+ console.warn(`Invalid ${fieldPath ?? "auditLog"} config: expected boolean, got ${JSON.stringify(value)} — audit log stays off`);
51467
+ return false;
51468
+ }
51360
51469
  var LIMITS_DEFAULTS = {
51361
51470
  maxSessions: 5,
51362
51471
  sessionTimeoutMinutes: 30,
@@ -51425,7 +51534,7 @@ function effectivePermissionMode(input) {
51425
51534
  }
51426
51535
 
51427
51536
  // src/config/index.ts
51428
- var CONFIG_PATH = resolve(homedir(), ".config", "claude-threads", "config.yaml");
51537
+ var CONFIG_PATH = resolve(homedir2(), ".config", "claude-threads", "config.yaml");
51429
51538
  function loadConfigWithMigration() {
51430
51539
  if (existsSync(CONFIG_PATH)) {
51431
51540
  const content = readFileSync(CONFIG_PATH, "utf-8");
@@ -51436,7 +51545,7 @@ function loadConfigWithMigration() {
51436
51545
  function saveConfig(config, path = CONFIG_PATH) {
51437
51546
  const configDir = dirname(path);
51438
51547
  if (!existsSync(configDir)) {
51439
- mkdirSync(configDir, { recursive: true, mode: 448 });
51548
+ mkdirSync2(configDir, { recursive: true, mode: 448 });
51440
51549
  }
51441
51550
  const yamlContent = yaml.dump(config, {
51442
51551
  indent: 2,
@@ -51446,8 +51555,8 @@ function saveConfig(config, path = CONFIG_PATH) {
51446
51555
  });
51447
51556
  writeFileSync(path, yamlContent, { encoding: "utf-8", mode: 384 });
51448
51557
  try {
51449
- chmodSync(configDir, 448);
51450
- chmodSync(path, 384);
51558
+ chmodSync2(configDir, 448);
51559
+ chmodSync2(path, 384);
51451
51560
  } catch {}
51452
51561
  }
51453
51562
  function configExists() {
@@ -51563,7 +51672,9 @@ var EMOJI_UNICODE_TO_NAME = {
51563
51672
  "\uD83D\uDC64": "bust_in_silhouette",
51564
51673
  "\uD83D\uDCCB": "clipboard",
51565
51674
  "\uD83D\uDD3D": "small_red_triangle_down",
51566
- "\uD83C\uDD95": "new"
51675
+ "\uD83C\uDD95": "new",
51676
+ "\uD83D\uDC40": "eyes",
51677
+ "❤️": "heart"
51567
51678
  };
51568
51679
  function getEmojiName(emoji) {
51569
51680
  const mapped = EMOJI_UNICODE_TO_NAME[emoji];
@@ -51638,6 +51749,27 @@ function resolveDirectChannelMode(cfg) {
51638
51749
  function resolveApprovals(configured, isDcmSession) {
51639
51750
  return configured ?? (isDcmSession ? "owner" : "all_users");
51640
51751
  }
51752
+ function normalizeAckReaction(value, fieldPath) {
51753
+ if (value === undefined || value === null)
51754
+ return;
51755
+ if (typeof value === "boolean")
51756
+ return value;
51757
+ if (typeof value === "string" && value.trim().length > 0) {
51758
+ const name = getEmojiName(value.trim());
51759
+ if (!/^[a-z0-9_+':.-]+$/i.test(name)) {
51760
+ console.warn(`Invalid ${fieldPath}: unknown emoji ${JSON.stringify(value)} — use its shortcode name (e.g. "eyes"); ack reaction disabled`);
51761
+ return;
51762
+ }
51763
+ return name;
51764
+ }
51765
+ console.warn(`Invalid ${fieldPath}: expected boolean or emoji name, got ${JSON.stringify(value)} — ack reaction disabled`);
51766
+ return;
51767
+ }
51768
+ function resolveAckReaction(configured) {
51769
+ if (!configured)
51770
+ return null;
51771
+ return typeof configured === "string" ? configured : "eyes";
51772
+ }
51641
51773
 
51642
51774
  // src/session/authorization.ts
51643
51775
  function isAuthorizedForSession(check) {
@@ -51654,7 +51786,7 @@ function isAuthorizedForSession(check) {
51654
51786
  // src/mcp/decision-bridge.ts
51655
51787
  import { createServer, createConnection } from "node:net";
51656
51788
  import { tmpdir } from "node:os";
51657
- import { join } from "node:path";
51789
+ import { join as join2 } from "node:path";
51658
51790
  import { randomUUID } from "node:crypto";
51659
51791
  import { mkdtempSync } from "node:fs";
51660
51792
  import { rm } from "node:fs/promises";
@@ -51665,8 +51797,8 @@ function bridgeSocketPath() {
51665
51797
  if (process.platform === "win32") {
51666
51798
  return `\\\\.\\pipe\\ctb-${randomUUID()}`;
51667
51799
  }
51668
- const dir = mkdtempSync(join(tmpdir(), "ctb-"));
51669
- return join(dir, "b.sock");
51800
+ const dir = mkdtempSync(join2(tmpdir(), "ctb-"));
51801
+ return join2(dir, "b.sock");
51670
51802
  }
51671
51803
 
51672
51804
  class DecisionBridgeServer {
@@ -51736,7 +51868,7 @@ class DecisionBridgeServer {
51736
51868
  });
51737
51869
  } catch (err) {
51738
51870
  if (process.platform !== "win32") {
51739
- await rm(join(path, ".."), { recursive: true, force: true }).catch(() => {});
51871
+ await rm(join2(path, ".."), { recursive: true, force: true }).catch(() => {});
51740
51872
  }
51741
51873
  throw err;
51742
51874
  }
@@ -51749,7 +51881,7 @@ class DecisionBridgeServer {
51749
51881
  socket.destroy();
51750
51882
  await new Promise((resolve2) => this.server.close(() => resolve2()));
51751
51883
  if (process.platform !== "win32") {
51752
- await rm(join(this.path, ".."), { recursive: true, force: true }).catch(() => {});
51884
+ await rm(join2(this.path, ".."), { recursive: true, force: true }).catch(() => {});
51753
51885
  }
51754
51886
  }
51755
51887
  }
@@ -51762,18 +51894,18 @@ import { resolve as resolve2, dirname as dirname2 } from "path";
51762
51894
  import { fileURLToPath } from "url";
51763
51895
  import { existsSync as existsSync3, readFileSync as readFileSync2, watchFile, unwatchFile, unlinkSync, statSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
51764
51896
  import { tmpdir as tmpdir2 } from "os";
51765
- import { join as join3 } from "path";
51897
+ import { join as join4 } from "path";
51766
51898
 
51767
51899
  // src/claude/version-check.ts
51768
51900
  var import_semver = __toESM(require_semver2(), 1);
51769
51901
  import { execSync } from "child_process";
51770
51902
  import { existsSync as existsSync2 } from "fs";
51771
- import { join as join2 } from "path";
51903
+ import { join as join3 } from "path";
51772
51904
  var COMMON_CLAUDE_PATHS = process.platform === "win32" ? [
51773
- ...process.env.APPDATA ? [join2(process.env.APPDATA, "npm", "claude.cmd")] : [],
51774
- ...process.env.LOCALAPPDATA ? [join2(process.env.LOCALAPPDATA, "npm", "claude.cmd")] : [],
51775
- ...process.env.NVM_SYMLINK ? [join2(process.env.NVM_SYMLINK, "claude.cmd")] : [],
51776
- ...process.env.USERPROFILE ? [join2(process.env.USERPROFILE, ".bun", "bin", "claude.cmd")] : []
51905
+ ...process.env.APPDATA ? [join3(process.env.APPDATA, "npm", "claude.cmd")] : [],
51906
+ ...process.env.LOCALAPPDATA ? [join3(process.env.LOCALAPPDATA, "npm", "claude.cmd")] : [],
51907
+ ...process.env.NVM_SYMLINK ? [join3(process.env.NVM_SYMLINK, "claude.cmd")] : [],
51908
+ ...process.env.USERPROFILE ? [join3(process.env.USERPROFILE, ".bun", "bin", "claude.cmd")] : []
51777
51909
  ] : [
51778
51910
  "/usr/local/bin/claude",
51779
51911
  "/opt/homebrew/bin/claude",
@@ -52032,25 +52164,25 @@ function parseRateLimitEvent(event, now = Date.now()) {
52032
52164
  }
52033
52165
 
52034
52166
  // src/claude/cli.ts
52035
- var log2 = createLogger("claude");
52167
+ var log3 = createLogger("claude");
52036
52168
  function cleanupBrowserBridgeSockets() {
52037
52169
  try {
52038
52170
  const tempDir = tmpdir2();
52039
52171
  const files = readdirSync(tempDir);
52040
52172
  for (const file of files) {
52041
52173
  if (file.startsWith("claude-mcp-browser-bridge-")) {
52042
- const filePath = join3(tempDir, file);
52174
+ const filePath = join4(tempDir, file);
52043
52175
  try {
52044
52176
  const stats = statSync(filePath);
52045
52177
  if (stats.isSocket()) {
52046
52178
  unlinkSync(filePath);
52047
- log2.debug(`Removed stale browser bridge socket: ${file}`);
52179
+ log3.debug(`Removed stale browser bridge socket: ${file}`);
52048
52180
  }
52049
52181
  } catch {}
52050
52182
  }
52051
52183
  }
52052
52184
  } catch (err) {
52053
- log2.debug(`Browser bridge cleanup failed: ${err}`);
52185
+ log3.debug(`Browser bridge cleanup failed: ${err}`);
52054
52186
  }
52055
52187
  }
52056
52188
  function buildClaudeChildEnv(parentEnv, account, opts) {
@@ -52109,7 +52241,7 @@ function materializeMcpConfig(config, sessionId, opts = {}) {
52109
52241
  return { mode: "inline", value: JSON.stringify(config) };
52110
52242
  }
52111
52243
  const dir = opts.tmpDirOverride ?? tmpdir2();
52112
- const path = join3(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
52244
+ const path = join4(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
52113
52245
  writeFileSync2(path, JSON.stringify(config), { mode: 384 });
52114
52246
  return { mode: "file", path };
52115
52247
  }
@@ -52292,7 +52424,7 @@ class ClaudeCli extends EventEmitter {
52292
52424
  }
52293
52425
  let statusLineCommand;
52294
52426
  if (this.options.sessionId) {
52295
- this.statusFilePath = join3(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
52427
+ this.statusFilePath = join4(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
52296
52428
  const statusLineWriterPath = this.getStatusLineWriterPath();
52297
52429
  const runtime = runtimeForScriptPath(statusLineWriterPath);
52298
52430
  statusLineCommand = `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`;
@@ -52563,11 +52695,11 @@ class ClaudeCli extends EventEmitter {
52563
52695
 
52564
52696
  // src/persistence/thread-logger.ts
52565
52697
  init_logger();
52566
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, appendFileSync, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, rmdirSync, readFileSync as readFileSync3, chmodSync as chmodSync2 } from "fs";
52567
- import { homedir as homedir2 } from "os";
52568
- import { join as join4, dirname as dirname3 } from "path";
52569
- var log3 = createLogger("thread-log");
52570
- var LOGS_BASE_DIR = join4(homedir2(), ".claude-threads", "logs");
52698
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, appendFileSync, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, rmdirSync, readFileSync as readFileSync3, chmodSync as chmodSync3 } from "fs";
52699
+ import { homedir as homedir3 } from "os";
52700
+ import { join as join5, dirname as dirname3 } from "path";
52701
+ var log4 = createLogger("thread-log");
52702
+ var LOGS_BASE_DIR = join5(homedir3(), ".claude-threads", "logs");
52571
52703
 
52572
52704
  class ThreadLoggerImpl {
52573
52705
  platformId;
@@ -52587,16 +52719,16 @@ class ThreadLoggerImpl {
52587
52719
  this.enabled = options?.enabled ?? true;
52588
52720
  this.bufferSize = options?.bufferSize ?? 10;
52589
52721
  this.flushIntervalMs = options?.flushIntervalMs ?? 1000;
52590
- this.logPath = join4(LOGS_BASE_DIR, platformId, `${claudeSessionId}.jsonl`);
52722
+ this.logPath = join5(LOGS_BASE_DIR, platformId, `${claudeSessionId}.jsonl`);
52591
52723
  if (this.enabled) {
52592
52724
  const dir = dirname3(this.logPath);
52593
52725
  if (!existsSync4(dir)) {
52594
- mkdirSync2(dir, { recursive: true });
52726
+ mkdirSync3(dir, { recursive: true });
52595
52727
  }
52596
52728
  this.flushTimer = setInterval(() => {
52597
52729
  this.flushSync();
52598
52730
  }, this.flushIntervalMs);
52599
- log3.debug(`Thread logger initialized: ${this.logPath}`);
52731
+ log4.debug(`Thread logger initialized: ${this.logPath}`);
52600
52732
  }
52601
52733
  }
52602
52734
  isEnabled() {
@@ -52710,7 +52842,7 @@ class ThreadLoggerImpl {
52710
52842
  this.flushTimer = null;
52711
52843
  }
52712
52844
  this.flushSync();
52713
- log3.debug(`Thread logger closed: ${this.logPath}`);
52845
+ log4.debug(`Thread logger closed: ${this.logPath}`);
52714
52846
  }
52715
52847
  addEntry(entry) {
52716
52848
  this.buffer.push(entry);
@@ -52728,11 +52860,11 @@ class ThreadLoggerImpl {
52728
52860
  const isNewFile = !existsSync4(this.logPath);
52729
52861
  appendFileSync(this.logPath, lines, { encoding: "utf8", mode: 384 });
52730
52862
  if (isNewFile) {
52731
- chmodSync2(this.logPath, 384);
52863
+ chmodSync3(this.logPath, 384);
52732
52864
  }
52733
52865
  this.buffer = [];
52734
52866
  } catch (err) {
52735
- log3.error(`Failed to flush thread log: ${err}`);
52867
+ log4.error(`Failed to flush thread log: ${err}`);
52736
52868
  }
52737
52869
  }
52738
52870
  }
@@ -52769,7 +52901,7 @@ function cleanupOldLogs(retentionDays = 30) {
52769
52901
  try {
52770
52902
  const platformDirs = readdirSync2(LOGS_BASE_DIR);
52771
52903
  for (const platformId of platformDirs) {
52772
- const platformDir = join4(LOGS_BASE_DIR, platformId);
52904
+ const platformDir = join5(LOGS_BASE_DIR, platformId);
52773
52905
  const stat = statSync2(platformDir);
52774
52906
  if (!stat.isDirectory())
52775
52907
  continue;
@@ -52777,49 +52909,49 @@ function cleanupOldLogs(retentionDays = 30) {
52777
52909
  for (const file of logFiles) {
52778
52910
  if (!file.endsWith(".jsonl"))
52779
52911
  continue;
52780
- const filePath = join4(platformDir, file);
52912
+ const filePath = join5(platformDir, file);
52781
52913
  try {
52782
52914
  const fileStat = statSync2(filePath);
52783
52915
  if (fileStat.mtimeMs < cutoffMs) {
52784
52916
  unlinkSync2(filePath);
52785
52917
  deletedCount++;
52786
- log3.debug(`Deleted old log file: ${filePath}`);
52918
+ log4.debug(`Deleted old log file: ${filePath}`);
52787
52919
  }
52788
52920
  } catch (err) {
52789
- log3.warn(`Failed to check/delete log file ${filePath}: ${err}`);
52921
+ log4.warn(`Failed to check/delete log file ${filePath}: ${err}`);
52790
52922
  }
52791
52923
  }
52792
52924
  try {
52793
52925
  const remaining = readdirSync2(platformDir);
52794
52926
  if (remaining.length === 0) {
52795
52927
  rmdirSync(platformDir);
52796
- log3.debug(`Removed empty platform log directory: ${platformDir}`);
52928
+ log4.debug(`Removed empty platform log directory: ${platformDir}`);
52797
52929
  }
52798
52930
  } catch {}
52799
52931
  }
52800
52932
  if (deletedCount > 0) {
52801
- log3.info(`Cleaned up ${deletedCount} old log file(s)`);
52933
+ log4.info(`Cleaned up ${deletedCount} old log file(s)`);
52802
52934
  }
52803
52935
  } catch (err) {
52804
- log3.error(`Failed to clean up old logs: ${err}`);
52936
+ log4.error(`Failed to clean up old logs: ${err}`);
52805
52937
  }
52806
52938
  return deletedCount;
52807
52939
  }
52808
52940
  function getLogFilePath(platformId, sessionId) {
52809
- return join4(LOGS_BASE_DIR, platformId, `${sessionId}.jsonl`);
52941
+ return join5(LOGS_BASE_DIR, platformId, `${sessionId}.jsonl`);
52810
52942
  }
52811
52943
  function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
52812
52944
  const logPath = getLogFilePath(platformId, sessionId);
52813
- log3.debug(`Reading log entries from: ${logPath}`);
52945
+ log4.debug(`Reading log entries from: ${logPath}`);
52814
52946
  if (!existsSync4(logPath)) {
52815
- log3.debug(`Log file does not exist: ${logPath}`);
52947
+ log4.debug(`Log file does not exist: ${logPath}`);
52816
52948
  return [];
52817
52949
  }
52818
52950
  try {
52819
52951
  const content = readFileSync3(logPath, "utf8");
52820
52952
  const lines = content.trim().split(`
52821
52953
  `);
52822
- log3.debug(`Log file has ${lines.length} lines`);
52954
+ log4.debug(`Log file has ${lines.length} lines`);
52823
52955
  const recentLines = lines.slice(-maxLines);
52824
52956
  const entries = [];
52825
52957
  for (const line of recentLines) {
@@ -52829,10 +52961,10 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
52829
52961
  entries.push(JSON.parse(line));
52830
52962
  } catch {}
52831
52963
  }
52832
- log3.debug(`Parsed ${entries.length} log entries`);
52964
+ log4.debug(`Parsed ${entries.length} log entries`);
52833
52965
  return entries;
52834
52966
  } catch (err) {
52835
- log3.error(`Failed to read log file: ${err}`);
52967
+ log4.error(`Failed to read log file: ${err}`);
52836
52968
  return [];
52837
52969
  }
52838
52970
  }
@@ -53799,7 +53931,7 @@ async function handleDynamicSlashCommand(command, args, ctx) {
53799
53931
  }
53800
53932
  // src/commands/system-prompt-generator.ts
53801
53933
  init_logger();
53802
- var log4 = createLogger("system-prompt");
53934
+ var log5 = createLogger("system-prompt");
53803
53935
  function formatUserCommand(cmd) {
53804
53936
  const cmdStr = cmd.args ? `\`!${cmd.command} ${cmd.args}\`` : `\`!${cmd.command}\``;
53805
53937
  const description = cmd.description;
@@ -53838,7 +53970,7 @@ async function resolveCollaborators(platform, platformId, ownerUsername, allowed
53838
53970
  continue;
53839
53971
  const email = githubEmailsStore.get(platformId, username);
53840
53972
  if (!email) {
53841
- log4.debug(`Collaborator @${username} has no registered GitHub noreply email — skipping`);
53973
+ log5.debug(`Collaborator @${username} has no registered GitHub noreply email — skipping`);
53842
53974
  continue;
53843
53975
  }
53844
53976
  let name = username;
@@ -53847,7 +53979,7 @@ async function resolveCollaborators(platform, platformId, ownerUsername, allowed
53847
53979
  if (user)
53848
53980
  name = user.displayName || user.username;
53849
53981
  } catch (err) {
53850
- log4.debug(`Display name lookup failed for @${username}: ${err.message}`);
53982
+ log5.debug(`Display name lookup failed for @${username}: ${err.message}`);
53851
53983
  }
53852
53984
  resolved.push({ username, name, email });
53853
53985
  }
@@ -53976,7 +54108,7 @@ import { existsSync as existsSync11 } from "fs";
53976
54108
  // src/utils/keep-alive.ts
53977
54109
  init_logger();
53978
54110
  import { spawn } from "child_process";
53979
- var log5 = createLogger("keepalive");
54111
+ var log6 = createLogger("keepalive");
53980
54112
  function keepAliveSpawnSpec(platform, parentPid) {
53981
54113
  switch (platform) {
53982
54114
  case "darwin":
@@ -54032,7 +54164,7 @@ class KeepAliveManager {
54032
54164
  if (!enabled && this.keepAliveProcess) {
54033
54165
  this.stopKeepAlive();
54034
54166
  }
54035
- log5.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
54167
+ log6.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
54036
54168
  }
54037
54169
  isEnabled() {
54038
54170
  return this.enabled;
@@ -54042,7 +54174,7 @@ class KeepAliveManager {
54042
54174
  }
54043
54175
  sessionStarted() {
54044
54176
  this.activeSessionCount++;
54045
- log5.debug(`Session started (${this.activeSessionCount} active)`);
54177
+ log6.debug(`Session started (${this.activeSessionCount} active)`);
54046
54178
  if (this.activeSessionCount === 1) {
54047
54179
  this.startKeepAlive();
54048
54180
  }
@@ -54051,7 +54183,7 @@ class KeepAliveManager {
54051
54183
  if (this.activeSessionCount > 0) {
54052
54184
  this.activeSessionCount--;
54053
54185
  }
54054
- log5.debug(`Session ended (${this.activeSessionCount} active)`);
54186
+ log6.debug(`Session ended (${this.activeSessionCount} active)`);
54055
54187
  if (this.activeSessionCount === 0) {
54056
54188
  this.stopKeepAlive();
54057
54189
  }
@@ -54065,11 +54197,11 @@ class KeepAliveManager {
54065
54197
  }
54066
54198
  startKeepAlive() {
54067
54199
  if (!this.enabled) {
54068
- log5.debug("Keep-alive disabled, skipping");
54200
+ log6.debug("Keep-alive disabled, skipping");
54069
54201
  return;
54070
54202
  }
54071
54203
  if (this.keepAliveProcess) {
54072
- log5.debug("Keep-alive already running");
54204
+ log6.debug("Keep-alive already running");
54073
54205
  return;
54074
54206
  }
54075
54207
  switch (this.platform) {
@@ -54083,12 +54215,12 @@ class KeepAliveManager {
54083
54215
  this.startWindowsKeepAlive();
54084
54216
  break;
54085
54217
  default:
54086
- log5.warn(`Keep-alive not supported on ${this.platform}`);
54218
+ log6.warn(`Keep-alive not supported on ${this.platform}`);
54087
54219
  }
54088
54220
  }
54089
54221
  stopKeepAlive() {
54090
54222
  if (this.keepAliveProcess) {
54091
- log5.debug("Stopping keep-alive");
54223
+ log6.debug("Stopping keep-alive");
54092
54224
  this.keepAliveProcess.kill();
54093
54225
  this.keepAliveProcess = null;
54094
54226
  }
@@ -54103,18 +54235,18 @@ class KeepAliveManager {
54103
54235
  detached: false
54104
54236
  });
54105
54237
  this.keepAliveProcess.on("error", (err) => {
54106
- log5.error(`Failed to start caffeinate: ${err.message}`);
54238
+ log6.error(`Failed to start caffeinate: ${err.message}`);
54107
54239
  this.keepAliveProcess = null;
54108
54240
  });
54109
54241
  this.keepAliveProcess.on("exit", (code) => {
54110
54242
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
54111
- log5.debug(`caffeinate exited with code ${code}`);
54243
+ log6.debug(`caffeinate exited with code ${code}`);
54112
54244
  }
54113
54245
  this.keepAliveProcess = null;
54114
54246
  });
54115
- log5.info("Sleep prevention active (caffeinate)");
54247
+ log6.info("Sleep prevention active (caffeinate)");
54116
54248
  } catch (err) {
54117
- log5.error(`Failed to start caffeinate: ${err}`);
54249
+ log6.error(`Failed to start caffeinate: ${err}`);
54118
54250
  }
54119
54251
  }
54120
54252
  startLinuxKeepAlive() {
@@ -54127,19 +54259,19 @@ class KeepAliveManager {
54127
54259
  detached: false
54128
54260
  });
54129
54261
  this.keepAliveProcess.on("error", (err) => {
54130
- log5.debug(`systemd-inhibit not available: ${err.message}`);
54262
+ log6.debug(`systemd-inhibit not available: ${err.message}`);
54131
54263
  this.keepAliveProcess = null;
54132
54264
  this.startLinuxKeepAliveFallback();
54133
54265
  });
54134
54266
  this.keepAliveProcess.on("exit", (code) => {
54135
54267
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
54136
- log5.debug(`systemd-inhibit exited with code ${code}`);
54268
+ log6.debug(`systemd-inhibit exited with code ${code}`);
54137
54269
  }
54138
54270
  this.keepAliveProcess = null;
54139
54271
  });
54140
- log5.info("Sleep prevention active (systemd-inhibit)");
54272
+ log6.info("Sleep prevention active (systemd-inhibit)");
54141
54273
  } catch (err) {
54142
- log5.debug(`Failed to start systemd-inhibit: ${err}`);
54274
+ log6.debug(`Failed to start systemd-inhibit: ${err}`);
54143
54275
  this.startLinuxKeepAliveFallback();
54144
54276
  }
54145
54277
  }
@@ -54150,15 +54282,15 @@ class KeepAliveManager {
54150
54282
  detached: false
54151
54283
  });
54152
54284
  this.keepAliveProcess.on("error", (err) => {
54153
- log5.warn(`Linux keep-alive fallback not available: ${err.message}`);
54285
+ log6.warn(`Linux keep-alive fallback not available: ${err.message}`);
54154
54286
  this.keepAliveProcess = null;
54155
54287
  });
54156
54288
  this.keepAliveProcess.on("exit", () => {
54157
54289
  this.keepAliveProcess = null;
54158
54290
  });
54159
- log5.info("Sleep prevention active (xdg-screensaver)");
54291
+ log6.info("Sleep prevention active (xdg-screensaver)");
54160
54292
  } catch (err) {
54161
- log5.warn(`Linux keep-alive not available: ${err}`);
54293
+ log6.warn(`Linux keep-alive not available: ${err}`);
54162
54294
  }
54163
54295
  }
54164
54296
  startWindowsKeepAlive() {
@@ -54170,18 +54302,18 @@ class KeepAliveManager {
54170
54302
  windowsHide: true
54171
54303
  });
54172
54304
  this.keepAliveProcess.on("error", (err) => {
54173
- log5.warn(`Windows keep-alive not available: ${err.message}`);
54305
+ log6.warn(`Windows keep-alive not available: ${err.message}`);
54174
54306
  this.keepAliveProcess = null;
54175
54307
  });
54176
54308
  this.keepAliveProcess.on("exit", (code) => {
54177
54309
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
54178
- log5.debug(`PowerShell keep-alive exited with code ${code}`);
54310
+ log6.debug(`PowerShell keep-alive exited with code ${code}`);
54179
54311
  }
54180
54312
  this.keepAliveProcess = null;
54181
54313
  });
54182
- log5.info("Sleep prevention active (SetThreadExecutionState)");
54314
+ log6.info("Sleep prevention active (SetThreadExecutionState)");
54183
54315
  } catch (err) {
54184
- log5.warn(`Windows keep-alive not available: ${err}`);
54316
+ log6.warn(`Windows keep-alive not available: ${err}`);
54185
54317
  }
54186
54318
  }
54187
54319
  }
@@ -54249,7 +54381,7 @@ function truncateAtWord(str2, maxLength) {
54249
54381
  }
54250
54382
 
54251
54383
  // src/utils/error-handler/index.ts
54252
- var log6 = createLogger("error");
54384
+ var log7 = createLogger("error");
54253
54385
 
54254
54386
  class SessionError extends Error {
54255
54387
  sessionId;
@@ -54275,19 +54407,19 @@ async function handleError(error, context, severity = "recoverable") {
54275
54407
  const sessionPart = sessionId ? ` (${formatShortId(sessionId)})` : "";
54276
54408
  const logMessage = `${context.action}${sessionPart}: ${message}`;
54277
54409
  if (severity === "recoverable") {
54278
- log6.warn(logMessage);
54410
+ log7.warn(logMessage);
54279
54411
  } else {
54280
- log6.error(logMessage, error instanceof Error ? error : undefined);
54412
+ log7.error(logMessage, error instanceof Error ? error : undefined);
54281
54413
  }
54282
54414
  if (context.details) {
54283
- log6.debugJson("Error details", context.details);
54415
+ log7.debugJson("Error details", context.details);
54284
54416
  }
54285
54417
  if (context.notifyUser && context.session) {
54286
54418
  try {
54287
54419
  const fmt = context.session.platform.getFormatter();
54288
54420
  await context.session.platform.createPost(`⚠️ ${fmt.formatBold("Error")}: ${context.action} failed - ${message}`, context.session.threadId);
54289
54421
  } catch (notifyError) {
54290
- log6.warn(`Could not notify user: ${notifyError}`);
54422
+ log7.warn(`Could not notify user: ${notifyError}`);
54291
54423
  }
54292
54424
  }
54293
54425
  if (severity === "session-fatal" || severity === "system-fatal") {
@@ -54314,7 +54446,7 @@ async function logAndNotify(error, context) {
54314
54446
  }
54315
54447
  function logSilentError(context, error) {
54316
54448
  const message = error instanceof Error ? error.message : String(error);
54317
- log6.debug(`[${context}] Silently caught: ${message}`);
54449
+ log7.debug(`[${context}] Silently caught: ${message}`);
54318
54450
  }
54319
54451
 
54320
54452
  // src/session/lifecycle.ts
@@ -54334,8 +54466,8 @@ function createSessionLog(baseLog) {
54334
54466
  init_logger();
54335
54467
  init_emoji();
54336
54468
  init_worktree();
54337
- var log8 = createLogger("helpers");
54338
- var sessionLog = createSessionLog(log8);
54469
+ var log9 = createLogger("helpers");
54470
+ var sessionLog = createSessionLog(log9);
54339
54471
  var POST_TYPES = {
54340
54472
  info: "",
54341
54473
  success: "✅",
@@ -54423,7 +54555,7 @@ function updateLastMessage(session, post2) {
54423
54555
  init_logger();
54424
54556
  import { lstat, mkdir as mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
54425
54557
  import { tmpdir as tmpdir3 } from "os";
54426
- import { join as join6 } from "path";
54558
+ import { join as join7 } from "path";
54427
54559
 
54428
54560
  // src/utils/safe-filename.ts
54429
54561
  import { basename as basename2 } from "path";
@@ -54464,13 +54596,13 @@ function formatBytes(bytes) {
54464
54596
  }
54465
54597
 
54466
54598
  // src/operations/streaming/handler.ts
54467
- var log9 = createLogger("streaming");
54599
+ var log10 = createLogger("streaming");
54468
54600
  var UPLOAD_ROOT_DIR = "claude-threads-uploads";
54469
54601
  function safeIdSegment(id) {
54470
54602
  return id.replace(/[^A-Za-z0-9._-]/g, "_");
54471
54603
  }
54472
54604
  function getSessionUploadDir(platformId, threadId) {
54473
- return join6(tmpdir3(), UPLOAD_ROOT_DIR, `${safeIdSegment(platformId)}-${safeIdSegment(threadId)}`);
54605
+ return join7(tmpdir3(), UPLOAD_ROOT_DIR, `${safeIdSegment(platformId)}-${safeIdSegment(threadId)}`);
54474
54606
  }
54475
54607
  async function cleanupSessionUploads(platformId, threadId) {
54476
54608
  if (!platformId || !threadId)
@@ -54479,7 +54611,7 @@ async function cleanupSessionUploads(platformId, threadId) {
54479
54611
  try {
54480
54612
  await rm2(dir, { recursive: true, force: true });
54481
54613
  } catch (err) {
54482
- log9.debug(`Upload cleanup for ${platformId}:${threadId} failed (ignored): ${err}`);
54614
+ log10.debug(`Upload cleanup for ${platformId}:${threadId} failed (ignored): ${err}`);
54483
54615
  }
54484
54616
  }
54485
54617
  function sanitizeForPrompt(value) {
@@ -54500,16 +54632,16 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
54500
54632
  for (const file of files) {
54501
54633
  skipped.push({ name: file.name, reason: "Refusing to write under symlinked upload directory" });
54502
54634
  }
54503
- log9.error(`Upload dir is a symlink, refusing all writes: ${uploadDir}`);
54635
+ log10.error(`Upload dir is a symlink, refusing all writes: ${uploadDir}`);
54504
54636
  return { saved, skipped };
54505
54637
  }
54506
- const messageDir = await mkdtemp(join6(uploadDir, `${Date.now().toString(36)}-`));
54638
+ const messageDir = await mkdtemp(join7(uploadDir, `${Date.now().toString(36)}-`));
54507
54639
  const usedNames = new Set;
54508
54640
  for (const file of files) {
54509
54641
  try {
54510
54642
  const buffer = await platform.downloadFile(file.id);
54511
54643
  const safeName = dedupeFilename(sanitizeFilename(file.name), usedNames);
54512
- const absolutePath = join6(messageDir, safeName);
54644
+ const absolutePath = join7(messageDir, safeName);
54513
54645
  await writeFile2(absolutePath, buffer, { mode: 384, flag: "wx" });
54514
54646
  saved.push({
54515
54647
  originalName: file.name,
@@ -54518,11 +54650,11 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
54518
54650
  size: buffer.length
54519
54651
  });
54520
54652
  if (debug) {
54521
- log9.debug(`Saved ${file.name} → ${absolutePath} (${formatBytes(buffer.length)})`);
54653
+ log10.debug(`Saved ${file.name} → ${absolutePath} (${formatBytes(buffer.length)})`);
54522
54654
  }
54523
54655
  } catch (err) {
54524
54656
  const message = err instanceof Error ? err.message : String(err);
54525
- log9.error(`Failed to save uploaded file ${file.name}: ${message}`);
54657
+ log10.error(`Failed to save uploaded file ${file.name}: ${message}`);
54526
54658
  skipped.push({
54527
54659
  name: file.name,
54528
54660
  reason: `Download failed: ${message}`
@@ -58024,7 +58156,7 @@ init_emoji();
58024
58156
  import { execSync as execSync2 } from "child_process";
58025
58157
  import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
58026
58158
  import { tmpdir as tmpdir4 } from "os";
58027
- import { join as join7 } from "path";
58159
+ import { join as join8 } from "path";
58028
58160
 
58029
58161
  // node_modules/@redactpii/node/lib/index.mjs
58030
58162
  class Redactor {
@@ -58604,7 +58736,7 @@ async function createGitHubIssue(title, body, workingDir) {
58604
58736
  if (!ghStatus.installed || !ghStatus.authenticated) {
58605
58737
  throw new Error(ghStatus.error);
58606
58738
  }
58607
- const bodyFile = join7(tmpdir4(), `bug-body-${Date.now()}.md`);
58739
+ const bodyFile = join8(tmpdir4(), `bug-body-${Date.now()}.md`);
58608
58740
  try {
58609
58741
  writeFileSync4(bodyFile, body, "utf-8");
58610
58742
  const cmd = `gh issue create --repo "${GITHUB_REPO}" --title "${escapeShell(title)}" --body-file "${bodyFile}"`;
@@ -62155,14 +62287,29 @@ class QuestionApprovalExecutor extends BaseExecutor {
62155
62287
  return false;
62156
62288
  }
62157
62289
  if (this.state.pendingApproval?.postId === postId) {
62290
+ const approvalType = this.state.pendingApproval.type;
62291
+ const auditDecision = (approved) => {
62292
+ if (approvalType !== "plan")
62293
+ return;
62294
+ auditLog(ctx.platform.platformId, {
62295
+ threadId: ctx.threadId,
62296
+ sessionId: ctx.sessionId,
62297
+ actor: user,
62298
+ kind: "plan_approval",
62299
+ approved,
62300
+ detail: "via reaction"
62301
+ });
62302
+ };
62158
62303
  if (isApprovalEmoji(emoji)) {
62159
62304
  ctx.logger.debug(`Approval reaction from @${user}: approved`);
62305
+ auditDecision(true);
62160
62306
  const handled = await this.handleApprovalResponse(postId, true, ctx);
62161
62307
  ctx.logger.debug(`QuestionApprovalExecutor: approval outcome=approved, handled=${handled}`);
62162
62308
  return handled;
62163
62309
  }
62164
62310
  if (isDenialEmoji(emoji)) {
62165
62311
  ctx.logger.debug(`Approval reaction from @${user}: denied`);
62312
+ auditDecision(false);
62166
62313
  const handled = await this.handleApprovalResponse(postId, false, ctx);
62167
62314
  ctx.logger.debug(`QuestionApprovalExecutor: approval outcome=denied, handled=${handled}`);
62168
62315
  return handled;
@@ -62620,7 +62767,7 @@ class BugReportExecutor extends BaseExecutor {
62620
62767
  // src/operations/executors/worktree-prompt.ts
62621
62768
  init_emoji();
62622
62769
  init_logger();
62623
- var log10 = createLogger("wt-prompt");
62770
+ var log11 = createLogger("wt-prompt");
62624
62771
  // src/operations/message-manager.ts
62625
62772
  init_logger();
62626
62773
 
@@ -62700,7 +62847,7 @@ function formatRelativeTime(date) {
62700
62847
  return `${diffMin} min ago`;
62701
62848
  }
62702
62849
  // src/operations/message-manager.ts
62703
- var log11 = createLogger("msg-mgr");
62850
+ var log12 = createLogger("msg-mgr");
62704
62851
 
62705
62852
  class MessageManager {
62706
62853
  platform;
@@ -62793,7 +62940,7 @@ class MessageManager {
62793
62940
  });
62794
62941
  }
62795
62942
  async handleEvent(event) {
62796
- const logger = log11.forSession(this.sessionId);
62943
+ const logger = log12.forSession(this.sessionId);
62797
62944
  const transformCtx = {
62798
62945
  sessionId: this.sessionId,
62799
62946
  formatter: this.platform.getFormatter(),
@@ -62847,7 +62994,7 @@ class MessageManager {
62847
62994
  }
62848
62995
  }
62849
62996
  async executeOperation(op) {
62850
- const logger = log11.forSession(this.sessionId);
62997
+ const logger = log12.forSession(this.sessionId);
62851
62998
  const ctx = this.getExecutorContext();
62852
62999
  try {
62853
63000
  if (isContentOp(op)) {
@@ -62915,7 +63062,7 @@ class MessageManager {
62915
63062
  threadId: this.threadId,
62916
63063
  platform: this.platform,
62917
63064
  formatter: this.platform.getFormatter(),
62918
- logger: log11.forSession(this.sessionId),
63065
+ logger: log12.forSession(this.sessionId),
62919
63066
  postTracker: this.postTracker,
62920
63067
  contentBreaker: this.contentBreaker,
62921
63068
  threadLogger: this.session.threadLogger,
@@ -63135,13 +63282,13 @@ class MessageManager {
63135
63282
  return this.systemExecutor.postSuccess(message, this.getExecutorContext());
63136
63283
  }
63137
63284
  async prepareForUserMessage() {
63138
- const logger = log11.forSession(this.sessionId);
63285
+ const logger = log12.forSession(this.sessionId);
63139
63286
  logger.debug("Preparing for new user message");
63140
63287
  await this.closeCurrentPost();
63141
63288
  await this.bumpTaskList();
63142
63289
  }
63143
63290
  async handleUserMessage(message, files, username, displayName) {
63144
- const logger = log11.forSession(this.sessionId);
63291
+ const logger = log12.forSession(this.sessionId);
63145
63292
  if (!this.session.claude.isRunning()) {
63146
63293
  logger.debug("Claude not running, ignoring user message");
63147
63294
  return false;
@@ -63184,7 +63331,7 @@ class MessageManager {
63184
63331
  ];
63185
63332
  }
63186
63333
  async handleReaction(postId, emoji, user, action) {
63187
- const logger = log11.forSession(this.sessionId);
63334
+ const logger = log12.forSession(this.sessionId);
63188
63335
  const ctx = this.getExecutorContext();
63189
63336
  logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
63190
63337
  for (const { name, executor } of this.reactionDispatchList()) {
@@ -63282,7 +63429,7 @@ class MessageManager {
63282
63429
  }
63283
63430
  // src/operations/sticky-message/handler.ts
63284
63431
  init_logger();
63285
- var log12 = createLogger("sticky");
63432
+ var log13 = createLogger("sticky");
63286
63433
  var botStartedAt = new Date;
63287
63434
  function getPendingPrompts(session) {
63288
63435
  const prompts = [];
@@ -63357,21 +63504,21 @@ function initialize(store) {
63357
63504
  stickyPostIds.set(platformId, postId);
63358
63505
  }
63359
63506
  if (persistedIds.size > 0) {
63360
- log12.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
63507
+ log13.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
63361
63508
  }
63362
63509
  }
63363
63510
  function setPlatformPaused(platformId, paused) {
63364
63511
  if (paused) {
63365
63512
  pausedPlatforms.set(platformId, true);
63366
- log12.debug(`Platform ${platformId} marked as paused`);
63513
+ log13.debug(`Platform ${platformId} marked as paused`);
63367
63514
  } else {
63368
63515
  pausedPlatforms.delete(platformId);
63369
- log12.debug(`Platform ${platformId} marked as active`);
63516
+ log13.debug(`Platform ${platformId} marked as active`);
63370
63517
  }
63371
63518
  }
63372
63519
  function setShuttingDown(shuttingDown) {
63373
63520
  isShuttingDown = shuttingDown;
63374
- log12.debug(`Bot shutdown state: ${shuttingDown}`);
63521
+ log13.debug(`Bot shutdown state: ${shuttingDown}`);
63375
63522
  }
63376
63523
  function getTaskContent(session) {
63377
63524
  const taskState = session.messageManager?.getTaskListState();
@@ -63704,12 +63851,12 @@ async function validateLastMessageIds(platform, sessions) {
63704
63851
  try {
63705
63852
  const post2 = await platform.getPost(lastMessageId);
63706
63853
  if (!post2) {
63707
- log12.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
63854
+ log13.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
63708
63855
  session.lastMessageId = undefined;
63709
63856
  session.lastMessageTs = undefined;
63710
63857
  }
63711
63858
  } catch (err) {
63712
- log12.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
63859
+ log13.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
63713
63860
  session.lastMessageId = undefined;
63714
63861
  session.lastMessageTs = undefined;
63715
63862
  }
@@ -63726,7 +63873,7 @@ async function updateStickyMessageImpl(platform, sessions, config) {
63726
63873
  hiddenCleanupDone.add(platform.platformId);
63727
63874
  const existing = stickyPostIds.get(platform.platformId);
63728
63875
  if (existing) {
63729
- log12.info(`sticky[${platform.platformId}] hidden mode: removing leftover ${formatShortId(existing)}`);
63876
+ log13.info(`sticky[${platform.platformId}] hidden mode: removing leftover ${formatShortId(existing)}`);
63730
63877
  try {
63731
63878
  await platform.unpinPost(existing);
63732
63879
  } catch {}
@@ -63746,63 +63893,63 @@ async function updateStickyMessageImpl(platform, sessions, config) {
63746
63893
  return;
63747
63894
  }
63748
63895
  const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
63749
- log12.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
63896
+ log13.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
63750
63897
  for (const s of platformSessions) {
63751
- log12.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
63898
+ log13.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
63752
63899
  }
63753
63900
  await validateLastMessageIds(platform, platformSessions);
63754
63901
  const formatter = platform.getFormatter();
63755
63902
  const content = await buildStickyMessage(sessions, platform.platformId, config, formatter, (threadId) => platform.getThreadLink(threadId));
63756
63903
  const existingPostId = stickyPostIds.get(platform.platformId);
63757
63904
  const shouldBump = needsBump.get(platform.platformId) ?? false;
63758
- log12.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
63905
+ log13.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
63759
63906
  try {
63760
63907
  if (existingPostId && !shouldBump) {
63761
- log12.debug(`Updating existing post in place...`);
63908
+ log13.debug(`Updating existing post in place...`);
63762
63909
  try {
63763
63910
  await platform.updatePost(existingPostId, content);
63764
63911
  try {
63765
63912
  await platform.pinPost(existingPostId);
63766
- log12.debug(`Re-pinned post`);
63913
+ log13.debug(`Re-pinned post`);
63767
63914
  } catch (pinErr) {
63768
- log12.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
63915
+ log13.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
63769
63916
  }
63770
- log12.debug(`Updated successfully`);
63917
+ log13.debug(`Updated successfully`);
63771
63918
  return;
63772
63919
  } catch (err) {
63773
- log12.debug(`Update failed, will create new: ${err}`);
63920
+ log13.debug(`Update failed, will create new: ${err}`);
63774
63921
  }
63775
63922
  }
63776
63923
  needsBump.set(platform.platformId, false);
63777
63924
  if (existingPostId) {
63778
- log12.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
63925
+ log13.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
63779
63926
  try {
63780
63927
  await platform.unpinPost(existingPostId);
63781
- log12.debug(`Unpinned successfully`);
63928
+ log13.debug(`Unpinned successfully`);
63782
63929
  } catch (err) {
63783
- log12.debug(`Unpin failed (probably already unpinned): ${err}`);
63930
+ log13.debug(`Unpin failed (probably already unpinned): ${err}`);
63784
63931
  }
63785
63932
  try {
63786
63933
  await platform.deletePost(existingPostId);
63787
- log12.debug(`Deleted successfully`);
63934
+ log13.debug(`Deleted successfully`);
63788
63935
  } catch (err) {
63789
- log12.debug(`Delete failed (probably already deleted): ${err}`);
63936
+ log13.debug(`Delete failed (probably already deleted): ${err}`);
63790
63937
  }
63791
63938
  stickyPostIds.delete(platform.platformId);
63792
63939
  }
63793
- log12.debug(`Creating new post...`);
63940
+ log13.debug(`Creating new post...`);
63794
63941
  const post2 = await platform.createPost(content);
63795
63942
  stickyPostIds.set(platform.platformId, post2.id);
63796
63943
  try {
63797
63944
  await platform.pinPost(post2.id);
63798
- log12.debug(`Pinned post successfully`);
63945
+ log13.debug(`Pinned post successfully`);
63799
63946
  } catch (err) {
63800
- log12.debug(`Failed to pin post: ${err}`);
63947
+ log13.debug(`Failed to pin post: ${err}`);
63801
63948
  }
63802
63949
  if (sessionStore) {
63803
63950
  sessionStore.saveStickyPostId(platform.platformId, post2.id);
63804
63951
  }
63805
- log12.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
63952
+ log13.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
63806
63953
  const excludePostIds = new Set;
63807
63954
  if (sessionStore) {
63808
63955
  for (const session of sessionStore.load().values()) {
@@ -63818,10 +63965,10 @@ async function updateStickyMessageImpl(platform, sessions, config) {
63818
63965
  }
63819
63966
  const botUser = await platform.getBotUser();
63820
63967
  cleanupOldStickyMessages(platform, botUser.id, false, excludePostIds).catch((err) => {
63821
- log12.debug(`Background cleanup failed: ${err}`);
63968
+ log13.debug(`Background cleanup failed: ${err}`);
63822
63969
  });
63823
63970
  } catch (err) {
63824
- log12.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
63971
+ log13.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
63825
63972
  }
63826
63973
  }
63827
63974
  async function updateAllStickyMessages(platforms, sessions, config, overheadByPlatform) {
@@ -63849,7 +63996,7 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
63849
63996
  if (!forceRun) {
63850
63997
  const lastRun = lastCleanupTime.get(platformId) || 0;
63851
63998
  if (now - lastRun < CLEANUP_THROTTLE_MS) {
63852
- log12.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
63999
+ log13.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
63853
64000
  return;
63854
64001
  }
63855
64002
  }
@@ -63859,31 +64006,31 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
63859
64006
  const pinnedPostIds = await platform.getPinnedPosts();
63860
64007
  const recentPinnedIds = pinnedPostIds.filter((id) => id !== currentStickyId && !excludePostIds?.has(id) && isRecentPost(id));
63861
64008
  if (recentPinnedIds.length === 0) {
63862
- log12.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
64009
+ log13.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
63863
64010
  return;
63864
64011
  }
63865
- log12.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
64012
+ log13.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
63866
64013
  for (const postId of recentPinnedIds) {
63867
64014
  try {
63868
64015
  const post2 = await platform.getPost(postId);
63869
64016
  if (!post2)
63870
64017
  continue;
63871
64018
  if (post2.userId === botUserId) {
63872
- log12.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
64019
+ log13.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
63873
64020
  try {
63874
64021
  await platform.unpinPost(postId);
63875
64022
  await platform.deletePost(postId);
63876
- log12.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
64023
+ log13.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
63877
64024
  } catch (err) {
63878
- log12.debug(`Failed to cleanup ${postId}: ${err}`);
64025
+ log13.debug(`Failed to cleanup ${postId}: ${err}`);
63879
64026
  }
63880
64027
  }
63881
64028
  } catch (err) {
63882
- log12.debug(`Could not check post ${postId}: ${err}`);
64029
+ log13.debug(`Could not check post ${postId}: ${err}`);
63883
64030
  }
63884
64031
  }
63885
64032
  } catch (err) {
63886
- log12.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
64033
+ log13.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
63887
64034
  }
63888
64035
  }
63889
64036
  // src/memory/store.ts
@@ -63891,18 +64038,18 @@ init_logger();
63891
64038
  init_worktree();
63892
64039
  import { createHash } from "crypto";
63893
64040
  import {
63894
- chmodSync as chmodSync3,
64041
+ chmodSync as chmodSync4,
63895
64042
  existsSync as existsSync7,
63896
- mkdirSync as mkdirSync3,
64043
+ mkdirSync as mkdirSync4,
63897
64044
  readFileSync as readFileSync6,
63898
64045
  renameSync,
63899
64046
  realpathSync,
63900
64047
  writeFileSync as writeFileSync5
63901
64048
  } from "fs";
63902
- import { homedir as homedir4 } from "os";
63903
- import { basename as basename3, dirname as dirname7, join as join8, sep as sep2 } from "path";
63904
- var log13 = createLogger("memory");
63905
- var DEFAULT_ROOT = join8(homedir4(), ".config", "claude-threads", "memory");
64049
+ import { homedir as homedir5 } from "os";
64050
+ import { basename as basename3, dirname as dirname7, join as join9, sep as sep2 } from "path";
64051
+ var log14 = createLogger("memory");
64052
+ var DEFAULT_ROOT = join9(homedir5(), ".config", "claude-threads", "memory");
63906
64053
  var CHANNEL_BLOCK_MAX_LINES = 200;
63907
64054
  var CHANNEL_BLOCK_MAX_BYTES = 25 * 1024;
63908
64055
  var CHANNEL_FILE_MAX_ENTRIES = 400;
@@ -63965,10 +64112,10 @@ class MemoryStore {
63965
64112
  return this.root;
63966
64113
  }
63967
64114
  channelMemoryPath(platformId) {
63968
- return join8(this.root, platformSegment(platformId), "channel", "MEMORY.md");
64115
+ return join9(this.root, platformSegment(platformId), "channel", "MEMORY.md");
63969
64116
  }
63970
64117
  repoMemoryDir(platformId, repoKey) {
63971
- const dir = join8(this.root, platformSegment(platformId), "repos", repoKey);
64118
+ const dir = join9(this.root, platformSegment(platformId), "repos", repoKey);
63972
64119
  this.ensureDir(dir);
63973
64120
  return dir;
63974
64121
  }
@@ -64015,7 +64162,7 @@ class MemoryStore {
64015
64162
  if (result.added.length > 0) {
64016
64163
  this.enforceFileCap(lines);
64017
64164
  this.writeLines(platformId, lines);
64018
- log13.debug(`Channel memory for ${platformId}: +${result.added.length} entries` + (result.duplicates.length ? ` (${result.duplicates.length} duplicates skipped)` : ""));
64165
+ log14.debug(`Channel memory for ${platformId}: +${result.added.length} entries` + (result.duplicates.length ? ` (${result.duplicates.length} duplicates skipped)` : ""));
64019
64166
  }
64020
64167
  return result;
64021
64168
  });
@@ -64054,14 +64201,14 @@ class MemoryStore {
64054
64201
  }
64055
64202
  lines.splice(target.lineIndex, 1);
64056
64203
  this.writeLines(platformId, lines);
64057
- log13.debug(`Channel memory for ${platformId}: removed one entry`);
64204
+ log14.debug(`Channel memory for ${platformId}: removed one entry`);
64058
64205
  return { ok: true, removed: target.entry };
64059
64206
  });
64060
64207
  }
64061
64208
  clearChannel(platformId) {
64062
64209
  return this.runExclusive(platformId, () => {
64063
64210
  this.writeLines(platformId, []);
64064
- log13.debug(`Channel memory for ${platformId}: cleared`);
64211
+ log14.debug(`Channel memory for ${platformId}: cleared`);
64065
64212
  });
64066
64213
  }
64067
64214
  buildChannelMemoryBlock(platformId) {
@@ -64069,7 +64216,7 @@ class MemoryStore {
64069
64216
  try {
64070
64217
  lines = this.loadLines(platformId);
64071
64218
  } catch (err) {
64072
- log13.warn(`Failed to read channel memory for ${platformId}: ${err.message}`);
64219
+ log14.warn(`Failed to read channel memory for ${platformId}: ${err.message}`);
64073
64220
  return null;
64074
64221
  }
64075
64222
  if (lines.length === 0)
@@ -64144,11 +64291,11 @@ _(older entries omitted — \`!memory\` shows all)_` : rendered;
64144
64291
  const tempFile = `${file}.tmp`;
64145
64292
  writeFileSync5(tempFile, content, { encoding: "utf-8", mode: 384 });
64146
64293
  renameSync(tempFile, file);
64147
- chmodSync3(file, 384);
64294
+ chmodSync4(file, 384);
64148
64295
  }
64149
64296
  ensureDir(dir) {
64150
64297
  if (!existsSync7(dir)) {
64151
- mkdirSync3(dir, { recursive: true, mode: 448 });
64298
+ mkdirSync4(dir, { recursive: true, mode: 448 });
64152
64299
  }
64153
64300
  }
64154
64301
  }
@@ -64159,7 +64306,7 @@ async function resolveSessionMemory(memoryStore, memoryConfig, platformId, worki
64159
64306
  const repoKey = await resolveRepoKey(workingDir, worktreeRepoRoot);
64160
64307
  return { autoMemoryDir: memoryStore.repoMemoryDir(platformId, repoKey) };
64161
64308
  } catch (err) {
64162
- log13.warn(`Failed to resolve repo memory dir for ${platformId}: ${err.message}`);
64309
+ log14.warn(`Failed to resolve repo memory dir for ${platformId}: ${err.message}`);
64163
64310
  return null;
64164
64311
  }
64165
64312
  }
@@ -64167,7 +64314,7 @@ async function resolveSessionMemory(memoryStore, memoryConfig, platformId, worki
64167
64314
  // src/claude/quick-query.ts
64168
64315
  init_spawn();
64169
64316
  init_logger();
64170
- var log14 = createLogger("query");
64317
+ var log15 = createLogger("query");
64171
64318
  async function quickQuery(options) {
64172
64319
  const {
64173
64320
  prompt,
@@ -64182,7 +64329,7 @@ async function quickQuery(options) {
64182
64329
  if (systemPrompt) {
64183
64330
  args.push("--system-prompt", systemPrompt);
64184
64331
  }
64185
- log14.debug(`Quick query: model=${model}, timeout=${timeout2}ms, prompt="${prompt.substring(0, 50)}..."`);
64332
+ log15.debug(`Quick query: model=${model}, timeout=${timeout2}ms, prompt="${prompt.substring(0, 50)}..."`);
64186
64333
  return new Promise((resolve6) => {
64187
64334
  let stdout = "";
64188
64335
  let stderr = "";
@@ -64196,7 +64343,7 @@ async function quickQuery(options) {
64196
64343
  if (!resolved) {
64197
64344
  resolved = true;
64198
64345
  proc.kill("SIGTERM");
64199
- log14.debug(`Quick query timed out after ${timeout2}ms`);
64346
+ log15.debug(`Quick query timed out after ${timeout2}ms`);
64200
64347
  resolve6({
64201
64348
  success: false,
64202
64349
  error: "timeout",
@@ -64214,7 +64361,7 @@ async function quickQuery(options) {
64214
64361
  if (!resolved) {
64215
64362
  resolved = true;
64216
64363
  clearTimeout(timeoutId);
64217
- log14.debug(`Quick query error: ${err.message}`);
64364
+ log15.debug(`Quick query error: ${err.message}`);
64218
64365
  resolve6({
64219
64366
  success: false,
64220
64367
  error: err.message,
@@ -64228,14 +64375,14 @@ async function quickQuery(options) {
64228
64375
  clearTimeout(timeoutId);
64229
64376
  const durationMs = Date.now() - startTime;
64230
64377
  if (code === 0 && stdout.trim()) {
64231
- log14.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
64378
+ log15.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
64232
64379
  resolve6({
64233
64380
  success: true,
64234
64381
  response: stdout.trim(),
64235
64382
  durationMs
64236
64383
  });
64237
64384
  } else {
64238
- log14.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
64385
+ log15.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
64239
64386
  resolve6({
64240
64387
  success: false,
64241
64388
  error: stderr || `exit code ${code}`,
@@ -64254,7 +64401,7 @@ init_logger();
64254
64401
  import { exec as exec3 } from "child_process";
64255
64402
  import { promisify as promisify3 } from "util";
64256
64403
  var execAsync2 = promisify3(exec3);
64257
- var log15 = createLogger("branch");
64404
+ var log16 = createLogger("branch");
64258
64405
  var SUGGESTION_TIMEOUT = 15000;
64259
64406
  var MAX_SUGGESTIONS = 3;
64260
64407
  async function getCurrentBranch3(workingDir) {
@@ -64303,7 +64450,7 @@ function parseBranchSuggestions(response) {
64303
64450
  return lines.slice(0, MAX_SUGGESTIONS);
64304
64451
  }
64305
64452
  async function suggestBranchNames(workingDir, userMessage) {
64306
- log15.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
64453
+ log16.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
64307
64454
  try {
64308
64455
  const [currentBranch, recentCommits] = await Promise.all([
64309
64456
  getCurrentBranch3(workingDir),
@@ -64317,14 +64464,14 @@ async function suggestBranchNames(workingDir, userMessage) {
64317
64464
  workingDir
64318
64465
  });
64319
64466
  if (!result.success || !result.response) {
64320
- log15.debug(`Branch suggestion failed: ${result.error || "no response"}`);
64467
+ log16.debug(`Branch suggestion failed: ${result.error || "no response"}`);
64321
64468
  return [];
64322
64469
  }
64323
64470
  const suggestions = parseBranchSuggestions(result.response);
64324
- log15.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
64471
+ log16.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
64325
64472
  return suggestions;
64326
64473
  } catch (err) {
64327
- log15.debug(`Branch suggestion error: ${err}`);
64474
+ log16.debug(`Branch suggestion error: ${err}`);
64328
64475
  return [];
64329
64476
  }
64330
64477
  }
@@ -64333,8 +64480,8 @@ async function suggestBranchNames(workingDir, userMessage) {
64333
64480
  init_worktree();
64334
64481
  import { randomUUID as randomUUID3 } from "crypto";
64335
64482
  init_logger();
64336
- var log16 = createLogger("worktree");
64337
- var sessionLog2 = createSessionLog(log16);
64483
+ var log17 = createLogger("worktree");
64484
+ var sessionLog2 = createSessionLog(log17);
64338
64485
  function parseWorktreeError(error) {
64339
64486
  const message = error instanceof Error ? error.message : String(error);
64340
64487
  const lowerMessage = message.toLowerCase();
@@ -64860,6 +65007,14 @@ async function removeWorktreeCommand(session, branchOrPath, username) {
64860
65007
  }
64861
65008
  try {
64862
65009
  await removeWorktree(repoRoot, target.path);
65010
+ auditLog(session.platformId, {
65011
+ threadId: session.threadId,
65012
+ sessionId: session.sessionId,
65013
+ actor: username,
65014
+ kind: "command",
65015
+ tool: "worktree remove",
65016
+ detail: target.path
65017
+ });
64863
65018
  const shortPath = shortenPath(target.path, undefined, { path: target.path, branch: target.branch });
64864
65019
  await post(session, "success", `Removed worktree \`${target.branch}\` at \`${shortPath}\``);
64865
65020
  sessionLog2(session).info(`\uD83D\uDDD1️ Removed worktree ${target.branch} at ${shortPath}`);
@@ -64916,8 +65071,8 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
64916
65071
  }
64917
65072
  // src/operations/events/handler.ts
64918
65073
  init_logger();
64919
- var log17 = createLogger("events");
64920
- var sessionLog3 = createSessionLog(log17);
65074
+ var log18 = createLogger("events");
65075
+ var sessionLog3 = createSessionLog(log18);
64921
65076
  function detectAndExecuteClaudeCommands(text, session, ctx) {
64922
65077
  const parsed = parseClaudeCommand(text);
64923
65078
  if (parsed && isClaudeAllowedCommand(parsed.command)) {
@@ -64980,6 +65135,34 @@ function isSidechainEvent(event) {
64980
65135
  }
64981
65136
  function handleEventPreProcessing(session, event, ctx) {
64982
65137
  session.threadLogger?.logEvent(event);
65138
+ try {
65139
+ if (isAuditEnabled(session.platformId)) {
65140
+ const subagent = isSidechainEvent(event) || undefined;
65141
+ const record = (name, input) => auditLog(session.platformId, {
65142
+ threadId: session.threadId,
65143
+ sessionId: session.sessionId,
65144
+ actor: session.lastActorUsername ?? session.startedBy,
65145
+ kind: "tool_use",
65146
+ tool: name,
65147
+ detail: auditDetailForTool(name, input),
65148
+ subagent
65149
+ });
65150
+ if (event.type === "assistant") {
65151
+ const msg = event.message;
65152
+ if (Array.isArray(msg?.content)) {
65153
+ for (const block of msg.content) {
65154
+ if ((block.type === "tool_use" || block.type === "server_tool_use") && block.name) {
65155
+ record(block.name, block.input);
65156
+ }
65157
+ }
65158
+ }
65159
+ } else if (event.type === "tool_use") {
65160
+ const tool = event.tool_use;
65161
+ if (tool?.name)
65162
+ record(tool.name, tool.input);
65163
+ }
65164
+ }
65165
+ } catch {}
64983
65166
  resetSessionActivity(session);
64984
65167
  if (!session.lifecycle.hasClaudeResponded && (event.type === "assistant" || event.type === "tool_use")) {
64985
65168
  markClaudeResponded(session);
@@ -65232,7 +65415,7 @@ function updateUsageFromStatusLine(session) {
65232
65415
  }
65233
65416
  // src/operations/monitor/handler.ts
65234
65417
  init_logger();
65235
- var log18 = createLogger("monitor");
65418
+ var log19 = createLogger("monitor");
65236
65419
  var DEFAULT_INTERVAL_MS = 60 * 1000;
65237
65420
 
65238
65421
  class SessionMonitor {
@@ -65254,14 +65437,14 @@ class SessionMonitor {
65254
65437
  }
65255
65438
  start() {
65256
65439
  if (this.isRunning) {
65257
- log18.debug("Session monitor already running");
65440
+ log19.debug("Session monitor already running");
65258
65441
  return;
65259
65442
  }
65260
65443
  this.isRunning = true;
65261
- log18.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
65444
+ log19.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
65262
65445
  this.timer = setInterval(() => {
65263
65446
  this.runCheck().catch((err) => {
65264
- log18.error(`Error during session monitoring: ${err}`);
65447
+ log19.error(`Error during session monitoring: ${err}`);
65265
65448
  });
65266
65449
  }, this.intervalMs);
65267
65450
  }
@@ -65271,7 +65454,7 @@ class SessionMonitor {
65271
65454
  this.timer = null;
65272
65455
  }
65273
65456
  this.isRunning = false;
65274
- log18.debug("Session monitor stopped");
65457
+ log19.debug("Session monitor stopped");
65275
65458
  }
65276
65459
  async runCheck() {
65277
65460
  await cleanupIdleSessions(this.sessionTimeoutMs, this.sessionWarningMs, this.getContext());
@@ -65291,8 +65474,8 @@ function createSessionContext(config, state, ops) {
65291
65474
  // src/operations/context-prompt/handler.ts
65292
65475
  init_emoji();
65293
65476
  init_logger();
65294
- var log19 = createLogger("context");
65295
- var sessionLog4 = createSessionLog(log19);
65477
+ var log20 = createLogger("context");
65478
+ var sessionLog4 = createSessionLog(log20);
65296
65479
  var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
65297
65480
  var CONTEXT_OPTIONS = [3, 5, 10];
65298
65481
  var contextPromptTimeouts = new Map;
@@ -65522,7 +65705,7 @@ async function offerContextPrompt(session, queuedPrompt, queuedFiles, ctx, exclu
65522
65705
  }
65523
65706
  // src/operations/suggestions/tag.ts
65524
65707
  init_logger();
65525
- var log20 = createLogger("tags");
65708
+ var log21 = createLogger("tags");
65526
65709
  var SUGGESTION_TIMEOUT2 = 15000;
65527
65710
  var MAX_TAGS = 3;
65528
65711
  var VALID_TAGS = [
@@ -65554,7 +65737,7 @@ function parseTags(response) {
65554
65737
  return [...new Set(tags)].slice(0, MAX_TAGS);
65555
65738
  }
65556
65739
  async function suggestSessionTags(userMessage) {
65557
- log20.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
65740
+ log21.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
65558
65741
  try {
65559
65742
  const result = await quickQuery({
65560
65743
  prompt: buildTagPrompt(userMessage),
@@ -65562,20 +65745,20 @@ async function suggestSessionTags(userMessage) {
65562
65745
  timeout: SUGGESTION_TIMEOUT2
65563
65746
  });
65564
65747
  if (!result.success || !result.response) {
65565
- log20.debug(`Tag suggestion failed: ${result.error || "no response"}`);
65748
+ log21.debug(`Tag suggestion failed: ${result.error || "no response"}`);
65566
65749
  return [];
65567
65750
  }
65568
65751
  const tags = parseTags(result.response);
65569
- log20.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
65752
+ log21.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
65570
65753
  return tags;
65571
65754
  } catch (err) {
65572
- log20.debug(`Tag suggestion error: ${err}`);
65755
+ log21.debug(`Tag suggestion error: ${err}`);
65573
65756
  return [];
65574
65757
  }
65575
65758
  }
65576
65759
  // src/operations/suggestions/title.ts
65577
65760
  init_logger();
65578
- var log21 = createLogger("title");
65761
+ var log22 = createLogger("title");
65579
65762
  var SUGGESTION_TIMEOUT3 = 15000;
65580
65763
  var MIN_TITLE_LENGTH = 3;
65581
65764
  var MAX_TITLE_LENGTH = 50;
@@ -65639,32 +65822,32 @@ function parseMetadata(response) {
65639
65822
  const titleMatch = response.match(/TITLE:\s*(.+)/i);
65640
65823
  const descMatch = response.match(/DESC:\s*(.+)/i);
65641
65824
  if (!titleMatch || !descMatch) {
65642
- log21.debug("Failed to parse title/description from response");
65825
+ log22.debug("Failed to parse title/description from response");
65643
65826
  return null;
65644
65827
  }
65645
65828
  let title = titleMatch[1].trim();
65646
65829
  let description = descMatch[1].trim();
65647
65830
  if (title.length < MIN_TITLE_LENGTH) {
65648
- log21.debug(`Title too short: ${title.length} chars`);
65831
+ log22.debug(`Title too short: ${title.length} chars`);
65649
65832
  return null;
65650
65833
  }
65651
65834
  if (title.length > MAX_TITLE_LENGTH) {
65652
- log21.debug(`Title too long (${title.length} chars), truncating`);
65835
+ log22.debug(`Title too long (${title.length} chars), truncating`);
65653
65836
  title = truncateAtWord(title, MAX_TITLE_LENGTH);
65654
65837
  }
65655
65838
  if (description.length < MIN_DESC_LENGTH) {
65656
- log21.debug(`Description too short: ${description.length} chars`);
65839
+ log22.debug(`Description too short: ${description.length} chars`);
65657
65840
  return null;
65658
65841
  }
65659
65842
  if (description.length > MAX_DESC_LENGTH) {
65660
- log21.debug(`Description too long (${description.length} chars), truncating`);
65843
+ log22.debug(`Description too long (${description.length} chars), truncating`);
65661
65844
  description = truncateAtWord(description, MAX_DESC_LENGTH);
65662
65845
  }
65663
65846
  return { title, description };
65664
65847
  }
65665
65848
  async function suggestSessionMetadata(context) {
65666
65849
  const logContext = typeof context === "string" ? context.substring(0, 50) : context.originalTask.substring(0, 50);
65667
- log21.debug(`Suggesting title for: "${logContext}..."`);
65850
+ log22.debug(`Suggesting title for: "${logContext}..."`);
65668
65851
  try {
65669
65852
  const result = await quickQuery({
65670
65853
  prompt: buildTitlePrompt(context),
@@ -65672,27 +65855,27 @@ async function suggestSessionMetadata(context) {
65672
65855
  timeout: SUGGESTION_TIMEOUT3
65673
65856
  });
65674
65857
  if (!result.success || !result.response) {
65675
- log21.debug(`Title suggestion failed: ${result.error || "no response"}`);
65858
+ log22.debug(`Title suggestion failed: ${result.error || "no response"}`);
65676
65859
  return null;
65677
65860
  }
65678
65861
  const metadata = parseMetadata(result.response);
65679
65862
  if (metadata) {
65680
- log21.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
65863
+ log22.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
65681
65864
  }
65682
65865
  return metadata;
65683
65866
  } catch (err) {
65684
- log21.debug(`Title suggestion error: ${err}`);
65867
+ log22.debug(`Title suggestion error: ${err}`);
65685
65868
  return null;
65686
65869
  }
65687
65870
  }
65688
65871
  // src/persistence/github-emails-store.ts
65689
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync6, renameSync as renameSync2, chmodSync as chmodSync4 } from "fs";
65690
- import { homedir as homedir5 } from "os";
65691
- import { join as join9 } from "path";
65872
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync6, renameSync as renameSync2, chmodSync as chmodSync5 } from "fs";
65873
+ import { homedir as homedir6 } from "os";
65874
+ import { join as join10 } from "path";
65692
65875
  init_logger();
65693
- var log22 = createLogger("gh-emails");
65694
- var DEFAULT_CONFIG_DIR = join9(homedir5(), ".config", "claude-threads");
65695
- var DEFAULT_FILE = join9(DEFAULT_CONFIG_DIR, "github-emails.yaml");
65876
+ var log23 = createLogger("gh-emails");
65877
+ var DEFAULT_CONFIG_DIR = join10(homedir6(), ".config", "claude-threads");
65878
+ var DEFAULT_FILE = join10(DEFAULT_CONFIG_DIR, "github-emails.yaml");
65696
65879
  var NOREPLY_REGEX = /^\d+\+[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})@users\.noreply\.github\.com$/;
65697
65880
  var STORE_VERSION = 1;
65698
65881
  function isValidGitHubNoreplyEmail(s) {
@@ -65707,13 +65890,13 @@ class GitHubEmailsStore {
65707
65890
  const effective = filePath ?? envPath;
65708
65891
  if (effective) {
65709
65892
  this.file = effective;
65710
- this.configDir = join9(effective, "..");
65893
+ this.configDir = join10(effective, "..");
65711
65894
  } else {
65712
65895
  this.file = DEFAULT_FILE;
65713
65896
  this.configDir = DEFAULT_CONFIG_DIR;
65714
65897
  }
65715
65898
  if (!existsSync8(this.configDir)) {
65716
- mkdirSync4(this.configDir, { recursive: true });
65899
+ mkdirSync5(this.configDir, { recursive: true });
65717
65900
  }
65718
65901
  }
65719
65902
  get(platformId, username) {
@@ -65730,7 +65913,7 @@ class GitHubEmailsStore {
65730
65913
  }
65731
65914
  data.emails[platformId][username] = email;
65732
65915
  this.writeAtomic(data);
65733
- log22.debug(`Stored GitHub email for ${platformId}/${username}`);
65916
+ log23.debug(`Stored GitHub email for ${platformId}/${username}`);
65734
65917
  }
65735
65918
  delete(platformId, username) {
65736
65919
  const data = this.loadRaw();
@@ -65742,7 +65925,7 @@ class GitHubEmailsStore {
65742
65925
  delete data.emails[platformId];
65743
65926
  }
65744
65927
  this.writeAtomic(data);
65745
- log22.debug(`Removed GitHub email for ${platformId}/${username}`);
65928
+ log23.debug(`Removed GitHub email for ${platformId}/${username}`);
65746
65929
  return true;
65747
65930
  }
65748
65931
  loadRaw() {
@@ -65758,7 +65941,7 @@ class GitHubEmailsStore {
65758
65941
  const emails = parsed.emails && typeof parsed.emails === "object" ? parsed.emails : {};
65759
65942
  return { version: parsed.version ?? STORE_VERSION, emails };
65760
65943
  } catch (err) {
65761
- log22.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
65944
+ log23.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
65762
65945
  return { version: STORE_VERSION, emails: {} };
65763
65946
  }
65764
65947
  }
@@ -65767,19 +65950,19 @@ class GitHubEmailsStore {
65767
65950
  const yamlText = yaml.dump(data, { sortKeys: true, lineWidth: -1 });
65768
65951
  writeFileSync6(tempFile, yamlText, { encoding: "utf-8", mode: 384 });
65769
65952
  renameSync2(tempFile, this.file);
65770
- chmodSync4(this.file, 384);
65953
+ chmodSync5(this.file, 384);
65771
65954
  }
65772
65955
  }
65773
65956
 
65774
65957
  // src/persistence/routines-store.ts
65775
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync8 } from "fs";
65776
- import { homedir as homedir6 } from "os";
65777
- import { join as join10 } from "path";
65958
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8 } from "fs";
65959
+ import { homedir as homedir7 } from "os";
65960
+ import { join as join11 } from "path";
65778
65961
  import { randomUUID as randomUUID4 } from "crypto";
65779
65962
  init_logger();
65780
65963
 
65781
65964
  // src/persistence/atomic-file.ts
65782
- import { chmodSync as chmodSync5, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
65965
+ import { chmodSync as chmodSync6, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
65783
65966
 
65784
65967
  class SerialQueue {
65785
65968
  tail = Promise.resolve();
@@ -65795,13 +65978,13 @@ function writeFileAtomic(file, content) {
65795
65978
  const tempFile = `${file}.tmp`;
65796
65979
  writeFileSync7(tempFile, content, { encoding: "utf-8", mode: 384 });
65797
65980
  renameSync3(tempFile, file);
65798
- chmodSync5(file, 384);
65981
+ chmodSync6(file, 384);
65799
65982
  }
65800
65983
 
65801
65984
  // src/persistence/routines-store.ts
65802
- var log23 = createLogger("routines");
65803
- var DEFAULT_CONFIG_DIR2 = join10(homedir6(), ".config", "claude-threads");
65804
- var DEFAULT_FILE2 = join10(DEFAULT_CONFIG_DIR2, "routines.yaml");
65985
+ var log24 = createLogger("routines");
65986
+ var DEFAULT_CONFIG_DIR2 = join11(homedir7(), ".config", "claude-threads");
65987
+ var DEFAULT_FILE2 = join11(DEFAULT_CONFIG_DIR2, "routines.yaml");
65805
65988
  var STORE_VERSION2 = 1;
65806
65989
  var MAX_CONSECUTIVE_FAILURES = 3;
65807
65990
  var DEFAULT_MAX_ROUTINES = 10;
@@ -65860,13 +66043,13 @@ class RoutinesStore {
65860
66043
  const effective = filePath ?? process.env.CLAUDE_THREADS_ROUTINES_PATH;
65861
66044
  if (effective) {
65862
66045
  this.file = effective;
65863
- this.configDir = join10(effective, "..");
66046
+ this.configDir = join11(effective, "..");
65864
66047
  } else {
65865
66048
  this.file = DEFAULT_FILE2;
65866
66049
  this.configDir = DEFAULT_CONFIG_DIR2;
65867
66050
  }
65868
66051
  if (!existsSync9(this.configDir)) {
65869
- mkdirSync5(this.configDir, { recursive: true, mode: 448 });
66052
+ mkdirSync6(this.configDir, { recursive: true, mode: 448 });
65870
66053
  }
65871
66054
  }
65872
66055
  list(platformId) {
@@ -65900,7 +66083,7 @@ class RoutinesStore {
65900
66083
  };
65901
66084
  data.routines[platformId] = [...existing, full];
65902
66085
  this.writeAtomic(data);
65903
- log23.info(`Routine "${full.name}" created on ${platformId} by @${full.createdBy}`);
66086
+ log24.info(`Routine "${full.name}" created on ${platformId} by @${full.createdBy}`);
65904
66087
  return { ok: true, routine: full };
65905
66088
  });
65906
66089
  }
@@ -65927,7 +66110,7 @@ class RoutinesStore {
65927
66110
  if (routines.length === 0)
65928
66111
  delete data.routines[platformId];
65929
66112
  this.writeAtomic(data);
65930
- log23.info(`Routine "${removed.name}" removed from ${platformId}`);
66113
+ log24.info(`Routine "${removed.name}" removed from ${platformId}`);
65931
66114
  return removed;
65932
66115
  });
65933
66116
  }
@@ -65952,7 +66135,7 @@ class RoutinesStore {
65952
66135
  }
65953
66136
  return { version: parsed.version ?? STORE_VERSION2, routines };
65954
66137
  } catch (err) {
65955
- log23.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
66138
+ log24.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
65956
66139
  return { version: STORE_VERSION2, routines: {} };
65957
66140
  }
65958
66141
  }
@@ -65963,7 +66146,7 @@ class RoutinesStore {
65963
66146
 
65964
66147
  // src/routines/parser.ts
65965
66148
  init_logger();
65966
- var log24 = createLogger("routines");
66149
+ var log25 = createLogger("routines");
65967
66150
  var PARSE_TIMEOUT_MS = 15000;
65968
66151
  function hostTimezone() {
65969
66152
  return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
@@ -66032,7 +66215,7 @@ async function parseRoutineRequest(request, defaultTimezone = hostTimezone()) {
66032
66215
  timeout: PARSE_TIMEOUT_MS
66033
66216
  });
66034
66217
  if (!result.success || !result.response) {
66035
- log24.debug(`Routine parse quickQuery failed: ${result.error ?? "no response"}`);
66218
+ log25.debug(`Routine parse quickQuery failed: ${result.error ?? "no response"}`);
66036
66219
  return { ok: false, error: "could not reach the parsing model — try again in a moment" };
66037
66220
  }
66038
66221
  const raw = extractJsonObject(result.response);
@@ -66043,8 +66226,8 @@ async function parseRoutineRequest(request, defaultTimezone = hostTimezone()) {
66043
66226
  }
66044
66227
 
66045
66228
  // src/operations/commands/handler.ts
66046
- var log25 = createLogger("commands");
66047
- var sessionLog5 = createSessionLog(log25);
66229
+ var log26 = createLogger("commands");
66230
+ var sessionLog5 = createSessionLog(log26);
66048
66231
  function sessionAccountOption(session, ctx) {
66049
66232
  if (!session.claudeAccountId)
66050
66233
  return;
@@ -66088,6 +66271,16 @@ async function restartClaudeSession(session, cliOptions, ctx, actionName) {
66088
66271
  return false;
66089
66272
  }
66090
66273
  }
66274
+ function auditCommand(session, command, detail, username) {
66275
+ auditLog(session.platformId, {
66276
+ threadId: session.threadId,
66277
+ sessionId: session.sessionId,
66278
+ actor: username,
66279
+ kind: "command",
66280
+ tool: command,
66281
+ detail
66282
+ });
66283
+ }
66091
66284
  async function requireSessionOwner(session, username, action) {
66092
66285
  const formatter = session.platform.getFormatter();
66093
66286
  if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
@@ -66123,6 +66316,7 @@ function formatContextBar(percent) {
66123
66316
  }
66124
66317
  async function cancelSession(session, username, ctx) {
66125
66318
  sessionLog5(session).info(`\uD83D\uDED1 Cancelled by @${username}`);
66319
+ auditCommand(session, "stop", undefined, username);
66126
66320
  session.threadLogger?.logCommand("stop", undefined, username);
66127
66321
  transitionTo(session, "cancelling");
66128
66322
  const formatter = session.platform.getFormatter();
@@ -66161,6 +66355,14 @@ async function approvePendingPlan(session, username, ctx) {
66161
66355
  return;
66162
66356
  }
66163
66357
  const { postId } = pendingApproval;
66358
+ auditLog(session.platformId, {
66359
+ threadId: session.threadId,
66360
+ sessionId: session.sessionId,
66361
+ actor: username,
66362
+ kind: "plan_approval",
66363
+ approved: true,
66364
+ detail: "via !approve"
66365
+ });
66164
66366
  sessionLog5(session).info(`✅ Plan approved by @${username} via command`);
66165
66367
  const viaBridge = session.messageManager?.resolveBridgePlan(true) ?? false;
66166
66368
  const formatter = session.platform.getFormatter();
@@ -66231,6 +66433,7 @@ async function changeDirectory(session, newDir, username, ctx) {
66231
66433
  const worktreeContext = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
66232
66434
  const shortDir = shortenPath(absoluteDir, undefined, worktreeContext);
66233
66435
  sessionLog5(session).info(`\uD83D\uDCC2 Changing directory to ${shortDir}`);
66436
+ auditCommand(session, "cd", absoluteDir, username);
66234
66437
  session.threadLogger?.logCommand("cd", absoluteDir, username);
66235
66438
  const previousDir = session.workingDir;
66236
66439
  const workSummary = await generateWorkSummary(session);
@@ -66313,6 +66516,7 @@ async function inviteUser(session, invitedUser, invitedBy, ctx) {
66313
66516
  session.sessionAllowedUsers.add(invitedUser);
66314
66517
  await post(session, "success", `${formatter.formatUserMention(invitedUser)} can now participate in this session (invited by ${formatter.formatUserMention(invitedBy)})`);
66315
66518
  sessionLog5(session).info(`\uD83D\uDC4B @${invitedUser} invited by @${invitedBy}`);
66519
+ auditCommand(session, "invite", invitedUser, invitedBy);
66316
66520
  session.threadLogger?.logCommand("invite", invitedUser, invitedBy);
66317
66521
  await updateSessionHeader(session, ctx);
66318
66522
  ctx.ops.persistSession(session);
@@ -66343,6 +66547,7 @@ async function kickUser(session, kickedUser, kickedBy, ctx) {
66343
66547
  if (session.sessionAllowedUsers.delete(kickedUser)) {
66344
66548
  await post(session, "user", `${formatter.formatUserMention(kickedUser)} removed from this session by ${formatter.formatUserMention(kickedBy)}`);
66345
66549
  sessionLog5(session).info(`\uD83D\uDEAB @${kickedUser} kicked by @${kickedBy}`);
66550
+ auditCommand(session, "kick", kickedUser, kickedBy);
66346
66551
  session.threadLogger?.logCommand("kick", kickedUser, kickedBy);
66347
66552
  await updateSessionHeader(session, ctx);
66348
66553
  ctx.ops.persistSession(session);
@@ -66499,6 +66704,7 @@ async function forgetMemory(session, selector, username, ctx) {
66499
66704
  await ctx.state.memoryStore.clearChannel(session.platformId);
66500
66705
  await post(session, "success", `\uD83E\uDDE0 Channel memory cleared (${count} ${count === 1 ? "entry" : "entries"} removed). Running sessions keep their copy until their next restart.`);
66501
66706
  sessionLog5(session).info(`\uD83E\uDDE0 @${username} cleared channel memory (${count} entries)`);
66707
+ auditCommand(session, "memory", "forget all", username);
66502
66708
  session.threadLogger?.logCommand("memory", "forget all", username);
66503
66709
  return;
66504
66710
  }
@@ -66507,6 +66713,7 @@ async function forgetMemory(session, selector, username, ctx) {
66507
66713
  if (result.ok) {
66508
66714
  await post(session, "success", `\uD83E\uDDE0 Forgot: ${formatter.formatItalic(result.removed.text)}`);
66509
66715
  sessionLog5(session).info(`\uD83E\uDDE0 @${username} removed a channel memory entry`);
66716
+ auditCommand(session, "memory", "forget", username);
66510
66717
  session.threadLogger?.logCommand("memory", "forget", username);
66511
66718
  return;
66512
66719
  }
@@ -66646,6 +66853,7 @@ async function manageRoutines(session, args, username, ctx) {
66646
66853
  }
66647
66854
  }
66648
66855
  sessionLog5(session).info(`\uD83D\uDD58 @${username}: !routines ${lowered} ${indexArg} ("${routine.name}")`);
66856
+ auditCommand(session, "routines", `${lowered} ${indexArg}`, username);
66649
66857
  session.threadLogger?.logCommand("routines", `${lowered} ${indexArg}`, username);
66650
66858
  }
66651
66859
  async function setSessionPermissionMode(session, username, mode, ctx) {
@@ -66655,6 +66863,7 @@ async function setSessionPermissionMode(session, username, mode, ctx) {
66655
66863
  session.permissionModeOverride = mode;
66656
66864
  session.forceInteractivePermissions = mode === "default";
66657
66865
  sessionLog5(session).info(`\uD83D\uDD10 Setting permission mode to "${mode}"`);
66866
+ auditCommand(session, "permissions", mode, username);
66658
66867
  session.threadLogger?.logCommand("permissions", mode, username);
66659
66868
  const canResume = session.lifecycle.hasClaudeResponded;
66660
66869
  const memoryConfig = ctx.ops.getPlatformMemoryConfig(session.platformId);
@@ -66948,7 +67157,7 @@ init_worktree();
66948
67157
 
66949
67158
  // src/memory/distiller.ts
66950
67159
  init_logger();
66951
- var log26 = createLogger("memory");
67160
+ var log27 = createLogger("memory");
66952
67161
  var MIN_THREAD_MESSAGES = 4;
66953
67162
  var DISTILL_MESSAGE_LIMIT = 30;
66954
67163
  var MESSAGE_CHAR_CAP = 500;
@@ -66992,17 +67201,17 @@ function scheduleDistillation(session, ctx, reason) {
66992
67201
  return;
66993
67202
  }
66994
67203
  if (isDcmThreadId(session.threadId)) {
66995
- log26.debug(`Skipping distillation for DCM session ${session.platformId}:${session.threadId}`);
67204
+ log27.debug(`Skipping distillation for DCM session ${session.platformId}:${session.threadId}`);
66996
67205
  return;
66997
67206
  }
66998
67207
  const { platformId, threadId, platform } = session;
66999
67208
  const store = ctx.state.memoryStore;
67000
67209
  distillThread(store, platformId, threadId, platform).then((added) => {
67001
67210
  if (added > 0) {
67002
- log26.debug(`Distilled ${added} memory entries from ${platformId}:${threadId} (${reason})`);
67211
+ log27.debug(`Distilled ${added} memory entries from ${platformId}:${threadId} (${reason})`);
67003
67212
  }
67004
67213
  }).catch((err) => {
67005
- log26.debug(`Distillation failed for ${platformId}:${threadId}: ${err.message}`);
67214
+ log27.debug(`Distillation failed for ${platformId}:${threadId}: ${err.message}`);
67006
67215
  });
67007
67216
  }
67008
67217
  async function distillThread(store, platformId, threadId, platform) {
@@ -67029,8 +67238,8 @@ async function distillThread(store, platformId, threadId, platform) {
67029
67238
  }
67030
67239
 
67031
67240
  // src/session/lifecycle.ts
67032
- var log27 = createLogger("lifecycle");
67033
- var sessionLog6 = createSessionLog(log27);
67241
+ var log28 = createLogger("lifecycle");
67242
+ var sessionLog6 = createSessionLog(log28);
67034
67243
  function mutableSessions(ctx) {
67035
67244
  return ctx.state.sessions;
67036
67245
  }
@@ -67046,7 +67255,22 @@ function mutablePostIndex(ctx) {
67046
67255
  function cleanupSessionTimers(session) {
67047
67256
  clearAllTimers(session.timers);
67048
67257
  }
67049
- async function closeThreadLogger(session, action, details) {
67258
+ function auditSessionEnd(session, reason) {
67259
+ if (session.auditEndRecorded)
67260
+ return;
67261
+ session.auditEndRecorded = true;
67262
+ auditLog(session.platformId, {
67263
+ threadId: session.threadId,
67264
+ sessionId: session.sessionId,
67265
+ actor: session.lastActorUsername ?? session.startedBy,
67266
+ kind: "session_end",
67267
+ detail: reason
67268
+ });
67269
+ }
67270
+ async function closeThreadLogger(session, action, details, auditReason) {
67271
+ if (auditReason ?? action) {
67272
+ auditSessionEnd(session, auditReason ?? action);
67273
+ }
67050
67274
  if (session.threadLogger) {
67051
67275
  if (action) {
67052
67276
  session.threadLogger.logLifecycle(action, details);
@@ -67071,12 +67295,15 @@ async function cleanupSession(session, ctx, options = {}) {
67071
67295
  action,
67072
67296
  details,
67073
67297
  closeLogger: doCloseLogger = true,
67074
- cleanupPostIndex: doCleanupPostIndex = true
67298
+ cleanupPostIndex: doCleanupPostIndex = true,
67299
+ auditReason
67075
67300
  } = options;
67076
67301
  ctx.ops.stopTyping(session);
67077
67302
  cleanupSessionTimers(session);
67078
67303
  if (doCloseLogger) {
67079
- await closeThreadLogger(session, action, details);
67304
+ await closeThreadLogger(session, action, details, auditReason);
67305
+ } else if (auditReason ?? action) {
67306
+ auditSessionEnd(session, auditReason ?? action);
67080
67307
  }
67081
67308
  session.messageManager?.dispose();
67082
67309
  session.decisionBridge?.close();
@@ -67096,7 +67323,9 @@ function releaseAccountIfHeld(session, ctx) {
67096
67323
  session.claudeAccountId = undefined;
67097
67324
  }
67098
67325
  }
67099
- function removeFromRegistry(session, ctx) {
67326
+ function removeFromRegistry(session, ctx, auditReason) {
67327
+ if (auditReason)
67328
+ auditSessionEnd(session, auditReason);
67100
67329
  session.messageManager?.dispose();
67101
67330
  session.decisionBridge?.close();
67102
67331
  session.decisionBridge = undefined;
@@ -67135,7 +67364,7 @@ async function createSessionDecisionBridge(ref) {
67135
67364
  return messageManager.handleBridgeRequest(request, signal);
67136
67365
  });
67137
67366
  } catch (err) {
67138
- log27.warn(`Decision bridge unavailable — falling back to legacy MCP prompts: ${err}`);
67367
+ log28.warn(`Decision bridge unavailable — falling back to legacy MCP prompts: ${err}`);
67139
67368
  return null;
67140
67369
  }
67141
67370
  }
@@ -67204,6 +67433,14 @@ function createMessageManager(session, ctx) {
67204
67433
  }
67205
67434
  });
67206
67435
  messageManager.events.on("routine-prompt:complete", async ({ approved, parsed, requestedBy, postId }) => {
67436
+ auditLog(session.platformId, {
67437
+ threadId: session.threadId,
67438
+ sessionId: session.sessionId,
67439
+ actor: requestedBy,
67440
+ kind: "command",
67441
+ tool: "routine",
67442
+ detail: `${approved ? "created" : "discarded"}: ${parsed.name}`
67443
+ });
67207
67444
  session.threadLogger?.logCommand("routine", approved ? "created" : "discarded", requestedBy);
67208
67445
  if (!approved) {
67209
67446
  sessionLog6(session).info(`\uD83D\uDD58 Routine "${parsed.name}" discarded before saving`);
@@ -67422,7 +67659,7 @@ function resumeSessionHeaderMode(persisted, platformConfigured) {
67422
67659
  function resolveSessionHeaderMode(configured, replyToPostId, platformId) {
67423
67660
  const mode = configured ?? DEFAULT_OVERHEAD_VISIBILITY;
67424
67661
  if (mode === "hidden" && !replyToPostId) {
67425
- log27.error(`sessionHeader: hidden requires a replyToPostId for ${platformId}; ` + `downgrading this session to 'minimal' so the header post is still short.`);
67662
+ log28.error(`sessionHeader: hidden requires a replyToPostId for ${platformId}; ` + `downgrading this session to 'minimal' so the header post is still short.`);
67426
67663
  return "minimal";
67427
67664
  }
67428
67665
  return mode;
@@ -67462,7 +67699,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
67462
67699
  throw new Error(`Platform '${platformId}' not found. Call addPlatform() first.`);
67463
67700
  }
67464
67701
  if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers: undefined })) {
67465
- log27.warn(`auth.denied.startSession: @${username || "unknown"} not authorized to start session in ${threadId.substring(0, 8)}...`);
67702
+ log28.warn(`auth.denied.startSession: @${username || "unknown"} not authorized to start session in ${threadId.substring(0, 8)}...`);
67466
67703
  return;
67467
67704
  }
67468
67705
  const activeOrPending = ctx.state.sessions.size + pendingStartsCount;
@@ -67523,17 +67760,17 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
67523
67760
  return;
67524
67761
  }
67525
67762
  workingDir = resolvedDir;
67526
- log27.info(`Starting session in directory: ${workingDir} (from !cd command)`);
67763
+ log28.info(`Starting session in directory: ${workingDir} (from !cd command)`);
67527
67764
  }
67528
67765
  if (initialOptions?.permissionMode) {
67529
67766
  permissionMode = initialOptions.permissionMode;
67530
67767
  forceInteractivePermissions = permissionMode === "default";
67531
67768
  sessionPermissionModeOverride = permissionMode;
67532
- log27.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
67769
+ log28.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
67533
67770
  } else if (initialOptions?.forceInteractivePermissions) {
67534
67771
  forceInteractivePermissions = true;
67535
67772
  permissionMode = "default";
67536
- log27.info(`Starting session with interactive permissions (from !permissions command)`);
67773
+ log28.info(`Starting session with interactive permissions (from !permissions command)`);
67537
67774
  }
67538
67775
  const userAttribution = ctx.config.userAttribution ?? true;
67539
67776
  const memoryConfig = ctx.ops.getPlatformMemoryConfig(platformId);
@@ -67547,7 +67784,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
67547
67784
  balanceByUsage: true
67548
67785
  });
67549
67786
  if (claudeAccount) {
67550
- log27.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
67787
+ log28.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
67551
67788
  }
67552
67789
  const bridgeSessionRef = {};
67553
67790
  const decisionBridge = await createSessionDecisionBridge(bridgeSessionRef);
@@ -67612,6 +67849,12 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
67612
67849
  session.decisionBridge = decisionBridge ?? undefined;
67613
67850
  session.messageManager = createMessageManager(session, ctx);
67614
67851
  bridgeSessionRef.current = session;
67852
+ auditLog(session.platformId, {
67853
+ threadId: session.threadId,
67854
+ sessionId: session.sessionId,
67855
+ actor: session.startedBy,
67856
+ kind: "session_start"
67857
+ });
67615
67858
  session.threadLogger?.logLifecycle("start", {
67616
67859
  username,
67617
67860
  workingDir: ctx.config.workingDir
@@ -67636,6 +67879,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
67636
67879
  claude.start();
67637
67880
  } catch (err) {
67638
67881
  await logAndNotify(err, { action: "Start Claude", session });
67882
+ auditSessionEnd(session, "start-failed");
67639
67883
  ctx.ops.stopTyping(session);
67640
67884
  session.messageManager?.dispose();
67641
67885
  session.decisionBridge?.close();
@@ -67670,12 +67914,12 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
67670
67914
  claude.sendMessage(formatUserTurn(content, username, shouldAttribute(session.userAttribution, session.sessionAllowedUsers.size)));
67671
67915
  await postSkippedFilesFeedback(session.platform, actualThreadId, skipped);
67672
67916
  }
67673
- async function resumeSession(state, ctx) {
67917
+ async function resumeSession(state, ctx, resumedBy) {
67674
67918
  if (state.threadId && state.platformId) {
67675
67919
  const sessionKey = `${state.platformId}:${state.threadId}`;
67676
67920
  const sessions = ctx.state?.sessions;
67677
67921
  if (sessions?.has(sessionKey)) {
67678
- log27.debug(`Session ${state.threadId.substring(0, 8)}... already active, skipping resume`);
67922
+ log28.debug(`Session ${state.threadId.substring(0, 8)}... already active, skipping resume`);
67679
67923
  return;
67680
67924
  }
67681
67925
  const inFlight = _inFlightSessionStarts.get(sessionKey);
@@ -67683,7 +67927,7 @@ async function resumeSession(state, ctx) {
67683
67927
  await inFlight.catch(() => {});
67684
67928
  return;
67685
67929
  }
67686
- const attempt = resumeSessionImpl(state, ctx);
67930
+ const attempt = resumeSessionImpl(state, ctx, resumedBy);
67687
67931
  _inFlightSessionStarts.set(sessionKey, attempt);
67688
67932
  try {
67689
67933
  await attempt;
@@ -67692,9 +67936,9 @@ async function resumeSession(state, ctx) {
67692
67936
  }
67693
67937
  return;
67694
67938
  }
67695
- await resumeSessionImpl(state, ctx);
67939
+ await resumeSessionImpl(state, ctx, resumedBy);
67696
67940
  }
67697
- async function resumeSessionImpl(state, ctx) {
67941
+ async function resumeSessionImpl(state, ctx, resumedBy) {
67698
67942
  if (!state.threadId || !state.platformId || !state.claudeSessionId || !state.workingDir) {
67699
67943
  const missing = [
67700
67944
  !state.threadId && "threadId",
@@ -67702,35 +67946,35 @@ async function resumeSessionImpl(state, ctx) {
67702
67946
  !state.claudeSessionId && "claudeSessionId",
67703
67947
  !state.workingDir && "workingDir"
67704
67948
  ].filter(Boolean).join(", ");
67705
- log27.warn(`Skipping session with missing required fields: ${missing}`);
67949
+ log28.warn(`Skipping session with missing required fields: ${missing}`);
67706
67950
  return;
67707
67951
  }
67708
67952
  const shortId = state.threadId.substring(0, 8);
67709
67953
  const platforms = ctx.state.platforms;
67710
67954
  const platform = platforms.get(state.platformId);
67711
67955
  if (!platform) {
67712
- log27.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
67956
+ log28.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
67713
67957
  return;
67714
67958
  }
67715
67959
  if (isDcmThreadId(state.threadId) && !platform.directChannelMode?.enabled) {
67716
- log27.warn(`Direct channel mode disabled for ${state.platformId}, dropping persisted DCM session`);
67960
+ log28.warn(`Direct channel mode disabled for ${state.platformId}, dropping persisted DCM session`);
67717
67961
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
67718
67962
  return;
67719
67963
  }
67720
67964
  if (!isDcmThreadId(state.threadId)) {
67721
67965
  const threadPost = await platform.getPost(state.threadId);
67722
67966
  if (!threadPost) {
67723
- log27.warn(`Thread ${shortId}... deleted, skipping resume`);
67967
+ log28.warn(`Thread ${shortId}... deleted, skipping resume`);
67724
67968
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
67725
67969
  return;
67726
67970
  }
67727
67971
  }
67728
67972
  if (ctx.state.sessions.size >= ctx.config.maxSessions) {
67729
- log27.warn(`Max sessions reached, skipping resume for ${shortId}...`);
67973
+ log28.warn(`Max sessions reached, skipping resume for ${shortId}...`);
67730
67974
  return;
67731
67975
  }
67732
67976
  if (!existsSync11(state.workingDir)) {
67733
- log27.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
67977
+ log28.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
67734
67978
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
67735
67979
  const resumeFormatter = platform.getFormatter();
67736
67980
  const tempSession = {
@@ -67756,7 +68000,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
67756
68000
  const appendSystemPrompt = await buildAppendSystemPrompt(platform, state.platformId, state.workingDir, state.threadId, state.startedBy, state.sessionAllowedUsers || [state.startedBy], CHAT_PLATFORM_PROMPT, ctx.state.githubEmailsStore, memoryConfig.enabled && memoryConfig.channelLayer ? ctx.state.memoryStore : null, { userAttribution });
67757
68001
  const claudeAccount = ctx.ops.acquireClaudeAccount(state.claudeAccountId, state.threadId);
67758
68002
  if (state.claudeAccountId && !claudeAccount) {
67759
- log27.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
68003
+ log28.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
67760
68004
  }
67761
68005
  const resumeBridgeRef = {};
67762
68006
  const resumeBridge = await createSessionDecisionBridge(resumeBridgeRef);
@@ -67839,7 +68083,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
67839
68083
  worktreePath: detected.worktreePath,
67840
68084
  branch: detected.branch
67841
68085
  };
67842
- log27.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
68086
+ log28.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
67843
68087
  }
67844
68088
  }
67845
68089
  session.messageManager = createMessageManager(session, ctx);
@@ -67858,6 +68102,14 @@ Please start a new session.`), { action: "Post resume failure notification" });
67858
68102
  pendingApproval: persistedWithInteractive.pendingApproval
67859
68103
  });
67860
68104
  }
68105
+ if (resumedBy)
68106
+ session.lastActorUsername = resumedBy;
68107
+ auditLog(session.platformId, {
68108
+ threadId: session.threadId,
68109
+ sessionId: session.sessionId,
68110
+ actor: resumedBy ?? session.startedBy,
68111
+ kind: "session_resume"
68112
+ });
67861
68113
  session.threadLogger?.logLifecycle("resume", {
67862
68114
  username: state.startedBy,
67863
68115
  workingDir: state.workingDir
@@ -67898,7 +68150,8 @@ ${sessionFormatter.formatItalic("Reconnected to Claude session. You can continue
67898
68150
  await postResumeCoAuthorOnboarding(session, ctx);
67899
68151
  ctx.ops.persistSession(session);
67900
68152
  } catch (err) {
67901
- log27.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
68153
+ log28.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
68154
+ auditSessionEnd(session, "resume-failed");
67902
68155
  session.messageManager?.dispose();
67903
68156
  session.decisionBridge?.close();
67904
68157
  session.decisionBridge = undefined;
@@ -67931,6 +68184,8 @@ async function sendFollowUp(session, message, files, ctx, username, displayName,
67931
68184
  return;
67932
68185
  }
67933
68186
  }
68187
+ if (username && !options?.system)
68188
+ session.lastActorUsername = username;
67934
68189
  if (session.needsContextPromptOnNextMessage) {
67935
68190
  session.needsContextPromptOnNextMessage = false;
67936
68191
  await session.messageManager?.prepareForUserMessage();
@@ -67951,28 +68206,28 @@ async function resumePausedSession(threadId, message, files, ctx, username) {
67951
68206
  const persisted = ctx.state.sessionStore.load();
67952
68207
  const state = findPersistedByThreadId(persisted, threadId);
67953
68208
  if (!state) {
67954
- log27.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
68209
+ log28.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
67955
68210
  return;
67956
68211
  }
67957
68212
  const shortId = threadId.substring(0, 8);
67958
68213
  const platform = ctx.state.platforms.get(state.platformId);
67959
68214
  if (!platform) {
67960
- log27.warn(`auth.denied.resume: platform '${state.platformId}' not found for ${shortId}...`);
68215
+ log28.warn(`auth.denied.resume: platform '${state.platformId}' not found for ${shortId}...`);
67961
68216
  return;
67962
68217
  }
67963
68218
  const sessionAllowedUsers = new Set(state.sessionAllowedUsers || [state.startedBy].filter(Boolean));
67964
68219
  if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers })) {
67965
- log27.warn(`auth.denied.resume: @${username || "unknown"} not authorized to resume ${shortId}...`);
68220
+ log28.warn(`auth.denied.resume: @${username || "unknown"} not authorized to resume ${shortId}...`);
67966
68221
  return;
67967
68222
  }
67968
- log27.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
67969
- await resumeSession(state, ctx);
68223
+ log28.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
68224
+ await resumeSession(state, ctx, username);
67970
68225
  const session = ctx.ops.findSessionByThreadId(threadId);
67971
68226
  if (session && session.claude.isRunning() && session.messageManager) {
67972
68227
  session.messageCount++;
67973
68228
  await session.messageManager.handleUserMessage(message, files, username);
67974
68229
  } else {
67975
- log27.warn(`Failed to resume session ${shortId}..., could not send message`);
68230
+ log28.warn(`Failed to resume session ${shortId}..., could not send message`);
67976
68231
  }
67977
68232
  }
67978
68233
  async function handleExit(sessionId, code, ctx, source) {
@@ -67980,7 +68235,7 @@ async function handleExit(sessionId, code, ctx, source) {
67980
68235
  const shortId = sessionId.substring(0, 8);
67981
68236
  sessionLog6(session).debug(`handleExit called code=${code} isShuttingDown=${ctx.state.isShuttingDown}`);
67982
68237
  if (!session) {
67983
- log27.debug(`Session ${shortId}... not found (already cleaned up)`);
68238
+ log28.debug(`Session ${shortId}... not found (already cleaned up)`);
67984
68239
  return;
67985
68240
  }
67986
68241
  if (source && session.claude !== source) {
@@ -68001,7 +68256,8 @@ async function handleExit(sessionId, code, ctx, source) {
68001
68256
  await cleanupSession(session, ctx, {
68002
68257
  action: "exit",
68003
68258
  details: { reason: "shutdown", exitCode: code },
68004
- cleanupPostIndex: false
68259
+ cleanupPostIndex: false,
68260
+ auditReason: "shutdown"
68005
68261
  });
68006
68262
  return;
68007
68263
  }
@@ -68009,7 +68265,7 @@ async function handleExit(sessionId, code, ctx, source) {
68009
68265
  sessionLog6(session).debug(`Exited after interrupt, preserving for resume`);
68010
68266
  ctx.ops.stopTyping(session);
68011
68267
  cleanupSessionTimers(session);
68012
- await closeThreadLogger(session, "interrupt", { exitCode: code });
68268
+ await closeThreadLogger(session, "interrupt", { exitCode: code }, "pause");
68013
68269
  const message = session.lifecycle.hasClaudeResponded ? `ℹ️ Session paused. Send a new message to continue.` : `ℹ️ Session ended before Claude could respond. Send a new message to start fresh.`;
68014
68270
  const pausePost = await withErrorHandling(() => post(session, "info", message), { action: "Post session pause notification", session });
68015
68271
  if (session.lifecycle.hasClaudeResponded) {
@@ -68020,7 +68276,7 @@ async function handleExit(sessionId, code, ctx, source) {
68020
68276
  }
68021
68277
  ctx.ops.persistSession(session);
68022
68278
  }
68023
- removeFromRegistry(session, ctx);
68279
+ removeFromRegistry(session, ctx, "pause");
68024
68280
  sessionLog6(session).info(`⏸ Session paused`);
68025
68281
  await ctx.ops.updateStickyMessage();
68026
68282
  return;
@@ -68030,7 +68286,8 @@ async function handleExit(sessionId, code, ctx, source) {
68030
68286
  sessionLog6(session).debug(`Exited before Claude responded, not persisting`);
68031
68287
  await cleanupSession(session, ctx, {
68032
68288
  action: "exit",
68033
- details: { reason: "early_exit", exitCode: code }
68289
+ details: { reason: "early_exit", exitCode: code },
68290
+ auditReason: "early-exit"
68034
68291
  });
68035
68292
  const earlyExitFormatter = session.platform.getFormatter();
68036
68293
  await withErrorHandling(() => post(session, "warning", `${earlyExitFormatter.formatBold("Session ended")} before Claude could respond (exit code ${code}). Please start a new session.`), { action: "Post early exit notification", session });
@@ -68044,6 +68301,7 @@ async function handleExit(sessionId, code, ctx, source) {
68044
68301
  const isPermanent = session.claude.isPermanentFailure();
68045
68302
  const permanentReason = session.claude.getPermanentFailureReason();
68046
68303
  sessionLog6(session).debug(`Resumed session failed with code ${code}, attempt ${session.lifecycle.resumeFailCount}/${MAX_RESUME_FAILURES}, permanent=${isPermanent}`);
68304
+ auditSessionEnd(session, code === null ? "exit" : `exit:${code}`);
68047
68305
  await cleanupSession(session, ctx, {
68048
68306
  closeLogger: false,
68049
68307
  cleanupPostIndex: false
@@ -68079,7 +68337,7 @@ Please start a new session.`), { action: "Post session permanent failure", sessi
68079
68337
  scheduleDistillation(session, ctx, "exit");
68080
68338
  ctx.ops.stopTyping(session);
68081
68339
  cleanupSessionTimers(session);
68082
- await closeThreadLogger(session, "exit", { exitCode: code });
68340
+ await closeThreadLogger(session, "exit", { exitCode: code }, code === 0 || code === null ? "exit" : `exit:${code}`);
68083
68341
  const exitTaskState = session.messageManager?.getTaskListState();
68084
68342
  if (exitTaskState?.postId) {
68085
68343
  await session.platform.unpinPost(exitTaskState.postId).catch(() => {});
@@ -68092,7 +68350,7 @@ Please start a new session.`), { action: "Post session permanent failure", sessi
68092
68350
  if (session.worktreeInfo) {
68093
68351
  ctx.ops.unregisterWorktreeUser(session.worktreeInfo.worktreePath, session.sessionId);
68094
68352
  }
68095
- removeFromRegistry(session, ctx);
68353
+ removeFromRegistry(session, ctx, code === 0 ? "exit" : `exit:${code}`);
68096
68354
  if (code === 0 || code === null) {
68097
68355
  ctx.ops.unpersistSession(session.sessionId);
68098
68356
  } else {
@@ -68101,7 +68359,7 @@ Please start a new session.`), { action: "Post session permanent failure", sessi
68101
68359
  sessionLog6(session).info(`■ Session ended`);
68102
68360
  await ctx.ops.updateStickyMessage();
68103
68361
  }
68104
- async function killSession(session, unpersist, ctx) {
68362
+ async function killSession(session, unpersist, ctx, auditCause = "kill") {
68105
68363
  if (!unpersist) {
68106
68364
  transitionTo(session, "restarting");
68107
68365
  }
@@ -68109,7 +68367,7 @@ async function killSession(session, unpersist, ctx) {
68109
68367
  scheduleDistillation(session, ctx, "stop");
68110
68368
  }
68111
68369
  ctx.ops.stopTyping(session);
68112
- await closeThreadLogger(session, "kill", { unpersist });
68370
+ await closeThreadLogger(session, "kill", { unpersist }, auditCause);
68113
68371
  session.claude.kill();
68114
68372
  const killTaskState = session.messageManager?.getTaskListState();
68115
68373
  if (killTaskState?.postId) {
@@ -68118,7 +68376,7 @@ async function killSession(session, unpersist, ctx) {
68118
68376
  if (unpersist && session.worktreeInfo) {
68119
68377
  ctx.ops.unregisterWorktreeUser(session.worktreeInfo.worktreePath, session.sessionId);
68120
68378
  }
68121
- removeFromRegistry(session, ctx);
68379
+ removeFromRegistry(session, ctx, auditCause);
68122
68380
  if (unpersist) {
68123
68381
  ctx.ops.unpersistSession(session.sessionId);
68124
68382
  }
@@ -68132,6 +68390,7 @@ async function killAllSessions(ctx) {
68132
68390
  if (ctx.state.isShuttingDown) {
68133
68391
  ctx.ops.persistSession(session);
68134
68392
  }
68393
+ auditSessionEnd(session, ctx.state.isShuttingDown ? "shutdown" : "kill");
68135
68394
  killPromises.push(session.claude.kill());
68136
68395
  }
68137
68396
  await Promise.all(killPromises);
@@ -68162,7 +68421,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
68162
68421
  transitionTo(session, "paused");
68163
68422
  ctx.ops.persistSession(session);
68164
68423
  scheduleDistillation(session, ctx, "timeout");
68165
- await killSession(session, false, ctx);
68424
+ await killSession(session, false, ctx, "timeout");
68166
68425
  continue;
68167
68426
  }
68168
68427
  const warningThresholdMs = timeoutMs - warningMs;
@@ -68183,7 +68442,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
68183
68442
 
68184
68443
  // src/platform/dm-discovery-runtime.ts
68185
68444
  function createDmDiscoveryRuntime(deps) {
68186
- const { platforms, session, log: log28 } = deps;
68445
+ const { platforms, session, log: log29 } = deps;
68187
68446
  const graceMs = deps.graceMs ?? 30000;
68188
68447
  const orphanTtlMs = deps.orphanTtlMs ?? 10 * 60000;
68189
68448
  const instanceByChannel = new Map;
@@ -68226,9 +68485,10 @@ function createDmDiscoveryRuntime(deps) {
68226
68485
  session.removePlatform(dmId);
68227
68486
  if (client)
68228
68487
  Promise.resolve(client.disconnect()).catch(() => {});
68488
+ configureAuditLog(dmId, false);
68229
68489
  if (deps.isEnabled?.(dmId) !== false)
68230
68490
  deps.removeUiRow?.(dmId);
68231
- log28("info", `\uD83E\uDDF9 DM instance ${dmId} torn down (${reason})`);
68491
+ log29("info", `\uD83E\uDDF9 DM instance ${dmId} torn down (${reason})`);
68232
68492
  };
68233
68493
  const register = (parentCfg, channelId, partnerUsernames) => {
68234
68494
  const dmConfig = deriveDmPlatformConfig(parentCfg, channelId, partnerUsernames);
@@ -68257,10 +68517,10 @@ function createDmDiscoveryRuntime(deps) {
68257
68517
  }
68258
68518
  const dmId = dmPlatformId(parentConfig.id, post2.channelId);
68259
68519
  if (deps.isEnabled && !deps.isEnabled(dmId)) {
68260
- log28("info", `Ignoring DM for disabled instance ${dmId}`);
68520
+ log29("info", `Ignoring DM for disabled instance ${dmId}`);
68261
68521
  return;
68262
68522
  }
68263
- log28("info", `\uD83D\uDCE9 New DM conversation with @${username} — spawning ${dmId}`);
68523
+ log29("info", `\uD83D\uDCE9 New DM conversation with @${username} — spawning ${dmId}`);
68264
68524
  const dmClient = register(parentConfig, post2.channelId, [username]);
68265
68525
  connecting.add(dmId);
68266
68526
  dmClient.connect().then(() => {
@@ -68270,7 +68530,7 @@ function createDmDiscoveryRuntime(deps) {
68270
68530
  }).catch((err) => {
68271
68531
  if (platforms.get(dmId) !== dmClient)
68272
68532
  return;
68273
- log28("error", `Failed to connect DM instance ${dmId}, discarding: ${err}`);
68533
+ log29("error", `Failed to connect DM instance ${dmId}, discarding: ${err}`);
68274
68534
  (async () => {
68275
68535
  const threadId = `dcm:${dmId}`;
68276
68536
  const inFlightDeadline = Date.now() + 30000;
@@ -68279,7 +68539,7 @@ function createDmDiscoveryRuntime(deps) {
68279
68539
  if (!inFlight)
68280
68540
  break;
68281
68541
  if (Date.now() > inFlightDeadline) {
68282
- log28("warn", `In-flight session start for ${dmId} did not settle within 30s — proceeding with teardown`);
68542
+ log29("warn", `In-flight session start for ${dmId} did not settle within 30s — proceeding with teardown`);
68283
68543
  break;
68284
68544
  }
68285
68545
  await Promise.race([
@@ -68294,7 +68554,7 @@ function createDmDiscoveryRuntime(deps) {
68294
68554
  try {
68295
68555
  await session.cancelSession(threadId, dmClient.getBotName());
68296
68556
  } catch (cancelErr) {
68297
- log28("warn", `Failed to cancel stranded DM session ${threadId} (will be reaped by idle cleanup): ${cancelErr}`);
68557
+ log29("warn", `Failed to cancel stranded DM session ${threadId} (will be reaped by idle cleanup): ${cancelErr}`);
68298
68558
  }
68299
68559
  }
68300
68560
  }
@@ -68304,7 +68564,7 @@ function createDmDiscoveryRuntime(deps) {
68304
68564
  return;
68305
68565
  if (!session.registry.findByThreadId(threadId))
68306
68566
  return;
68307
- log28("warn", `Sweeping session stranded on removed DM platform ${dmId}`);
68567
+ log29("warn", `Sweeping session stranded on removed DM platform ${dmId}`);
68308
68568
  session.cancelSession(threadId, dmClient.getBotName()).catch(() => {});
68309
68569
  }, 2000);
68310
68570
  })();
@@ -68335,21 +68595,21 @@ function createDmDiscoveryRuntime(deps) {
68335
68595
  continue;
68336
68596
  const parentCfg = platformConfigs.filter((p) => p.type === "mattermost" && !!p.directMessages && pid.startsWith(`${p.id}${DM_PLATFORM_SEP}`)).sort((a, b) => b.id.length - a.id.length)[0];
68337
68597
  if (!parentCfg) {
68338
- log28("warn", `Skipping persisted DM session for ${pid} (parent missing, renamed, or directMessages off)`);
68598
+ log29("warn", `Skipping persisted DM session for ${pid} (parent missing, renamed, or directMessages off)`);
68339
68599
  continue;
68340
68600
  }
68341
68601
  const channelId = pid.slice(parentCfg.id.length + DM_PLATFORM_SEP.length);
68342
68602
  if (instanceByChannel.has(channelId)) {
68343
- log28("warn", `Skipping persisted DM session for ${pid} (channel already owned by ${instanceByChannel.get(channelId)})`);
68603
+ log29("warn", `Skipping persisted DM session for ${pid} (channel already owned by ${instanceByChannel.get(channelId)})`);
68344
68604
  continue;
68345
68605
  }
68346
68606
  if (!isEnabled(pid)) {
68347
- log28("info", `Skipping disabled DM instance ${pid}`);
68607
+ log29("info", `Skipping disabled DM instance ${pid}`);
68348
68608
  skippedDisabled.push({ platformId: pid, channelId });
68349
68609
  continue;
68350
68610
  }
68351
68611
  const partners = persisted.sessionAllowedUsers && persisted.sessionAllowedUsers.length > 0 ? persisted.sessionAllowedUsers : [persisted.startedBy].filter((u) => !!u);
68352
- log28("info", `♻️ Reconstructing DM instance ${pid}`);
68612
+ log29("info", `♻️ Reconstructing DM instance ${pid}`);
68353
68613
  register(parentCfg, channelId, partners);
68354
68614
  connecting.add(pid);
68355
68615
  reconstructed.set(pid, channelId);
@@ -68391,7 +68651,7 @@ function createDmDiscoveryRuntime(deps) {
68391
68651
  // src/onboarding.ts
68392
68652
  var import_prompts = __toESM(require_prompts3(), 1);
68393
68653
  import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
68394
- import { join as join11, dirname as dirname8 } from "path";
68654
+ import { join as join12, dirname as dirname8 } from "path";
68395
68655
  import { spawn as spawn3 } from "child_process";
68396
68656
  import { fileURLToPath as fileURLToPath6 } from "url";
68397
68657
 
@@ -68455,7 +68715,7 @@ function overheadVisibilityChoiceIndex(mode) {
68455
68715
  return OVERHEAD_VISIBILITY_CHOICES.findIndex((c) => c.value === mode);
68456
68716
  }
68457
68717
  var __dirname6 = dirname8(fileURLToPath6(import.meta.url));
68458
- var SLACK_MANIFEST_PATH = join11(__dirname6, "..", "docs", "slack-app-manifest.yaml");
68718
+ var SLACK_MANIFEST_PATH = join12(__dirname6, "..", "docs", "slack-app-manifest.yaml");
68459
68719
  var onCancel = () => {
68460
68720
  console.log("");
68461
68721
  console.log(dim(" Setup cancelled."));
@@ -69781,7 +70041,7 @@ async function setupSlackPlatform(id, existing) {
69781
70041
  // src/platform/base-client.ts
69782
70042
  init_logger();
69783
70043
  import { EventEmitter as EventEmitter3 } from "events";
69784
- var log28 = createLogger("base-client");
70044
+ var log29 = createLogger("base-client");
69785
70045
 
69786
70046
  class BasePlatformClient extends EventEmitter3 {
69787
70047
  allowedUsers = [];
@@ -69814,7 +70074,7 @@ class BasePlatformClient extends EventEmitter3 {
69814
70074
  try {
69815
70075
  await this.addReaction(post2.id, emoji);
69816
70076
  } catch (err) {
69817
- log28.warn(`Failed to add reaction ${emoji}: ${err}`);
70077
+ log29.warn(`Failed to add reaction ${emoji}: ${err}`);
69818
70078
  }
69819
70079
  }
69820
70080
  return post2;
@@ -69841,7 +70101,7 @@ class BasePlatformClient extends EventEmitter3 {
69841
70101
  this.heartbeatInterval = setInterval(() => {
69842
70102
  const silentFor = Date.now() - this.lastMessageAt;
69843
70103
  if (silentFor > this.HEARTBEAT_TIMEOUT_MS) {
69844
- log28.warn(`Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
70104
+ log29.warn(`Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
69845
70105
  this.stopHeartbeat();
69846
70106
  this.scheduleReconnect();
69847
70107
  return;
@@ -69861,7 +70121,7 @@ class BasePlatformClient extends EventEmitter3 {
69861
70121
  this.reconnectTimeout = null;
69862
70122
  }
69863
70123
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
69864
- log28.error("Max reconnection attempts reached");
70124
+ log29.error("Max reconnection attempts reached");
69865
70125
  return;
69866
70126
  }
69867
70127
  this.forceCloseConnection();
@@ -69888,7 +70148,7 @@ class BasePlatformClient extends EventEmitter3 {
69888
70148
  this.emit("connected");
69889
70149
  if (this.isReconnecting) {
69890
70150
  this.recoverMissedMessages().catch((err) => {
69891
- log28.warn(`Failed to recover missed messages: ${err}`);
70151
+ log29.warn(`Failed to recover missed messages: ${err}`);
69892
70152
  });
69893
70153
  }
69894
70154
  this.isReconnecting = false;
@@ -69922,7 +70182,7 @@ init_logger();
69922
70182
  // src/platform/mattermost/upload.ts
69923
70183
  init_logger();
69924
70184
  import { readFile as readFile3 } from "fs/promises";
69925
- var log29 = createLogger("mm-upload");
70185
+ var log30 = createLogger("mm-upload");
69926
70186
  async function uploadFileMattermost(args) {
69927
70187
  const { url, token, channelId, threadId, filePath, filename, caption } = args;
69928
70188
  const buffer = await readFile3(filePath);
@@ -69930,7 +70190,7 @@ async function uploadFileMattermost(args) {
69930
70190
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
69931
70191
  const formData = new FormData;
69932
70192
  formData.append("files", new Blob([arrayBuffer]), filename);
69933
- log29.debug(`POST /files (${buffer.length} bytes, ${filename})`);
70193
+ log30.debug(`POST /files (${buffer.length} bytes, ${filename})`);
69934
70194
  const uploadResponse = await fetch(uploadUrl, {
69935
70195
  method: "POST",
69936
70196
  headers: {
@@ -69954,7 +70214,7 @@ async function uploadFileMattermost(args) {
69954
70214
  root_id: resolvePostThreadId(threadId),
69955
70215
  file_ids: [fileInfo.id]
69956
70216
  };
69957
- log29.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
70217
+ log30.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
69958
70218
  const postResponse = await fetch(postUrl, {
69959
70219
  method: "POST",
69960
70220
  headers: {
@@ -70041,7 +70301,7 @@ ${code}
70041
70301
  }
70042
70302
 
70043
70303
  // src/platform/mattermost/client.ts
70044
- var log30 = createLogger("mattermost");
70304
+ var log31 = createLogger("mattermost");
70045
70305
 
70046
70306
  class MattermostClient extends BasePlatformClient {
70047
70307
  platformId;
@@ -70049,6 +70309,7 @@ class MattermostClient extends BasePlatformClient {
70049
70309
  displayName;
70050
70310
  directChannelMode;
70051
70311
  approvals;
70312
+ ackReaction;
70052
70313
  ws = null;
70053
70314
  url;
70054
70315
  token;
@@ -70072,6 +70333,7 @@ class MattermostClient extends BasePlatformClient {
70072
70333
  this.outboundFiles = platformConfig.outboundFiles;
70073
70334
  this.directChannelMode = resolveDirectChannelMode(platformConfig.directChannelMode);
70074
70335
  this.approvals = platformConfig.approvals;
70336
+ this.ackReaction = normalizeAckReaction(platformConfig.ackReaction, `platforms[${platformConfig.id}].ackReaction`);
70075
70337
  }
70076
70338
  normalizePlatformUser(mattermostUser) {
70077
70339
  const displayName = mattermostUser.first_name || mattermostUser.nickname || mattermostUser.username;
@@ -70129,7 +70391,7 @@ class MattermostClient extends BasePlatformClient {
70129
70391
  const hasFileIds = fileIds && fileIds.length > 0;
70130
70392
  const hasFileMetadata = post2.metadata?.files && post2.metadata.files.length > 0;
70131
70393
  if (hasFileIds && !hasFileMetadata) {
70132
- log30.debug(`Post ${formatShortId(post2.id)} has ${fileIds.length} file(s), fetching metadata`);
70394
+ log31.debug(`Post ${formatShortId(post2.id)} has ${fileIds.length} file(s), fetching metadata`);
70133
70395
  try {
70134
70396
  const files = [];
70135
70397
  for (const fileId of fileIds) {
@@ -70137,7 +70399,7 @@ class MattermostClient extends BasePlatformClient {
70137
70399
  const file = await this.api("GET", `/files/${fileId}/info`);
70138
70400
  files.push(file);
70139
70401
  } catch (err) {
70140
- log30.warn(`Failed to fetch file info for ${fileId}: ${err}`);
70402
+ log31.warn(`Failed to fetch file info for ${fileId}: ${err}`);
70141
70403
  }
70142
70404
  }
70143
70405
  if (files.length > 0) {
@@ -70145,10 +70407,10 @@ class MattermostClient extends BasePlatformClient {
70145
70407
  ...post2.metadata,
70146
70408
  files
70147
70409
  };
70148
- log30.debug(`Enriched post ${formatShortId(post2.id)} with ${files.length} file(s)`);
70410
+ log31.debug(`Enriched post ${formatShortId(post2.id)} with ${files.length} file(s)`);
70149
70411
  }
70150
70412
  } catch (err) {
70151
- log30.warn(`Failed to fetch file metadata for post ${formatShortId(post2.id)}: ${err}`);
70413
+ log31.warn(`Failed to fetch file metadata for post ${formatShortId(post2.id)}: ${err}`);
70152
70414
  }
70153
70415
  }
70154
70416
  }
@@ -70158,7 +70420,7 @@ class MattermostClient extends BasePlatformClient {
70158
70420
  const user = await this.getUser(post2.user_id);
70159
70421
  this.emit("direct_message", this.normalizePlatformPost(post2), user);
70160
70422
  } catch (err) {
70161
- log30.warn(`Failed to emit direct message: ${err}`);
70423
+ log31.warn(`Failed to emit direct message: ${err}`);
70162
70424
  }
70163
70425
  }
70164
70426
  MAX_RETRIES = 6;
@@ -70166,7 +70428,7 @@ class MattermostClient extends BasePlatformClient {
70166
70428
  RETRY_DELAY_CAP_MS = 2000;
70167
70429
  async api(method, path10, body, retryCount = 0, options) {
70168
70430
  const url = `${this.url}/api/v4${path10}`;
70169
- log30.debug(`API ${method} ${path10}`);
70431
+ log31.debug(`API ${method} ${path10}`);
70170
70432
  const response = await fetch(url, {
70171
70433
  method,
70172
70434
  headers: {
@@ -70179,19 +70441,19 @@ class MattermostClient extends BasePlatformClient {
70179
70441
  const text = await response.text();
70180
70442
  if (response.status === 500 && retryCount < this.MAX_RETRIES) {
70181
70443
  const delay2 = this.retryDelayMs(retryCount);
70182
- log30.warn(`API ${method} ${path10} failed with 500, retrying in ${delay2}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
70444
+ log31.warn(`API ${method} ${path10} failed with 500, retrying in ${delay2}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
70183
70445
  await new Promise((resolve7) => setTimeout(resolve7, delay2));
70184
70446
  return this.api(method, path10, body, retryCount + 1, options);
70185
70447
  }
70186
70448
  const isSilent = options?.silent?.includes(response.status);
70187
70449
  if (isSilent) {
70188
- log30.debug(`API ${method} ${path10} failed: ${response.status} (expected)`);
70450
+ log31.debug(`API ${method} ${path10} failed: ${response.status} (expected)`);
70189
70451
  } else {
70190
- log30.warn(`API ${method} ${path10} failed: ${response.status} ${text.substring(0, 100)}`);
70452
+ log31.warn(`API ${method} ${path10} failed: ${response.status} ${text.substring(0, 100)}`);
70191
70453
  }
70192
70454
  throw new Error(`Mattermost API error ${response.status}: ${text}`);
70193
70455
  }
70194
- log30.debug(`API ${method} ${path10} → ${response.status}`);
70456
+ log31.debug(`API ${method} ${path10} → ${response.status}`);
70195
70457
  return response.json();
70196
70458
  }
70197
70459
  retryDelayMs(retryCount) {
@@ -70207,28 +70469,28 @@ class MattermostClient extends BasePlatformClient {
70207
70469
  async getUser(userId) {
70208
70470
  const cached = this.userCache.get(userId);
70209
70471
  if (cached) {
70210
- log30.debug(`User ${userId} found in cache: @${cached.username}`);
70472
+ log31.debug(`User ${userId} found in cache: @${cached.username}`);
70211
70473
  return this.normalizePlatformUser(cached);
70212
70474
  }
70213
70475
  try {
70214
70476
  const user = await this.api("GET", `/users/${userId}`);
70215
70477
  this.userCache.set(userId, user);
70216
- log30.debug(`User ${userId} fetched: @${user.username}`);
70478
+ log31.debug(`User ${userId} fetched: @${user.username}`);
70217
70479
  return this.normalizePlatformUser(user);
70218
70480
  } catch (err) {
70219
- log30.warn(`Failed to get user ${userId}: ${err}`);
70481
+ log31.warn(`Failed to get user ${userId}: ${err}`);
70220
70482
  return null;
70221
70483
  }
70222
70484
  }
70223
70485
  async getUserByUsername(username) {
70224
70486
  try {
70225
- log30.debug(`Looking up user by username: @${username}`);
70487
+ log31.debug(`Looking up user by username: @${username}`);
70226
70488
  const user = await this.api("GET", `/users/username/${username}`);
70227
70489
  this.userCache.set(user.id, user);
70228
- log30.debug(`User @${username} found: ${user.id}`);
70490
+ log31.debug(`User @${username} found: ${user.id}`);
70229
70491
  return this.normalizePlatformUser(user);
70230
70492
  } catch (err) {
70231
- log30.warn(`User @${username} not found: ${err}`);
70493
+ log31.warn(`User @${username} not found: ${err}`);
70232
70494
  return null;
70233
70495
  }
70234
70496
  }
@@ -70250,7 +70512,7 @@ class MattermostClient extends BasePlatformClient {
70250
70512
  return this.normalizePlatformPost(post2);
70251
70513
  }
70252
70514
  async addReaction(postId, emojiName) {
70253
- log30.debug(`Adding reaction :${emojiName}: to post ${postId.substring(0, 8)}`);
70515
+ log31.debug(`Adding reaction :${emojiName}: to post ${postId.substring(0, 8)}`);
70254
70516
  await this.api("POST", "/reactions", {
70255
70517
  user_id: this.botUserId,
70256
70518
  post_id: postId,
@@ -70258,11 +70520,11 @@ class MattermostClient extends BasePlatformClient {
70258
70520
  });
70259
70521
  }
70260
70522
  async removeReaction(postId, emojiName) {
70261
- log30.debug(`Removing reaction :${emojiName}: from post ${postId.substring(0, 8)}`);
70523
+ log31.debug(`Removing reaction :${emojiName}: from post ${postId.substring(0, 8)}`);
70262
70524
  await this.api("DELETE", `/users/${this.botUserId}/posts/${postId}/reactions/${emojiName}`);
70263
70525
  }
70264
70526
  async downloadFile(fileId) {
70265
- log30.debug(`Downloading file ${fileId}`);
70527
+ log31.debug(`Downloading file ${fileId}`);
70266
70528
  const url = `${this.url}/api/v4/files/${fileId}`;
70267
70529
  const response = await fetch(url, {
70268
70530
  headers: {
@@ -70270,11 +70532,11 @@ class MattermostClient extends BasePlatformClient {
70270
70532
  }
70271
70533
  });
70272
70534
  if (!response.ok) {
70273
- log30.warn(`Failed to download file ${fileId}: ${response.status}`);
70535
+ log31.warn(`Failed to download file ${fileId}: ${response.status}`);
70274
70536
  throw new Error(`Failed to download file ${fileId}: ${response.status}`);
70275
70537
  }
70276
70538
  const arrayBuffer = await response.arrayBuffer();
70277
- log30.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
70539
+ log31.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
70278
70540
  return Buffer.from(arrayBuffer);
70279
70541
  }
70280
70542
  async getFileInfo(fileId) {
@@ -70296,24 +70558,24 @@ class MattermostClient extends BasePlatformClient {
70296
70558
  }
70297
70559
  async getPost(postId) {
70298
70560
  try {
70299
- log30.debug(`Fetching post ${postId.substring(0, 8)}`);
70561
+ log31.debug(`Fetching post ${postId.substring(0, 8)}`);
70300
70562
  const post2 = await this.api("GET", `/posts/${postId}`);
70301
70563
  return this.normalizePlatformPost(post2);
70302
70564
  } catch (err) {
70303
- log30.debug(`Post ${postId.substring(0, 8)} not found: ${err}`);
70565
+ log31.debug(`Post ${postId.substring(0, 8)} not found: ${err}`);
70304
70566
  return null;
70305
70567
  }
70306
70568
  }
70307
70569
  async deletePost(postId) {
70308
- log30.debug(`Deleting post ${postId.substring(0, 8)}`);
70570
+ log31.debug(`Deleting post ${postId.substring(0, 8)}`);
70309
70571
  await this.api("DELETE", `/posts/${postId}`);
70310
70572
  }
70311
70573
  async pinPost(postId) {
70312
- log30.debug(`Pinning post ${postId.substring(0, 8)}`);
70574
+ log31.debug(`Pinning post ${postId.substring(0, 8)}`);
70313
70575
  await this.api("POST", `/posts/${postId}/pin`);
70314
70576
  }
70315
70577
  async unpinPost(postId) {
70316
- log30.debug(`Unpinning post ${postId.substring(0, 8)}`);
70578
+ log31.debug(`Unpinning post ${postId.substring(0, 8)}`);
70317
70579
  try {
70318
70580
  await this.api("POST", `/posts/${postId}/unpin`, undefined, 0, { silent: [403, 404] });
70319
70581
  } catch (err) {
@@ -70357,7 +70619,7 @@ class MattermostClient extends BasePlatformClient {
70357
70619
  }
70358
70620
  return messages;
70359
70621
  } catch (err) {
70360
- log30.warn(`Failed to get thread history for ${threadId}: ${err}`);
70622
+ log31.warn(`Failed to get thread history for ${threadId}: ${err}`);
70361
70623
  return [];
70362
70624
  }
70363
70625
  }
@@ -70376,7 +70638,7 @@ class MattermostClient extends BasePlatformClient {
70376
70638
  posts.sort((a, b) => (a.createAt ?? 0) - (b.createAt ?? 0));
70377
70639
  return posts;
70378
70640
  } catch (err) {
70379
- log30.warn(`Failed to get channel posts after ${afterPostId}: ${err}`);
70641
+ log31.warn(`Failed to get channel posts after ${afterPostId}: ${err}`);
70380
70642
  return [];
70381
70643
  }
70382
70644
  }
@@ -70512,13 +70774,13 @@ class MattermostClient extends BasePlatformClient {
70512
70774
  if (!this.lastProcessedPostId) {
70513
70775
  return;
70514
70776
  }
70515
- log30.info(`Recovering missed messages after post ${this.lastProcessedPostId}...`);
70777
+ log31.info(`Recovering missed messages after post ${this.lastProcessedPostId}...`);
70516
70778
  const missedPosts = await this.getChannelPostsAfter(this.lastProcessedPostId);
70517
70779
  if (missedPosts.length === 0) {
70518
- log30.info("No missed messages to recover");
70780
+ log31.info("No missed messages to recover");
70519
70781
  return;
70520
70782
  }
70521
- log30.info(`Recovered ${missedPosts.length} missed message(s)`);
70783
+ log31.info(`Recovered ${missedPosts.length} missed message(s)`);
70522
70784
  for (const post2 of missedPosts) {
70523
70785
  this.lastProcessedPostId = post2.id;
70524
70786
  const user = await this.getUser(post2.userId);
@@ -70578,7 +70840,7 @@ init_logger();
70578
70840
  // src/platform/slack/upload.ts
70579
70841
  init_logger();
70580
70842
  import { readFile as readFile4 } from "fs/promises";
70581
- var log31 = createLogger("slack-upload");
70843
+ var log32 = createLogger("slack-upload");
70582
70844
  var DEFAULT_API_URL = "https://slack.com/api";
70583
70845
  async function uploadFileSlack(args) {
70584
70846
  const { botToken, channelId, threadTs, filePath, filename, caption } = args;
@@ -70586,7 +70848,7 @@ async function uploadFileSlack(args) {
70586
70848
  const buffer = await readFile4(filePath);
70587
70849
  const params = new URLSearchParams({ filename, length: String(buffer.length) });
70588
70850
  const step1Url = `${apiUrl}/files.getUploadURLExternal?${params.toString()}`;
70589
- log31.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
70851
+ log32.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
70590
70852
  const step1Response = await fetch(step1Url, {
70591
70853
  method: "GET",
70592
70854
  headers: {
@@ -70604,7 +70866,7 @@ async function uploadFileSlack(args) {
70604
70866
  const uploadUrl = step1Data.upload_url;
70605
70867
  const fileId = step1Data.file_id;
70606
70868
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
70607
- log31.debug(`POST <upload_url>`);
70869
+ log32.debug(`POST <upload_url>`);
70608
70870
  const step2Response = await fetch(uploadUrl, {
70609
70871
  method: "POST",
70610
70872
  headers: {
@@ -70624,7 +70886,7 @@ async function uploadFileSlack(args) {
70624
70886
  if (caption !== undefined) {
70625
70887
  step3Body.initial_comment = caption;
70626
70888
  }
70627
- log31.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
70889
+ log32.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
70628
70890
  const step3Response = await fetch(`${apiUrl}/files.completeUploadExternal`, {
70629
70891
  method: "POST",
70630
70892
  headers: {
@@ -70642,7 +70904,7 @@ async function uploadFileSlack(args) {
70642
70904
  throw new Error(`Slack completeUploadExternal error: ${step3Data.error || "unknown"}`);
70643
70905
  }
70644
70906
  if (!step3Data.ts) {
70645
- log31.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
70907
+ log32.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
70646
70908
  }
70647
70909
  return { fileId, postId: step3Data.ts ?? fileId };
70648
70910
  }
@@ -70717,7 +70979,7 @@ ${code}
70717
70979
  }
70718
70980
 
70719
70981
  // src/platform/slack/client.ts
70720
- var log32 = createLogger("slack");
70982
+ var log33 = createLogger("slack");
70721
70983
 
70722
70984
  class SlackClient extends BasePlatformClient {
70723
70985
  platformId;
@@ -70725,6 +70987,7 @@ class SlackClient extends BasePlatformClient {
70725
70987
  displayName;
70726
70988
  directChannelMode;
70727
70989
  approvals;
70990
+ ackReaction;
70728
70991
  ws = null;
70729
70992
  botToken;
70730
70993
  appToken;
@@ -70757,6 +71020,7 @@ class SlackClient extends BasePlatformClient {
70757
71020
  this.outboundFiles = platformConfig.outboundFiles;
70758
71021
  this.directChannelMode = resolveDirectChannelMode(platformConfig.directChannelMode);
70759
71022
  this.approvals = platformConfig.approvals;
71023
+ this.ackReaction = normalizeAckReaction(platformConfig.ackReaction, `platforms[${platformConfig.id}].ackReaction`);
70760
71024
  }
70761
71025
  normalizePlatformUser(slackUser) {
70762
71026
  const displayName = slackUser.profile?.display_name || slackUser.profile?.real_name || slackUser.real_name || slackUser.name;
@@ -70796,13 +71060,13 @@ class SlackClient extends BasePlatformClient {
70796
71060
  const now = Date.now();
70797
71061
  if (now < this.rateLimitRetryAfter) {
70798
71062
  const waitTime = this.rateLimitRetryAfter - now;
70799
- log32.debug(`Rate limited, waiting ${waitTime}ms`);
71063
+ log33.debug(`Rate limited, waiting ${waitTime}ms`);
70800
71064
  await new Promise((resolve7) => setTimeout(resolve7, waitTime));
70801
71065
  }
70802
71066
  this.rateLimitDelay = 0;
70803
71067
  }
70804
71068
  const url = `${this.apiUrl}/${endpoint}`;
70805
- log32.debug(`API ${method} ${endpoint}`);
71069
+ log33.debug(`API ${method} ${endpoint}`);
70806
71070
  const headers = {
70807
71071
  Authorization: `Bearer ${this.botToken}`,
70808
71072
  "Content-Type": "application/json; charset=utf-8"
@@ -70814,25 +71078,25 @@ class SlackClient extends BasePlatformClient {
70814
71078
  });
70815
71079
  if (response.status === 429) {
70816
71080
  if (retryCount >= this.MAX_RATE_LIMIT_RETRIES) {
70817
- log32.error(`Rate limit max retries (${this.MAX_RATE_LIMIT_RETRIES}) exceeded for ${endpoint}`);
71081
+ log33.error(`Rate limit max retries (${this.MAX_RATE_LIMIT_RETRIES}) exceeded for ${endpoint}`);
70818
71082
  throw new Error(`Slack API rate limit exceeded after ${this.MAX_RATE_LIMIT_RETRIES} retries`);
70819
71083
  }
70820
71084
  const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
70821
71085
  this.rateLimitDelay = retryAfter * 1000;
70822
71086
  this.rateLimitRetryAfter = Date.now() + this.rateLimitDelay;
70823
- log32.warn(`Rate limited by Slack, retrying after ${retryAfter}s (attempt ${retryCount + 1}/${this.MAX_RATE_LIMIT_RETRIES})`);
71087
+ log33.warn(`Rate limited by Slack, retrying after ${retryAfter}s (attempt ${retryCount + 1}/${this.MAX_RATE_LIMIT_RETRIES})`);
70824
71088
  await new Promise((resolve7) => setTimeout(resolve7, this.rateLimitDelay));
70825
71089
  return this.api(method, endpoint, body, retryCount + 1);
70826
71090
  }
70827
71091
  if (!response.ok) {
70828
71092
  const text = await response.text();
70829
- log32.warn(`API ${method} ${endpoint} failed: ${response.status} ${text.substring(0, 100)}`);
71093
+ log33.warn(`API ${method} ${endpoint} failed: ${response.status} ${text.substring(0, 100)}`);
70830
71094
  throw new Error(`Slack API error ${response.status}: ${text}`);
70831
71095
  }
70832
71096
  const data = await response.json();
70833
71097
  if (!data.ok) {
70834
71098
  if (!expectedErrors.includes(data.error || "")) {
70835
- log32.warn(`API ${method} ${endpoint} error: ${data.error}`);
71099
+ log33.warn(`API ${method} ${endpoint} error: ${data.error}`);
70836
71100
  }
70837
71101
  throw new Error(`Slack API error: ${data.error}`);
70838
71102
  }
@@ -70840,7 +71104,7 @@ class SlackClient extends BasePlatformClient {
70840
71104
  }
70841
71105
  async appApi(method, endpoint, body) {
70842
71106
  const url = `${this.apiUrl}/${endpoint}`;
70843
- log32.debug(`App API ${method} ${endpoint}`);
71107
+ log33.debug(`App API ${method} ${endpoint}`);
70844
71108
  const headers = {
70845
71109
  Authorization: `Bearer ${this.appToken}`,
70846
71110
  "Content-Type": "application/json; charset=utf-8"
@@ -70903,7 +71167,7 @@ class SlackClient extends BasePlatformClient {
70903
71167
  this.onConnectionEstablished();
70904
71168
  if (this.isReconnecting && this.lastProcessedTs) {
70905
71169
  this.recoverMissedMessages().catch((err) => {
70906
- log32.warn(`Failed to recover missed messages: ${err}`);
71170
+ log33.warn(`Failed to recover missed messages: ${err}`);
70907
71171
  });
70908
71172
  }
70909
71173
  doResolve();
@@ -71000,7 +71264,7 @@ class SlackClient extends BasePlatformClient {
71000
71264
  this.emit("channel_post", post2, user);
71001
71265
  }
71002
71266
  }).catch((err) => {
71003
- log32.warn(`Failed to get user for message event: ${err}`);
71267
+ log33.warn(`Failed to get user for message event: ${err}`);
71004
71268
  this.emit("message", post2, null);
71005
71269
  });
71006
71270
  }
@@ -71020,7 +71284,7 @@ class SlackClient extends BasePlatformClient {
71020
71284
  this.getUser(event.user || "").then((user) => {
71021
71285
  this.emit("reaction", reaction, user);
71022
71286
  }).catch((err) => {
71023
- log32.warn(`Failed to get user for reaction event: ${err}`);
71287
+ log33.warn(`Failed to get user for reaction event: ${err}`);
71024
71288
  this.emit("reaction", reaction, null);
71025
71289
  });
71026
71290
  }
@@ -71040,7 +71304,7 @@ class SlackClient extends BasePlatformClient {
71040
71304
  this.getUser(event.user || "").then((user) => {
71041
71305
  this.emit("reaction_removed", reaction, user);
71042
71306
  }).catch((err) => {
71043
- log32.warn(`Failed to get user for reaction_removed event: ${err}`);
71307
+ log33.warn(`Failed to get user for reaction_removed event: ${err}`);
71044
71308
  this.emit("reaction_removed", reaction, null);
71045
71309
  });
71046
71310
  }
@@ -71077,15 +71341,15 @@ class SlackClient extends BasePlatformClient {
71077
71341
  if (!this.lastProcessedTs) {
71078
71342
  return;
71079
71343
  }
71080
- log32.info(`Recovering missed messages after ts ${this.lastProcessedTs}...`);
71344
+ log33.info(`Recovering missed messages after ts ${this.lastProcessedTs}...`);
71081
71345
  try {
71082
71346
  const response = await this.api("GET", `conversations.history?channel=${this.channelId}&oldest=${this.lastProcessedTs}&inclusive=false&limit=100`);
71083
71347
  const messages = response.messages || [];
71084
71348
  if (messages.length === 0) {
71085
- log32.info("No missed messages to recover");
71349
+ log33.info("No missed messages to recover");
71086
71350
  return;
71087
71351
  }
71088
- log32.info(`Recovered ${messages.length} missed message(s)`);
71352
+ log33.info(`Recovered ${messages.length} missed message(s)`);
71089
71353
  const sortedMessages = messages.sort((a, b) => parseFloat(a.ts) - parseFloat(b.ts));
71090
71354
  for (const message of sortedMessages) {
71091
71355
  if (message.user === this.botUserId || message.bot_id) {
@@ -71100,7 +71364,7 @@ class SlackClient extends BasePlatformClient {
71100
71364
  }
71101
71365
  }
71102
71366
  } catch (err) {
71103
- log32.warn(`Failed to recover missed messages: ${err}`);
71367
+ log33.warn(`Failed to recover missed messages: ${err}`);
71104
71368
  }
71105
71369
  }
71106
71370
  async fetchBotUser() {
@@ -71124,17 +71388,17 @@ class SlackClient extends BasePlatformClient {
71124
71388
  }
71125
71389
  const cached = this.userCache.get(userId);
71126
71390
  if (cached) {
71127
- log32.debug(`User ${userId} found in cache: @${cached.name}`);
71391
+ log33.debug(`User ${userId} found in cache: @${cached.name}`);
71128
71392
  return this.normalizePlatformUser(cached);
71129
71393
  }
71130
71394
  try {
71131
71395
  const response = await this.api("GET", `users.info?user=${userId}`);
71132
71396
  this.userCache.set(userId, response.user);
71133
71397
  this.usernameToIdCache.set(response.user.name, userId);
71134
- log32.debug(`User ${userId} fetched: @${response.user.name}`);
71398
+ log33.debug(`User ${userId} fetched: @${response.user.name}`);
71135
71399
  return this.normalizePlatformUser(response.user);
71136
71400
  } catch (err) {
71137
- log32.warn(`Failed to get user ${userId}: ${err}`);
71401
+ log33.warn(`Failed to get user ${userId}: ${err}`);
71138
71402
  return null;
71139
71403
  }
71140
71404
  }
@@ -71144,7 +71408,7 @@ class SlackClient extends BasePlatformClient {
71144
71408
  return this.getUser(cachedId);
71145
71409
  }
71146
71410
  try {
71147
- log32.debug(`Looking up user by username: @${username}`);
71411
+ log33.debug(`Looking up user by username: @${username}`);
71148
71412
  let cursor;
71149
71413
  do {
71150
71414
  const params = cursor ? `cursor=${cursor}&limit=200` : "limit=200";
@@ -71153,16 +71417,16 @@ class SlackClient extends BasePlatformClient {
71153
71417
  this.userCache.set(user.id, user);
71154
71418
  this.usernameToIdCache.set(user.name, user.id);
71155
71419
  if (user.name === username) {
71156
- log32.debug(`User @${username} found: ${user.id}`);
71420
+ log33.debug(`User @${username} found: ${user.id}`);
71157
71421
  return this.normalizePlatformUser(user);
71158
71422
  }
71159
71423
  }
71160
71424
  cursor = response.response_metadata?.next_cursor;
71161
71425
  } while (cursor);
71162
- log32.warn(`User @${username} not found`);
71426
+ log33.warn(`User @${username} not found`);
71163
71427
  return null;
71164
71428
  } catch (err) {
71165
- log32.warn(`Failed to lookup user @${username}: ${err}`);
71429
+ log33.warn(`Failed to lookup user @${username}: ${err}`);
71166
71430
  return null;
71167
71431
  }
71168
71432
  }
@@ -71250,19 +71514,19 @@ class SlackClient extends BasePlatformClient {
71250
71514
  }
71251
71515
  return null;
71252
71516
  } catch (err) {
71253
- log32.debug(`Post ${postId.substring(0, 12)} not found: ${err}`);
71517
+ log33.debug(`Post ${postId.substring(0, 12)} not found: ${err}`);
71254
71518
  return null;
71255
71519
  }
71256
71520
  }
71257
71521
  async deletePost(postId) {
71258
- log32.debug(`Deleting post ${postId.substring(0, 12)}`);
71522
+ log33.debug(`Deleting post ${postId.substring(0, 12)}`);
71259
71523
  await this.api("POST", "chat.delete", {
71260
71524
  channel: this.channelId,
71261
71525
  ts: postId
71262
71526
  });
71263
71527
  }
71264
71528
  async pinPost(postId) {
71265
- log32.debug(`Pinning post ${postId.substring(0, 12)}`);
71529
+ log33.debug(`Pinning post ${postId.substring(0, 12)}`);
71266
71530
  try {
71267
71531
  await this.api("POST", "pins.add", {
71268
71532
  channel: this.channelId,
@@ -71270,14 +71534,14 @@ class SlackClient extends BasePlatformClient {
71270
71534
  }, 0, ["already_pinned"]);
71271
71535
  } catch (err) {
71272
71536
  if (err instanceof Error && err.message.includes("already_pinned")) {
71273
- log32.debug(`Post ${postId.substring(0, 12)} already pinned`);
71537
+ log33.debug(`Post ${postId.substring(0, 12)} already pinned`);
71274
71538
  return;
71275
71539
  }
71276
71540
  throw err;
71277
71541
  }
71278
71542
  }
71279
71543
  async unpinPost(postId) {
71280
- log32.debug(`Unpinning post ${postId.substring(0, 12)}`);
71544
+ log33.debug(`Unpinning post ${postId.substring(0, 12)}`);
71281
71545
  try {
71282
71546
  await this.api("POST", "pins.remove", {
71283
71547
  channel: this.channelId,
@@ -71285,7 +71549,7 @@ class SlackClient extends BasePlatformClient {
71285
71549
  }, 0, ["no_pin"]);
71286
71550
  } catch (err) {
71287
71551
  if (err instanceof Error && err.message.includes("no_pin")) {
71288
- log32.debug(`Post ${postId.substring(0, 12)} was not pinned`);
71552
+ log33.debug(`Post ${postId.substring(0, 12)} was not pinned`);
71289
71553
  return;
71290
71554
  }
71291
71555
  throw err;
@@ -71303,7 +71567,7 @@ class SlackClient extends BasePlatformClient {
71303
71567
  if (message.length <= maxLength) {
71304
71568
  return message;
71305
71569
  }
71306
- log32.warn(`Truncating message from ${message.length} to ~${maxLength} chars`);
71570
+ log33.warn(`Truncating message from ${message.length} to ~${maxLength} chars`);
71307
71571
  return truncateMessageSafely(message, maxLength, "_... (truncated)_");
71308
71572
  }
71309
71573
  async getThreadHistory(threadId, options) {
@@ -71330,13 +71594,13 @@ class SlackClient extends BasePlatformClient {
71330
71594
  }
71331
71595
  return messages;
71332
71596
  } catch (err) {
71333
- log32.warn(`Failed to get thread history for ${threadId}: ${err}`);
71597
+ log33.warn(`Failed to get thread history for ${threadId}: ${err}`);
71334
71598
  return [];
71335
71599
  }
71336
71600
  }
71337
71601
  async addReaction(postId, emojiName) {
71338
71602
  const name = getEmojiName(emojiName);
71339
- log32.debug(`Adding reaction :${name}: to post ${postId.substring(0, 12)}`);
71603
+ log33.debug(`Adding reaction :${name}: to post ${postId.substring(0, 12)}`);
71340
71604
  await this.api("POST", "reactions.add", {
71341
71605
  channel: this.channelId,
71342
71606
  timestamp: postId,
@@ -71345,7 +71609,7 @@ class SlackClient extends BasePlatformClient {
71345
71609
  }
71346
71610
  async removeReaction(postId, emojiName) {
71347
71611
  const name = getEmojiName(emojiName);
71348
- log32.debug(`Removing reaction :${name}: from post ${postId.substring(0, 12)}`);
71612
+ log33.debug(`Removing reaction :${name}: from post ${postId.substring(0, 12)}`);
71349
71613
  await this.api("POST", "reactions.remove", {
71350
71614
  channel: this.channelId,
71351
71615
  timestamp: postId,
@@ -71371,7 +71635,7 @@ class SlackClient extends BasePlatformClient {
71371
71635
  }
71372
71636
  sendTyping(_threadId) {}
71373
71637
  async downloadFile(fileId) {
71374
- log32.debug(`Downloading file ${fileId}`);
71638
+ log33.debug(`Downloading file ${fileId}`);
71375
71639
  const fileInfo = await this.api("GET", `files.info?file=${fileId}`);
71376
71640
  const downloadUrl = fileInfo.file.url_private_download || fileInfo.file.url_private;
71377
71641
  if (!downloadUrl) {
@@ -71383,11 +71647,11 @@ class SlackClient extends BasePlatformClient {
71383
71647
  }
71384
71648
  });
71385
71649
  if (!response.ok) {
71386
- log32.warn(`Failed to download file ${fileId}: ${response.status}`);
71650
+ log33.warn(`Failed to download file ${fileId}: ${response.status}`);
71387
71651
  throw new Error(`Failed to download file ${fileId}: ${response.status}`);
71388
71652
  }
71389
71653
  const arrayBuffer = await response.arrayBuffer();
71390
- log32.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
71654
+ log33.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
71391
71655
  return Buffer.from(arrayBuffer);
71392
71656
  }
71393
71657
  async getFileInfo(fileId) {
@@ -72188,13 +72452,13 @@ import { EventEmitter as EventEmitter4 } from "events";
72188
72452
 
72189
72453
  // src/persistence/session-store.ts
72190
72454
  init_logger();
72191
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync4, chmodSync as chmodSync6 } from "fs";
72192
- import { homedir as homedir7 } from "os";
72193
- import { join as join12 } from "path";
72194
- var log33 = createLogger("persist");
72455
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync4, chmodSync as chmodSync7 } from "fs";
72456
+ import { homedir as homedir8 } from "os";
72457
+ import { join as join13 } from "path";
72458
+ var log34 = createLogger("persist");
72195
72459
  var STORE_VERSION3 = 2;
72196
- var DEFAULT_CONFIG_DIR3 = join12(homedir7(), ".config", "claude-threads");
72197
- var DEFAULT_SESSIONS_FILE = join12(DEFAULT_CONFIG_DIR3, "sessions.json");
72460
+ var DEFAULT_CONFIG_DIR3 = join13(homedir8(), ".config", "claude-threads");
72461
+ var DEFAULT_SESSIONS_FILE = join13(DEFAULT_CONFIG_DIR3, "sessions.json");
72198
72462
 
72199
72463
  class SessionStore {
72200
72464
  sessionsFile;
@@ -72204,25 +72468,25 @@ class SessionStore {
72204
72468
  const effectivePath = sessionsPath ?? envPath;
72205
72469
  if (effectivePath) {
72206
72470
  this.sessionsFile = effectivePath;
72207
- this.configDir = join12(effectivePath, "..");
72471
+ this.configDir = join13(effectivePath, "..");
72208
72472
  } else {
72209
72473
  this.sessionsFile = DEFAULT_SESSIONS_FILE;
72210
72474
  this.configDir = DEFAULT_CONFIG_DIR3;
72211
72475
  }
72212
72476
  if (!existsSync13(this.configDir)) {
72213
- mkdirSync6(this.configDir, { recursive: true });
72477
+ mkdirSync7(this.configDir, { recursive: true });
72214
72478
  }
72215
72479
  }
72216
72480
  load() {
72217
72481
  const sessions = new Map;
72218
72482
  if (!existsSync13(this.sessionsFile)) {
72219
- log33.debug("No sessions file found");
72483
+ log34.debug("No sessions file found");
72220
72484
  return sessions;
72221
72485
  }
72222
72486
  try {
72223
72487
  const data = this.loadRaw();
72224
72488
  if (data.version === 1) {
72225
- log33.info("Migrating sessions from v1 to v2 (adding platformId)");
72489
+ log34.info("Migrating sessions from v1 to v2 (adding platformId)");
72226
72490
  const newSessions = {};
72227
72491
  for (const [_oldKey, session] of Object.entries(data.sessions)) {
72228
72492
  const v1Session = session;
@@ -72236,7 +72500,7 @@ class SessionStore {
72236
72500
  data.version = 2;
72237
72501
  this.writeAtomic(data);
72238
72502
  } else if (data.version !== STORE_VERSION3) {
72239
- log33.warn(`Sessions file version ${data.version} not supported, starting fresh`);
72503
+ log34.warn(`Sessions file version ${data.version} not supported, starting fresh`);
72240
72504
  return sessions;
72241
72505
  }
72242
72506
  for (const session of Object.values(data.sessions)) {
@@ -72245,9 +72509,9 @@ class SessionStore {
72245
72509
  const sessionId = `${session.platformId}:${session.threadId}`;
72246
72510
  sessions.set(sessionId, session);
72247
72511
  }
72248
- log33.debug(`Loaded ${sessions.size} active session(s)`);
72512
+ log34.debug(`Loaded ${sessions.size} active session(s)`);
72249
72513
  } catch (err) {
72250
- log33.error(`Failed to load sessions: ${err}`);
72514
+ log34.error(`Failed to load sessions: ${err}`);
72251
72515
  }
72252
72516
  return sessions;
72253
72517
  }
@@ -72256,7 +72520,7 @@ class SessionStore {
72256
72520
  data.sessions[sessionId] = session;
72257
72521
  this.writeAtomic(data);
72258
72522
  const shortId = sessionId.substring(0, 20);
72259
- log33.debug(`Saved session ${shortId}...`);
72523
+ log34.debug(`Saved session ${shortId}...`);
72260
72524
  }
72261
72525
  remove(sessionId) {
72262
72526
  const data = this.loadRaw();
@@ -72264,7 +72528,7 @@ class SessionStore {
72264
72528
  delete data.sessions[sessionId];
72265
72529
  this.writeAtomic(data);
72266
72530
  const shortId = sessionId.substring(0, 20);
72267
- log33.debug(`Removed session ${shortId}...`);
72531
+ log34.debug(`Removed session ${shortId}...`);
72268
72532
  }
72269
72533
  }
72270
72534
  softDelete(sessionId) {
@@ -72273,7 +72537,7 @@ class SessionStore {
72273
72537
  data.sessions[sessionId].cleanedAt = new Date().toISOString();
72274
72538
  this.writeAtomic(data);
72275
72539
  const shortId = sessionId.substring(0, 20);
72276
- log33.debug(`Soft-deleted session ${shortId}...`);
72540
+ log34.debug(`Soft-deleted session ${shortId}...`);
72277
72541
  }
72278
72542
  }
72279
72543
  cleanStale(maxAgeMs) {
@@ -72291,7 +72555,7 @@ class SessionStore {
72291
72555
  }
72292
72556
  if (staleIds.length > 0) {
72293
72557
  this.writeAtomic(data);
72294
- log33.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
72558
+ log34.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
72295
72559
  }
72296
72560
  return staleIds;
72297
72561
  }
@@ -72310,7 +72574,7 @@ class SessionStore {
72310
72574
  }
72311
72575
  if (removedCount > 0) {
72312
72576
  this.writeAtomic(data);
72313
- log33.debug(`Permanently removed ${removedCount} old session(s) from history`);
72577
+ log34.debug(`Permanently removed ${removedCount} old session(s) from history`);
72314
72578
  }
72315
72579
  return removedCount;
72316
72580
  }
@@ -72337,7 +72601,7 @@ class SessionStore {
72337
72601
  clear() {
72338
72602
  const data = this.loadRaw();
72339
72603
  this.writeAtomic({ version: STORE_VERSION3, sessions: {}, stickyPostIds: data.stickyPostIds });
72340
- log33.debug("Cleared all sessions");
72604
+ log34.debug("Cleared all sessions");
72341
72605
  }
72342
72606
  saveStickyPostId(platformId, postId) {
72343
72607
  const data = this.loadRaw();
@@ -72346,7 +72610,7 @@ class SessionStore {
72346
72610
  }
72347
72611
  data.stickyPostIds[platformId] = postId;
72348
72612
  this.writeAtomic(data);
72349
- log33.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
72613
+ log34.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
72350
72614
  }
72351
72615
  getStickyPostIds() {
72352
72616
  const data = this.loadRaw();
@@ -72357,7 +72621,7 @@ class SessionStore {
72357
72621
  if (data.stickyPostIds && data.stickyPostIds[platformId]) {
72358
72622
  delete data.stickyPostIds[platformId];
72359
72623
  this.writeAtomic(data);
72360
- log33.debug(`Removed sticky post ID for ${platformId}`);
72624
+ log34.debug(`Removed sticky post ID for ${platformId}`);
72361
72625
  }
72362
72626
  }
72363
72627
  getPlatformEnabledState() {
@@ -72375,7 +72639,7 @@ class SessionStore {
72375
72639
  }
72376
72640
  data.platformEnabledState[platformId] = enabled;
72377
72641
  this.writeAtomic(data);
72378
- log33.debug(`Set platform ${platformId} enabled state to ${enabled}`);
72642
+ log34.debug(`Set platform ${platformId} enabled state to ${enabled}`);
72379
72643
  }
72380
72644
  findByThread(platformId, threadId) {
72381
72645
  const sessionId = `${platformId}:${threadId}`;
@@ -72439,13 +72703,13 @@ class SessionStore {
72439
72703
  const tempFile = `${this.sessionsFile}.tmp`;
72440
72704
  writeFileSync8(tempFile, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
72441
72705
  renameSync4(tempFile, this.sessionsFile);
72442
- chmodSync6(this.sessionsFile, 384);
72706
+ chmodSync7(this.sessionsFile, 384);
72443
72707
  }
72444
72708
  }
72445
72709
 
72446
72710
  // src/routines/scheduler.ts
72447
72711
  init_logger();
72448
- var log34 = createLogger("routines");
72712
+ var log35 = createLogger("routines");
72449
72713
  var DEFAULT_INTERVAL_MS2 = 60 * 1000;
72450
72714
  var FIRE_WINDOW_MS = 5 * 60 * 1000;
72451
72715
  var WEEKDAY_TO_ISO = {
@@ -72533,11 +72797,11 @@ class RoutineScheduler {
72533
72797
  if (this.timer)
72534
72798
  return;
72535
72799
  const safeTick = () => this.tick(new Date).catch((err) => {
72536
- log34.error(`Routine scheduler tick failed: ${err.message}`);
72800
+ log35.error(`Routine scheduler tick failed: ${err.message}`);
72537
72801
  });
72538
72802
  this.timer = setInterval(safeTick, this.intervalMs);
72539
72803
  safeTick();
72540
- log34.debug(`Routine scheduler started (interval: ${this.intervalMs / 1000}s)`);
72804
+ log35.debug(`Routine scheduler started (interval: ${this.intervalMs / 1000}s)`);
72541
72805
  }
72542
72806
  stop() {
72543
72807
  if (this.timer) {
@@ -72568,7 +72832,7 @@ class RoutineScheduler {
72568
72832
  try {
72569
72833
  status = await this.opts.fireRoutine(platformId, routine);
72570
72834
  } catch (err) {
72571
- log34.warn(`Routine "${routine.name}" (${platformId}) failed: ${err.message}`);
72835
+ log35.warn(`Routine "${routine.name}" (${platformId}) failed: ${err.message}`);
72572
72836
  status = "failed";
72573
72837
  }
72574
72838
  try {
@@ -72592,7 +72856,7 @@ class RoutineScheduler {
72592
72856
  }
72593
72857
  }
72594
72858
  } catch (err) {
72595
- log34.error(`Routine "${routine.name}" (${platformId}) bookkeeping failed: ${err.message}`);
72859
+ log35.error(`Routine "${routine.name}" (${platformId}) bookkeeping failed: ${err.message}`);
72596
72860
  }
72597
72861
  return status;
72598
72862
  }
@@ -72600,20 +72864,20 @@ class RoutineScheduler {
72600
72864
 
72601
72865
  // src/routines/runner.ts
72602
72866
  init_logger();
72603
- var log35 = createLogger("routines");
72867
+ var log36 = createLogger("routines");
72604
72868
  async function fireRoutine(routine, platformId, ctx) {
72605
72869
  const platforms = ctx.state.platforms;
72606
72870
  const platform = platforms.get(platformId);
72607
72871
  if (!platform) {
72608
- log35.debug(`Routine "${routine.name}": platform ${platformId} not registered — skipping`);
72872
+ log36.debug(`Routine "${routine.name}": platform ${platformId} not registered — skipping`);
72609
72873
  return "skipped";
72610
72874
  }
72611
72875
  if (!isAuthorizedForSession({ username: routine.createdBy, platform, sessionAllowedUsers: undefined })) {
72612
- log35.warn(`Routine "${routine.name}": creator @${routine.createdBy} no longer authorized on ${platformId}`);
72876
+ log36.warn(`Routine "${routine.name}": creator @${routine.createdBy} no longer authorized on ${platformId}`);
72613
72877
  return "unauthorized";
72614
72878
  }
72615
72879
  if (ctx.state.sessions.size >= ctx.config.maxSessions) {
72616
- log35.debug(`Routine "${routine.name}": at MAX_SESSIONS — skipping this tick`);
72880
+ log36.debug(`Routine "${routine.name}": at MAX_SESSIONS — skipping this tick`);
72617
72881
  return "skipped";
72618
72882
  }
72619
72883
  const formatter = platform.getFormatter();
@@ -72626,7 +72890,7 @@ ${routine.prompt}`,
72626
72890
  skipWorktreePrompt: true
72627
72891
  }, routine.createdBy, undefined, rootPost.id, platformId, ctx);
72628
72892
  if (!ctx.state.sessions.has(ctx.ops.getSessionId(platformId, rootPost.id))) {
72629
- log35.debug(`Routine "${routine.name}": startSession declined to start a session — skipping this tick`);
72893
+ log36.debug(`Routine "${routine.name}": startSession declined to start a session — skipping this tick`);
72630
72894
  return "skipped";
72631
72895
  }
72632
72896
  return "ok";
@@ -72638,7 +72902,7 @@ init_logger();
72638
72902
  // src/claude/usage-probe.ts
72639
72903
  init_spawn();
72640
72904
  init_logger();
72641
- var log36 = createLogger("usage-probe");
72905
+ var log37 = createLogger("usage-probe");
72642
72906
  var DEFAULT_USAGE_PROBE_TIMEOUT_MS = 30000;
72643
72907
  function parseUsageOutput(text) {
72644
72908
  if (!text)
@@ -72691,12 +72955,12 @@ async function probeAccountUsage(account, opts = {}) {
72691
72955
  stdio: ["ignore", "pipe", "pipe"]
72692
72956
  });
72693
72957
  } catch (err) {
72694
- log36.warn(`Failed to spawn /usage probe for "${account.id}": ${err}`);
72958
+ log37.warn(`Failed to spawn /usage probe for "${account.id}": ${err}`);
72695
72959
  resolve7(null);
72696
72960
  return;
72697
72961
  }
72698
72962
  const timer = setTimeout(() => {
72699
- log36.warn(`/usage probe for "${account.id}" timed out after ${timeoutMs}ms`);
72963
+ log37.warn(`/usage probe for "${account.id}" timed out after ${timeoutMs}ms`);
72700
72964
  try {
72701
72965
  child.kill("SIGKILL");
72702
72966
  } catch {}
@@ -72708,13 +72972,13 @@ async function probeAccountUsage(account, opts = {}) {
72708
72972
  });
72709
72973
  child.stderr?.on("data", () => {});
72710
72974
  child.on("error", (err) => {
72711
- log36.warn(`/usage probe for "${account.id}" errored: ${err}`);
72975
+ log37.warn(`/usage probe for "${account.id}" errored: ${err}`);
72712
72976
  finish(null);
72713
72977
  });
72714
72978
  child.on("close", () => {
72715
72979
  const usage = extractUsage(stdout);
72716
72980
  if (!usage) {
72717
- log36.debug(`/usage probe for "${account.id}" returned no parseable usage`);
72981
+ log37.debug(`/usage probe for "${account.id}" returned no parseable usage`);
72718
72982
  }
72719
72983
  finish(usage);
72720
72984
  });
@@ -72735,7 +72999,7 @@ function extractUsage(stdout) {
72735
72999
  }
72736
73000
 
72737
73001
  // src/claude/account-pool.ts
72738
- var log37 = createLogger("account-pool");
73002
+ var log38 = createLogger("account-pool");
72739
73003
  var ACTIVE_SESSION_LOAD_PENALTY = 5;
72740
73004
  function hashThreadId(threadId) {
72741
73005
  let h = 2166136261;
@@ -72758,11 +73022,11 @@ class AccountPool {
72758
73022
  this.accounts = (accounts ?? []).filter((acc) => {
72759
73023
  const hasAuth = !!acc.home || !!acc.apiKey;
72760
73024
  if (!hasAuth) {
72761
- log37.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
73025
+ log38.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
72762
73026
  return false;
72763
73027
  }
72764
73028
  if (acc.home && acc.apiKey) {
72765
- log37.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
73029
+ log38.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
72766
73030
  return false;
72767
73031
  }
72768
73032
  return true;
@@ -72792,7 +73056,7 @@ class AccountPool {
72792
73056
  this.incrementActive(preferred.id);
72793
73057
  return preferred;
72794
73058
  }
72795
- log37.warn(`Preferred account "${preferredId}" not in pool — falling back to usage balancing`);
73059
+ log38.warn(`Preferred account "${preferredId}" not in pool — falling back to usage balancing`);
72796
73060
  }
72797
73061
  const now = Date.now();
72798
73062
  const n = this.accounts.length;
@@ -72806,7 +73070,7 @@ class AccountPool {
72806
73070
  }
72807
73071
  const chosen = this.selectLeastLoaded(now);
72808
73072
  if (!chosen) {
72809
- log37.warn(`All ${n} accounts are in rate-limit cooldown`);
73073
+ log38.warn(`All ${n} accounts are in rate-limit cooldown`);
72810
73074
  return null;
72811
73075
  }
72812
73076
  this.incrementActive(chosen.id);
@@ -72853,19 +73117,19 @@ class AccountPool {
72853
73117
  return;
72854
73118
  this.usage.set(accountId, usage);
72855
73119
  if (usage) {
72856
- log37.debug(`Account "${accountId}" usage: ${usageLoadScore(usage)}% (load score)`);
73120
+ log38.debug(`Account "${accountId}" usage: ${usageLoadScore(usage)}% (load score)`);
72857
73121
  }
72858
73122
  }
72859
73123
  markCooling(accountId, untilEpochMs) {
72860
73124
  if (!this.byId.has(accountId)) {
72861
- log37.warn(`markCooling called for unknown account "${accountId}"`);
73125
+ log38.warn(`markCooling called for unknown account "${accountId}"`);
72862
73126
  return;
72863
73127
  }
72864
73128
  const existing = this.coolingUntil.get(accountId) ?? 0;
72865
73129
  if (untilEpochMs > existing) {
72866
73130
  this.coolingUntil.set(accountId, untilEpochMs);
72867
73131
  const minutes = Math.ceil((untilEpochMs - Date.now()) / 60000);
72868
- log37.info(`Account "${accountId}" cooling for ~${minutes}min`);
73132
+ log38.info(`Account "${accountId}" cooling for ~${minutes}min`);
72869
73133
  }
72870
73134
  }
72871
73135
  get(accountId) {
@@ -72894,9 +73158,9 @@ class AccountPool {
72894
73158
  init_logger();
72895
73159
  import { existsSync as existsSync14 } from "fs";
72896
73160
  import { readdir, rm as rm3 } from "fs/promises";
72897
- import { join as join13 } from "path";
73161
+ import { join as join14 } from "path";
72898
73162
  init_worktree();
72899
- var log38 = createLogger("cleanup");
73163
+ var log39 = createLogger("cleanup");
72900
73164
  var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
72901
73165
  var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
72902
73166
 
@@ -72919,17 +73183,17 @@ class CleanupScheduler {
72919
73183
  }
72920
73184
  start() {
72921
73185
  if (this.isRunning) {
72922
- log38.debug("Cleanup scheduler already running");
73186
+ log39.debug("Cleanup scheduler already running");
72923
73187
  return;
72924
73188
  }
72925
73189
  this.isRunning = true;
72926
- log38.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
73190
+ log39.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
72927
73191
  this.runCleanup().catch((err) => {
72928
- log38.warn(`Initial cleanup failed: ${err}`);
73192
+ log39.warn(`Initial cleanup failed: ${err}`);
72929
73193
  });
72930
73194
  this.timer = setInterval(() => {
72931
73195
  this.runCleanup().catch((err) => {
72932
- log38.warn(`Periodic cleanup failed: ${err}`);
73196
+ log39.warn(`Periodic cleanup failed: ${err}`);
72933
73197
  });
72934
73198
  }, this.intervalMs);
72935
73199
  }
@@ -72939,11 +73203,11 @@ class CleanupScheduler {
72939
73203
  this.timer = null;
72940
73204
  }
72941
73205
  this.isRunning = false;
72942
- log38.debug("Cleanup scheduler stopped");
73206
+ log39.debug("Cleanup scheduler stopped");
72943
73207
  }
72944
73208
  async runCleanup() {
72945
73209
  const startTime = Date.now();
72946
- log38.debug("Running background cleanup...");
73210
+ log39.debug("Running background cleanup...");
72947
73211
  const stats = {
72948
73212
  logsDeleted: 0,
72949
73213
  worktreesCleaned: 0,
@@ -72969,9 +73233,9 @@ class CleanupScheduler {
72969
73233
  const elapsed = Date.now() - startTime;
72970
73234
  const totalCleaned = stats.logsDeleted + stats.worktreesCleaned + stats.metadataCleaned;
72971
73235
  if (totalCleaned > 0 || stats.errors.length > 0) {
72972
- log38.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
73236
+ log39.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
72973
73237
  } else {
72974
- log38.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
73238
+ log39.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
72975
73239
  }
72976
73240
  return stats;
72977
73241
  }
@@ -72984,7 +73248,7 @@ class CleanupScheduler {
72984
73248
  const deleted = cleanupOldLogs(this.logRetentionDays);
72985
73249
  resolve7(deleted);
72986
73250
  } catch (err) {
72987
- log38.warn(`Log cleanup error: ${err}`);
73251
+ log39.warn(`Log cleanup error: ${err}`);
72988
73252
  resolve7(0);
72989
73253
  }
72990
73254
  });
@@ -72993,7 +73257,7 @@ class CleanupScheduler {
72993
73257
  const worktreesDir = getWorktreesDir();
72994
73258
  const result = { cleaned: 0, metadata: 0 };
72995
73259
  if (!existsSync14(worktreesDir)) {
72996
- log38.debug("No worktrees directory exists, nothing to clean");
73260
+ log39.debug("No worktrees directory exists, nothing to clean");
72997
73261
  return result;
72998
73262
  }
72999
73263
  const persisted = this.sessionStore.load();
@@ -73009,9 +73273,9 @@ class CleanupScheduler {
73009
73273
  for (const entry of entries) {
73010
73274
  if (!entry.isDirectory())
73011
73275
  continue;
73012
- const worktreePath = join13(worktreesDir, entry.name);
73276
+ const worktreePath = join14(worktreesDir, entry.name);
73013
73277
  if (activeWorktrees.has(worktreePath)) {
73014
- log38.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
73278
+ log39.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
73015
73279
  continue;
73016
73280
  }
73017
73281
  const meta = await readWorktreeMetadata(worktreePath);
@@ -73021,7 +73285,7 @@ class CleanupScheduler {
73021
73285
  const lastActivity = new Date(meta.lastActivityAt).getTime();
73022
73286
  const age = now - lastActivity;
73023
73287
  if (meta.sessionId && age < this.maxWorktreeAgeMs) {
73024
- log38.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
73288
+ log39.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
73025
73289
  continue;
73026
73290
  }
73027
73291
  const merged = age >= this.maxWorktreeAgeMs ? await isBranchMerged(meta.repoRoot, meta.branch).catch(() => false) : false;
@@ -73032,7 +73296,7 @@ class CleanupScheduler {
73032
73296
  shouldCleanup = true;
73033
73297
  cleanupReason = `inactive for ${Math.round(age / 3600000)}h`;
73034
73298
  } else {
73035
- log38.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
73299
+ log39.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
73036
73300
  continue;
73037
73301
  }
73038
73302
  } else {
@@ -73041,7 +73305,7 @@ class CleanupScheduler {
73041
73305
  }
73042
73306
  if (!shouldCleanup)
73043
73307
  continue;
73044
- log38.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
73308
+ log39.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
73045
73309
  try {
73046
73310
  if (meta?.repoRoot) {
73047
73311
  await removeWorktree(meta.repoRoot, worktreePath);
@@ -73052,19 +73316,19 @@ class CleanupScheduler {
73052
73316
  await removeWorktreeMetadata(worktreePath);
73053
73317
  result.metadata++;
73054
73318
  } catch (err) {
73055
- log38.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
73319
+ log39.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
73056
73320
  try {
73057
73321
  await rm3(worktreePath, { recursive: true, force: true });
73058
73322
  result.cleaned++;
73059
73323
  await removeWorktreeMetadata(worktreePath);
73060
73324
  result.metadata++;
73061
73325
  } catch (rmErr) {
73062
- log38.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
73326
+ log39.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
73063
73327
  }
73064
73328
  }
73065
73329
  }
73066
73330
  } catch (err) {
73067
- log38.warn(`Failed to scan worktrees directory: ${err}`);
73331
+ log39.warn(`Failed to scan worktrees directory: ${err}`);
73068
73332
  }
73069
73333
  return result;
73070
73334
  }
@@ -73072,8 +73336,8 @@ class CleanupScheduler {
73072
73336
  // src/operations/plugin/handler.ts
73073
73337
  init_spawn();
73074
73338
  init_logger();
73075
- var log39 = createLogger("plugin");
73076
- var sessionLog7 = createSessionLog(log39);
73339
+ var log40 = createLogger("plugin");
73340
+ var sessionLog7 = createSessionLog(log40);
73077
73341
  async function buildPluginRestartCliOptions(session, ctx) {
73078
73342
  const account = session.claudeAccountId ? ctx.ops.getClaudeAccount(session.claudeAccountId) : undefined;
73079
73343
  const memoryConfig = ctx.ops.getPlatformMemoryConfig(session.platformId);
@@ -73115,7 +73379,7 @@ async function runPluginCommand(args, cwd, timeout2 = 60000) {
73115
73379
  });
73116
73380
  proc.on("error", (err) => {
73117
73381
  resolve7({ stdout, stderr, exitCode: 1 });
73118
- log39.error(`Plugin command error: ${err.message}`);
73382
+ log40.error(`Plugin command error: ${err.message}`);
73119
73383
  });
73120
73384
  });
73121
73385
  }
@@ -73137,6 +73401,14 @@ async function handlePluginInstall(session, pluginName, username, ctx) {
73137
73401
  const formatter = session.platform.getFormatter();
73138
73402
  await post(session, "info", `\uD83D\uDCE6 Installing plugin: ${formatter.formatCode(pluginName)}...`);
73139
73403
  sessionLog7(session).info(`Installing plugin: ${pluginName} (requested by @${username})`);
73404
+ auditLog(session.platformId, {
73405
+ threadId: session.threadId,
73406
+ sessionId: session.sessionId,
73407
+ actor: username,
73408
+ kind: "command",
73409
+ tool: "plugin install",
73410
+ detail: pluginName
73411
+ });
73140
73412
  session.threadLogger?.logCommand("plugin install", pluginName, username);
73141
73413
  const result = await runPluginCommand(["install", pluginName], session.workingDir);
73142
73414
  if (result.exitCode !== 0) {
@@ -73160,6 +73432,14 @@ async function handlePluginUninstall(session, pluginName, username, ctx) {
73160
73432
  const formatter = session.platform.getFormatter();
73161
73433
  await post(session, "info", `\uD83D\uDDD1️ Uninstalling plugin: ${formatter.formatCode(pluginName)}...`);
73162
73434
  sessionLog7(session).info(`Uninstalling plugin: ${pluginName} (requested by @${username})`);
73435
+ auditLog(session.platformId, {
73436
+ threadId: session.threadId,
73437
+ sessionId: session.sessionId,
73438
+ actor: username,
73439
+ kind: "command",
73440
+ tool: "plugin uninstall",
73441
+ detail: pluginName
73442
+ });
73163
73443
  session.threadLogger?.logCommand("plugin uninstall", pluginName, username);
73164
73444
  const result = await runPluginCommand(["uninstall", pluginName], session.workingDir);
73165
73445
  if (result.exitCode !== 0) {
@@ -73289,7 +73569,7 @@ class SessionRegistry {
73289
73569
  // src/session/reaction-router.ts
73290
73570
  init_emoji();
73291
73571
  init_logger();
73292
- var log40 = createLogger("manager");
73572
+ var log41 = createLogger("manager");
73293
73573
  async function handleReaction(deps, platformId, postId, emojiName, username, action) {
73294
73574
  const normalizedEmoji = normalizeEmojiName(emojiName);
73295
73575
  if (action === "added" && isResumeEmoji(normalizedEmoji)) {
@@ -73304,7 +73584,7 @@ async function handleReaction(deps, platformId, postId, emojiName, username, act
73304
73584
  return;
73305
73585
  const ownerScoped = resolveApprovals(session.platform.approvals, isDcmThreadId(session.threadId)) === "owner";
73306
73586
  if (!session.sessionAllowedUsers.has(username) && (ownerScoped || !session.platform.isUserAllowed(username))) {
73307
- log40.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
73587
+ log41.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
73308
73588
  event: "reaction.rejected",
73309
73589
  platformId,
73310
73590
  sessionId: session.sessionId,
@@ -73342,8 +73622,8 @@ async function tryResumeFromReaction(deps, platformId, postId, username) {
73342
73622
  return false;
73343
73623
  }
73344
73624
  const shortId = persistedSession.threadId.substring(0, 8);
73345
- log40.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
73346
- await resumeSession(persistedSession, deps.getContext());
73625
+ log41.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
73626
+ await resumeSession(persistedSession, deps.getContext(), username);
73347
73627
  return true;
73348
73628
  }
73349
73629
  async function dispatch(deps, session, postId, emojiName, username, action) {
@@ -73372,7 +73652,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
73372
73652
  }
73373
73653
  if (session.lastError?.postId === postId && isBugReportEmoji(emojiName)) {
73374
73654
  if (session.startedBy === username || session.platform.isUserAllowed(username) || session.sessionAllowedUsers.has(username)) {
73375
- log40.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
73655
+ log41.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
73376
73656
  await reportBug(session, undefined, username, deps.getContext(), session.lastError);
73377
73657
  return;
73378
73658
  }
@@ -73387,7 +73667,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
73387
73667
 
73388
73668
  // src/session/manager.ts
73389
73669
  init_logger();
73390
- var log41 = createLogger("manager");
73670
+ var log42 = createLogger("manager");
73391
73671
  var USAGE_PROBE_TIMEOUT_MS = 1e4;
73392
73672
  var USAGE_REFRESH_DEADLINE_MS = 5000;
73393
73673
  var USAGE_CACHE_TTL_MS = 15000;
@@ -73497,7 +73777,7 @@ class SessionManager extends EventEmitter4 {
73497
73777
  markNeedsBump(platformId);
73498
73778
  this.updateStickyMessage();
73499
73779
  });
73500
- log41.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
73780
+ log42.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
73501
73781
  }
73502
73782
  removePlatform(platformId) {
73503
73783
  this.platforms.delete(platformId);
@@ -73517,7 +73797,7 @@ class SessionManager extends EventEmitter4 {
73517
73797
  if (users) {
73518
73798
  users.add(sessionId);
73519
73799
  }
73520
- log41.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
73800
+ log42.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
73521
73801
  }
73522
73802
  unregisterWorktreeUser(worktreePath, sessionId) {
73523
73803
  const users = this.worktreeUsers.get(worktreePath);
@@ -73723,7 +74003,7 @@ class SessionManager extends EventEmitter4 {
73723
74003
  try {
73724
74004
  this.persistSessionUnsafe(session);
73725
74005
  } catch (err) {
73726
- log41.error(`Failed to persist session ${session.sessionId}: ${err}`);
74006
+ log42.error(`Failed to persist session ${session.sessionId}: ${err}`);
73727
74007
  }
73728
74008
  }
73729
74009
  persistSessionUnsafe(session) {
@@ -73839,11 +74119,11 @@ class SessionManager extends EventEmitter4 {
73839
74119
  }
73840
74120
  }
73841
74121
  if (sessionsToKill.length === 0) {
73842
- log41.info(`No active sessions to pause for platform ${platformId}`);
74122
+ log42.info(`No active sessions to pause for platform ${platformId}`);
73843
74123
  await this.updateStickyMessage();
73844
74124
  return;
73845
74125
  }
73846
- log41.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
74126
+ log42.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
73847
74127
  for (const session of sessionsToKill) {
73848
74128
  try {
73849
74129
  const fmt = session.platform.getFormatter();
@@ -73859,9 +74139,9 @@ class SessionManager extends EventEmitter4 {
73859
74139
  session.claude.kill();
73860
74140
  this.registry.unregister(session.sessionId);
73861
74141
  this.emitSessionRemove(session.sessionId);
73862
- log41.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
74142
+ log42.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
73863
74143
  } catch (err) {
73864
- log41.warn(`Failed to pause session ${session.threadId}: ${err}`);
74144
+ log42.warn(`Failed to pause session ${session.threadId}: ${err}`);
73865
74145
  }
73866
74146
  }
73867
74147
  for (const session of sessionsToKill) {
@@ -73882,17 +74162,17 @@ class SessionManager extends EventEmitter4 {
73882
74162
  sessionsToResume.push(state);
73883
74163
  }
73884
74164
  if (sessionsToResume.length === 0) {
73885
- log41.info(`No paused sessions to resume for platform ${platformId}`);
74165
+ log42.info(`No paused sessions to resume for platform ${platformId}`);
73886
74166
  await this.updateStickyMessage();
73887
74167
  return;
73888
74168
  }
73889
- log41.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
74169
+ log42.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
73890
74170
  for (const state of sessionsToResume) {
73891
74171
  try {
73892
74172
  await resumeSession(state, this.getContext());
73893
- log41.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
74173
+ log42.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
73894
74174
  } catch (err) {
73895
- log41.warn(`Failed to resume session ${state.threadId}: ${err}`);
74175
+ log42.warn(`Failed to resume session ${state.threadId}: ${err}`);
73896
74176
  }
73897
74177
  }
73898
74178
  await this.updateStickyMessage();
@@ -73931,14 +74211,14 @@ class SessionManager extends EventEmitter4 {
73931
74211
  const sessionTimeoutMs = this.limits.sessionTimeoutMinutes * 60 * 1000;
73932
74212
  const staleIds = this.sessionStore.cleanStale(sessionTimeoutMs * 2);
73933
74213
  if (staleIds.length > 0) {
73934
- log41.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
74214
+ log42.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
73935
74215
  }
73936
74216
  const removedCount = this.sessionStore.cleanHistory();
73937
74217
  if (removedCount > 0) {
73938
- log41.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
74218
+ log42.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
73939
74219
  }
73940
74220
  const persisted = this.sessionStore.load();
73941
- log41.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
74221
+ log42.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
73942
74222
  const excludePostIdsByPlatform = new Map;
73943
74223
  for (const session of persisted.values()) {
73944
74224
  const platformId = session.platformId;
@@ -73958,10 +74238,10 @@ class SessionManager extends EventEmitter4 {
73958
74238
  const excludePostIds = excludePostIdsByPlatform.get(platform.platformId);
73959
74239
  platform.getBotUser().then((botUser) => {
73960
74240
  cleanupOldStickyMessages(platform, botUser.id, true, excludePostIds).catch((err) => {
73961
- log41.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
74241
+ log42.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
73962
74242
  });
73963
74243
  }).catch((err) => {
73964
- log41.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
74244
+ log42.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
73965
74245
  });
73966
74246
  }
73967
74247
  if (persisted.size > 0) {
@@ -73975,10 +74255,10 @@ class SessionManager extends EventEmitter4 {
73975
74255
  }
73976
74256
  }
73977
74257
  if (pausedToSkip.length > 0) {
73978
- log41.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
74258
+ log42.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
73979
74259
  }
73980
74260
  if (activeToResume.length > 0) {
73981
- log41.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
74261
+ log42.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
73982
74262
  for (const state of activeToResume) {
73983
74263
  await resumeSession(state, this.getContext());
73984
74264
  }
@@ -74456,7 +74736,7 @@ Mention me to start a session in this worktree.`, threadId);
74456
74736
  const message = messageBuilder(formatter);
74457
74737
  await post(session, "info", message);
74458
74738
  } catch (err) {
74459
- log41.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
74739
+ log42.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
74460
74740
  }
74461
74741
  }
74462
74742
  }
@@ -74475,7 +74755,7 @@ Mention me to start a session in this worktree.`, threadId);
74475
74755
  session.messageManager?.setPendingUpdatePrompt({ postId: post2.id });
74476
74756
  this.registerPost(post2.id, session.threadId);
74477
74757
  } catch (err) {
74478
- log41.warn(`Failed to post ask message to ${threadId}: ${err}`);
74758
+ log42.warn(`Failed to post ask message to ${threadId}: ${err}`);
74479
74759
  }
74480
74760
  }
74481
74761
  }
@@ -82070,29 +82350,29 @@ function SessionLog({ logs, maxLines = 20 }) {
82070
82350
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
82071
82351
  flexDirection: "column",
82072
82352
  flexShrink: 0,
82073
- children: displayLogs.map((log42) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
82353
+ children: displayLogs.map((log43) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
82074
82354
  flexShrink: 0,
82075
82355
  children: [
82076
82356
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
82077
- color: getColorForLevel(log42.level),
82357
+ color: getColorForLevel(log43.level),
82078
82358
  dimColor: true,
82079
82359
  wrap: "truncate",
82080
82360
  children: [
82081
82361
  "[",
82082
- padComponent(log42.component),
82362
+ padComponent(log43.component),
82083
82363
  "]"
82084
82364
  ]
82085
82365
  }, undefined, true, undefined, this),
82086
82366
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
82087
- color: getColorForLevel(log42.level),
82367
+ color: getColorForLevel(log43.level),
82088
82368
  wrap: "truncate",
82089
82369
  children: [
82090
82370
  " ",
82091
- log42.message
82371
+ log43.message
82092
82372
  ]
82093
82373
  }, undefined, true, undefined, this)
82094
82374
  ]
82095
- }, log42.id, true, undefined, this))
82375
+ }, log43.id, true, undefined, this))
82096
82376
  }, undefined, false, undefined, this);
82097
82377
  }
82098
82378
  // src/ui/components/Footer.tsx
@@ -82616,7 +82896,7 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
82616
82896
  const scrollRef = import_react59.default.useRef(null);
82617
82897
  const { stdout } = use_stdout_default();
82618
82898
  const isDebug = process.env.DEBUG === "1";
82619
- const displayLogs = logs.filter((log42) => isDebug || log42.level !== "debug");
82899
+ const displayLogs = logs.filter((log43) => isDebug || log43.level !== "debug");
82620
82900
  const visibleLogs = displayLogs.slice(-Math.max(maxLines * 3, 100));
82621
82901
  import_react59.default.useEffect(() => {
82622
82902
  const handleResize = () => scrollRef.current?.remeasure();
@@ -82656,25 +82936,25 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
82656
82936
  overflow: "hidden",
82657
82937
  children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ScrollView, {
82658
82938
  ref: scrollRef,
82659
- children: visibleLogs.map((log42) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
82939
+ children: visibleLogs.map((log43) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
82660
82940
  children: [
82661
82941
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
82662
82942
  dimColor: true,
82663
82943
  children: [
82664
82944
  "[",
82665
- padComponent2(log42.component),
82945
+ padComponent2(log43.component),
82666
82946
  "]"
82667
82947
  ]
82668
82948
  }, undefined, true, undefined, this),
82669
82949
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
82670
- color: getLevelColor(log42.level),
82950
+ color: getLevelColor(log43.level),
82671
82951
  children: [
82672
82952
  " ",
82673
- log42.message
82953
+ log43.message
82674
82954
  ]
82675
82955
  }, undefined, true, undefined, this)
82676
82956
  ]
82677
- }, log42.id, true, undefined, this))
82957
+ }, log43.id, true, undefined, this))
82678
82958
  }, undefined, false, undefined, this)
82679
82959
  }, undefined, false, undefined, this);
82680
82960
  }
@@ -83200,10 +83480,10 @@ function useAppState(initialConfig) {
83200
83480
  });
83201
83481
  }, []);
83202
83482
  const getLogsForSession = import_react60.useCallback((sessionId) => {
83203
- return state.logs.filter((log42) => log42.sessionId === sessionId);
83483
+ return state.logs.filter((log43) => log43.sessionId === sessionId);
83204
83484
  }, [state.logs]);
83205
83485
  const getGlobalLogs = import_react60.useCallback(() => {
83206
- return state.logs.filter((log42) => !log42.sessionId);
83486
+ return state.logs.filter((log43) => !log43.sessionId);
83207
83487
  }, [state.logs]);
83208
83488
  const togglePlatformEnabled = import_react60.useCallback((platformId) => {
83209
83489
  let newEnabled = false;
@@ -84006,6 +84286,16 @@ async function startUI(options) {
84006
84286
  init_logger();
84007
84287
 
84008
84288
  // src/message-handler.ts
84289
+ init_logger();
84290
+ var ackLog = createLogger("ack");
84291
+ function ackReceipt(client, postId) {
84292
+ const emoji = resolveAckReaction(client.ackReaction);
84293
+ if (!emoji)
84294
+ return;
84295
+ Promise.resolve(client.addReaction(postId, emoji)).catch((err) => {
84296
+ ackLog.debug(`ack reaction '${emoji}' failed on ${postId}: ${err}`);
84297
+ });
84298
+ }
84009
84299
  async function handleMessage(client, session, post2, user, options) {
84010
84300
  const { platformId, logger, onKill } = options;
84011
84301
  const dcm = resolveDirectChannelMode(options.directChannelMode);
@@ -84020,6 +84310,12 @@ async function handleMessage(client, session, post2, user, options) {
84020
84310
  await client.createPost(`⛔ Only authorized users can use ${formatter.formatCode("!kill")}`, threadRoot);
84021
84311
  return;
84022
84312
  }
84313
+ auditLog(platformId, {
84314
+ threadId: threadRoot,
84315
+ actor: username,
84316
+ kind: "command",
84317
+ tool: "kill"
84318
+ });
84023
84319
  const activeCount = session.registry.getActiveThreadIds().length;
84024
84320
  try {
84025
84321
  await client.createPost(`\uD83D\uDD34 ${formatter.formatBold("EMERGENCY SHUTDOWN")} initiated by ${formatter.formatUserMention(username)} - killing ${activeCount} active session${activeCount !== 1 ? "s" : ""}`, threadRoot);
@@ -84103,8 +84399,10 @@ async function handleMessage(client, session, post2, user, options) {
84103
84399
  return;
84104
84400
  }
84105
84401
  const files2 = post2.metadata?.files;
84106
- if (content || files2?.length)
84402
+ if (content || files2?.length) {
84403
+ ackReceipt(client, post2.id);
84107
84404
  await session.sendFollowUp(threadRoot, content, files2, username, user?.displayName);
84405
+ }
84108
84406
  return;
84109
84407
  }
84110
84408
  const hasPausedSession = session.registry.getPersistedByThreadId(threadRoot) !== undefined;
@@ -84121,6 +84419,13 @@ async function handleMessage(client, session, post2, user, options) {
84121
84419
  if (persistedSession2) {
84122
84420
  const allowedUsers = new Set(persistedSession2.sessionAllowedUsers);
84123
84421
  if (allowedUsers.has(username) || client.isUserAllowed(username)) {
84422
+ auditLog(platformId, {
84423
+ threadId: threadRoot,
84424
+ actor: username,
84425
+ kind: "command",
84426
+ tool: "stop",
84427
+ detail: "paused session cancelled"
84428
+ });
84124
84429
  session.cancelPausedSession(threadRoot);
84125
84430
  await client.createPost(`\uD83D\uDED1 ${formatter.formatBold("Session cancelled")} by ${formatter.formatUserMention(username)}`, threadRoot);
84126
84431
  }
@@ -84142,6 +84447,7 @@ async function handleMessage(client, session, post2, user, options) {
84142
84447
  }
84143
84448
  const files2 = post2.metadata?.files;
84144
84449
  if (content || files2?.length) {
84450
+ ackReceipt(client, post2.id);
84145
84451
  await session.resumePausedSession(threadRoot, content, files2, username);
84146
84452
  }
84147
84453
  return;
@@ -84210,6 +84516,7 @@ async function handleMessage(client, session, post2, user, options) {
84210
84516
  await client.createPost(`Mention me with your request`, threadRoot);
84211
84517
  return;
84212
84518
  }
84519
+ ackReceipt(client, post2.id);
84213
84520
  if (worktreeBranch) {
84214
84521
  await session.startSessionWithWorktree({ prompt, files }, worktreeBranch, username, threadRoot, platformId, user?.displayName, post2.id, initialOptions);
84215
84522
  return;
@@ -84233,7 +84540,7 @@ import { EventEmitter as EventEmitter9 } from "events";
84233
84540
  // src/auto-update/checker.ts
84234
84541
  init_logger();
84235
84542
  import { EventEmitter as EventEmitter7 } from "events";
84236
- var log42 = createLogger("checker");
84543
+ var log43 = createLogger("checker");
84237
84544
  var PACKAGE_NAME = "claude-threads";
84238
84545
  function compareVersions(a, b) {
84239
84546
  const partsA = a.replace(/^v/, "").split(".").map(Number);
@@ -84256,13 +84563,13 @@ async function fetchLatestVersion() {
84256
84563
  }
84257
84564
  });
84258
84565
  if (!response.ok) {
84259
- log42.warn(`Failed to fetch latest version: HTTP ${response.status}`);
84566
+ log43.warn(`Failed to fetch latest version: HTTP ${response.status}`);
84260
84567
  return null;
84261
84568
  }
84262
84569
  const data = await response.json();
84263
84570
  return data.version ?? null;
84264
84571
  } catch (err) {
84265
- log42.warn(`Failed to fetch latest version: ${err}`);
84572
+ log43.warn(`Failed to fetch latest version: ${err}`);
84266
84573
  return null;
84267
84574
  }
84268
84575
  }
@@ -84279,38 +84586,38 @@ class UpdateChecker extends EventEmitter7 {
84279
84586
  }
84280
84587
  start() {
84281
84588
  if (!this.config.enabled) {
84282
- log42.debug("Auto-update disabled, not starting checker");
84589
+ log43.debug("Auto-update disabled, not starting checker");
84283
84590
  return;
84284
84591
  }
84285
84592
  setTimeout(() => {
84286
84593
  this.check().catch((err) => {
84287
- log42.warn(`Initial update check failed: ${err}`);
84594
+ log43.warn(`Initial update check failed: ${err}`);
84288
84595
  });
84289
84596
  }, 5000);
84290
84597
  const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
84291
84598
  this.checkInterval = setInterval(() => {
84292
84599
  this.check().catch((err) => {
84293
- log42.warn(`Periodic update check failed: ${err}`);
84600
+ log43.warn(`Periodic update check failed: ${err}`);
84294
84601
  });
84295
84602
  }, intervalMs);
84296
- log42.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
84603
+ log43.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
84297
84604
  }
84298
84605
  stop() {
84299
84606
  if (this.checkInterval) {
84300
84607
  clearInterval(this.checkInterval);
84301
84608
  this.checkInterval = null;
84302
84609
  }
84303
- log42.debug("Update checker stopped");
84610
+ log43.debug("Update checker stopped");
84304
84611
  }
84305
84612
  async check() {
84306
84613
  if (this.isChecking) {
84307
- log42.debug("Check already in progress, skipping");
84614
+ log43.debug("Check already in progress, skipping");
84308
84615
  return this.lastUpdateInfo;
84309
84616
  }
84310
84617
  this.isChecking = true;
84311
84618
  this.emit("check:start");
84312
84619
  try {
84313
- log42.debug("Checking for updates...");
84620
+ log43.debug("Checking for updates...");
84314
84621
  const latestVersion2 = await fetchLatestVersion();
84315
84622
  if (!latestVersion2) {
84316
84623
  this.emit("check:complete", false);
@@ -84327,18 +84634,18 @@ class UpdateChecker extends EventEmitter7 {
84327
84634
  detectedAt: new Date
84328
84635
  };
84329
84636
  if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
84330
- log42.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
84637
+ log43.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
84331
84638
  this.lastUpdateInfo = updateInfo;
84332
84639
  this.emit("update", updateInfo);
84333
84640
  }
84334
84641
  this.emit("check:complete", true);
84335
84642
  return updateInfo;
84336
84643
  }
84337
- log42.debug(`Up to date (v${currentVersion})`);
84644
+ log43.debug(`Up to date (v${currentVersion})`);
84338
84645
  this.emit("check:complete", false);
84339
84646
  return null;
84340
84647
  } catch (err) {
84341
- log42.warn(`Update check failed: ${err}`);
84648
+ log43.warn(`Update check failed: ${err}`);
84342
84649
  this.emit("check:error", err);
84343
84650
  return null;
84344
84651
  } finally {
@@ -84409,7 +84716,7 @@ function isInScheduledWindow(window2) {
84409
84716
  }
84410
84717
 
84411
84718
  // src/auto-update/scheduler.ts
84412
- var log43 = createLogger("scheduler");
84719
+ var log44 = createLogger("scheduler");
84413
84720
 
84414
84721
  class UpdateScheduler extends EventEmitter8 {
84415
84722
  config;
@@ -84433,7 +84740,7 @@ class UpdateScheduler extends EventEmitter8 {
84433
84740
  scheduleUpdate(updateInfo) {
84434
84741
  this.pendingUpdate = updateInfo;
84435
84742
  if (this.config.autoRestartMode === "immediate") {
84436
- log43.info("Immediate mode: triggering update now");
84743
+ log44.info("Immediate mode: triggering update now");
84437
84744
  this.emit("ready", updateInfo);
84438
84745
  return;
84439
84746
  }
@@ -84446,19 +84753,19 @@ class UpdateScheduler extends EventEmitter8 {
84446
84753
  this.scheduledRestartAt = null;
84447
84754
  this.askApprovals.clear();
84448
84755
  this.askStartTime = null;
84449
- log43.debug("Update schedule cancelled");
84756
+ log44.debug("Update schedule cancelled");
84450
84757
  }
84451
84758
  deferUpdate(minutes) {
84452
84759
  const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
84453
84760
  this.scheduledRestartAt = null;
84454
84761
  this.idleStartTime = null;
84455
84762
  this.emit("deferred", deferUntil);
84456
- log43.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
84763
+ log44.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
84457
84764
  return deferUntil;
84458
84765
  }
84459
84766
  recordAskResponse(threadId, approved) {
84460
84767
  this.askApprovals.set(threadId, approved);
84461
- log43.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
84768
+ log44.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
84462
84769
  this.checkAskCondition();
84463
84770
  }
84464
84771
  getScheduledRestartAt() {
@@ -84479,7 +84786,7 @@ class UpdateScheduler extends EventEmitter8 {
84479
84786
  return;
84480
84787
  this.checkCondition();
84481
84788
  this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
84482
- log43.debug(`Started checking for ${this.config.autoRestartMode} condition`);
84789
+ log44.debug(`Started checking for ${this.config.autoRestartMode} condition`);
84483
84790
  }
84484
84791
  stopChecking() {
84485
84792
  if (this.checkTimer) {
@@ -84510,17 +84817,17 @@ class UpdateScheduler extends EventEmitter8 {
84510
84817
  if (activity.activeSessionCount === 0) {
84511
84818
  if (!this.idleStartTime) {
84512
84819
  this.idleStartTime = new Date;
84513
- log43.debug("No active sessions, starting idle timer");
84820
+ log44.debug("No active sessions, starting idle timer");
84514
84821
  }
84515
84822
  const idleMs = Date.now() - this.idleStartTime.getTime();
84516
84823
  const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
84517
84824
  if (idleMs >= requiredMs) {
84518
- log43.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
84825
+ log44.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
84519
84826
  this.triggerCountdown();
84520
84827
  }
84521
84828
  } else {
84522
84829
  if (this.idleStartTime) {
84523
- log43.debug("Sessions became active, resetting idle timer");
84830
+ log44.debug("Sessions became active, resetting idle timer");
84524
84831
  this.idleStartTime = null;
84525
84832
  }
84526
84833
  }
@@ -84531,7 +84838,7 @@ class UpdateScheduler extends EventEmitter8 {
84531
84838
  const quietMs = Date.now() - activity.lastActivityAt.getTime();
84532
84839
  const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
84533
84840
  if (quietMs >= requiredMs && !activity.anySessionBusy) {
84534
- log43.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
84841
+ log44.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
84535
84842
  this.triggerCountdown();
84536
84843
  }
84537
84844
  } else if (activity.activeSessionCount === 0) {
@@ -84541,7 +84848,7 @@ class UpdateScheduler extends EventEmitter8 {
84541
84848
  const idleMs = Date.now() - this.idleStartTime.getTime();
84542
84849
  const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
84543
84850
  if (idleMs >= requiredMs) {
84544
- log43.info("No sessions and quiet timeout reached, triggering update");
84851
+ log44.info("No sessions and quiet timeout reached, triggering update");
84545
84852
  this.triggerCountdown();
84546
84853
  }
84547
84854
  }
@@ -84552,13 +84859,13 @@ class UpdateScheduler extends EventEmitter8 {
84552
84859
  }
84553
84860
  const activity = this.getSessionActivity();
84554
84861
  if (activity.activeSessionCount === 0) {
84555
- log43.info("Within scheduled window and no active sessions, triggering update");
84862
+ log44.info("Within scheduled window and no active sessions, triggering update");
84556
84863
  this.triggerCountdown();
84557
84864
  } else if (activity.lastActivityAt) {
84558
84865
  const quietMs = Date.now() - activity.lastActivityAt.getTime();
84559
84866
  const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
84560
84867
  if (quietMs >= requiredMs && !activity.anySessionBusy) {
84561
- log43.info("Within scheduled window and sessions quiet, triggering update");
84868
+ log44.info("Within scheduled window and sessions quiet, triggering update");
84562
84869
  this.triggerCountdown();
84563
84870
  }
84564
84871
  }
@@ -84566,14 +84873,14 @@ class UpdateScheduler extends EventEmitter8 {
84566
84873
  checkAskCondition() {
84567
84874
  const threadIds = this.getActiveThreadIds();
84568
84875
  if (threadIds.length === 0) {
84569
- log43.info("No active threads, proceeding with update");
84876
+ log44.info("No active threads, proceeding with update");
84570
84877
  this.triggerCountdown();
84571
84878
  return;
84572
84879
  }
84573
84880
  if (!this.askStartTime && this.pendingUpdate) {
84574
84881
  this.askStartTime = new Date;
84575
84882
  this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
84576
- log43.warn(`Failed to post ask message: ${err}`);
84883
+ log44.warn(`Failed to post ask message: ${err}`);
84577
84884
  });
84578
84885
  return;
84579
84886
  }
@@ -84586,12 +84893,12 @@ class UpdateScheduler extends EventEmitter8 {
84586
84893
  denials++;
84587
84894
  }
84588
84895
  if (approvals > threadIds.length / 2) {
84589
- log43.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
84896
+ log44.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
84590
84897
  this.triggerCountdown();
84591
84898
  return;
84592
84899
  }
84593
84900
  if (denials > threadIds.length / 2) {
84594
- log43.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
84901
+ log44.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
84595
84902
  this.deferUpdate(60);
84596
84903
  return;
84597
84904
  }
@@ -84599,7 +84906,7 @@ class UpdateScheduler extends EventEmitter8 {
84599
84906
  const elapsedMs = Date.now() - this.askStartTime.getTime();
84600
84907
  const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
84601
84908
  if (elapsedMs >= timeoutMs) {
84602
- log43.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
84909
+ log44.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
84603
84910
  this.triggerCountdown();
84604
84911
  }
84605
84912
  }
@@ -84619,7 +84926,7 @@ class UpdateScheduler extends EventEmitter8 {
84619
84926
  this.emit("ready", this.pendingUpdate);
84620
84927
  }
84621
84928
  }, 1000);
84622
- log43.info("Update countdown started (60 seconds)");
84929
+ log44.info("Update countdown started (60 seconds)");
84623
84930
  }
84624
84931
  stopCountdown() {
84625
84932
  if (this.countdownTimer) {
@@ -84632,27 +84939,27 @@ class UpdateScheduler extends EventEmitter8 {
84632
84939
  // src/auto-update/installer.ts
84633
84940
  init_logger();
84634
84941
  import { spawn as spawn4, spawnSync } from "child_process";
84635
- import { existsSync as existsSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7 } from "fs";
84942
+ import { existsSync as existsSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8 } from "fs";
84636
84943
  import { dirname as dirname9, resolve as resolve7 } from "path";
84637
- import { homedir as homedir8 } from "os";
84638
- var log44 = createLogger("installer");
84944
+ import { homedir as homedir9 } from "os";
84945
+ var log45 = createLogger("installer");
84639
84946
  function detectPackageManager() {
84640
84947
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
84641
84948
  const originalInstaller = detectOriginalInstaller();
84642
84949
  if (originalInstaller) {
84643
- log44.debug(`Detected original installer: ${originalInstaller}`);
84950
+ log45.debug(`Detected original installer: ${originalInstaller}`);
84644
84951
  if (originalInstaller === "bun") {
84645
84952
  const bunCheck2 = spawnSync("bun", ["--version"], { stdio: "ignore" });
84646
84953
  if (bunCheck2.status === 0) {
84647
84954
  return { cmd: "bun", isBun: true };
84648
84955
  }
84649
- log44.warn("Originally installed with bun, but bun not found. Falling back to npm.");
84956
+ log45.warn("Originally installed with bun, but bun not found. Falling back to npm.");
84650
84957
  } else {
84651
84958
  const npmCheck2 = spawnSync(npmCmd, ["--version"], { stdio: "ignore" });
84652
84959
  if (npmCheck2.status === 0) {
84653
84960
  return { cmd: npmCmd, isBun: false };
84654
84961
  }
84655
- log44.warn("Originally installed with npm, but npm not found. Falling back to bun.");
84962
+ log45.warn("Originally installed with npm, but npm not found. Falling back to bun.");
84656
84963
  }
84657
84964
  }
84658
84965
  const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore" });
@@ -84674,7 +84981,7 @@ function normalizePath(p) {
84674
84981
  function detectOriginalInstaller() {
84675
84982
  try {
84676
84983
  const scriptPath = normalizePath(process.argv[1] || "");
84677
- const bunGlobalDir = normalizePath(process.env.BUN_INSTALL || resolve7(homedir8(), ".bun"));
84984
+ const bunGlobalDir = normalizePath(process.env.BUN_INSTALL || resolve7(homedir9(), ".bun"));
84678
84985
  if (scriptPath.startsWith(bunGlobalDir)) {
84679
84986
  return "bun";
84680
84987
  }
@@ -84694,7 +85001,7 @@ function detectOriginalInstaller() {
84694
85001
  return null;
84695
85002
  }
84696
85003
  }
84697
- var STATE_PATH = resolve7(homedir8(), ".config", "claude-threads", UPDATE_STATE_FILENAME);
85004
+ var STATE_PATH = resolve7(homedir9(), ".config", "claude-threads", UPDATE_STATE_FILENAME);
84698
85005
  var PACKAGE_NAME2 = "claude-threads";
84699
85006
  function loadUpdateState() {
84700
85007
  try {
@@ -84703,7 +85010,7 @@ function loadUpdateState() {
84703
85010
  return JSON.parse(content);
84704
85011
  }
84705
85012
  } catch (err) {
84706
- log44.warn(`Failed to load update state: ${err}`);
85013
+ log45.warn(`Failed to load update state: ${err}`);
84707
85014
  }
84708
85015
  return {};
84709
85016
  }
@@ -84711,12 +85018,12 @@ function saveUpdateState(state) {
84711
85018
  try {
84712
85019
  const dir = dirname9(STATE_PATH);
84713
85020
  if (!existsSync16(dir)) {
84714
- mkdirSync7(dir, { recursive: true });
85021
+ mkdirSync8(dir, { recursive: true });
84715
85022
  }
84716
85023
  writeFileSync9(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
84717
- log44.debug("Update state saved");
85024
+ log45.debug("Update state saved");
84718
85025
  } catch (err) {
84719
- log44.warn(`Failed to save update state: ${err}`);
85026
+ log45.warn(`Failed to save update state: ${err}`);
84720
85027
  }
84721
85028
  }
84722
85029
  function clearUpdateState() {
@@ -84725,7 +85032,7 @@ function clearUpdateState() {
84725
85032
  writeFileSync9(STATE_PATH, "{}", "utf-8");
84726
85033
  }
84727
85034
  } catch (err) {
84728
- log44.warn(`Failed to clear update state: ${err}`);
85035
+ log45.warn(`Failed to clear update state: ${err}`);
84729
85036
  }
84730
85037
  }
84731
85038
  function checkJustUpdated() {
@@ -84757,11 +85064,11 @@ function clearRuntimeSettings() {
84757
85064
  }
84758
85065
  }
84759
85066
  async function installVersion(version) {
84760
- log44.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
85067
+ log45.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
84761
85068
  const pm = detectPackageManager();
84762
85069
  if (!pm) {
84763
85070
  const error = "Neither bun nor npm found in PATH. Cannot install update.";
84764
- log44.error(`❌ ${error}`);
85071
+ log45.error(`❌ ${error}`);
84765
85072
  return { success: false, error };
84766
85073
  }
84767
85074
  saveUpdateState({
@@ -84773,7 +85080,7 @@ async function installVersion(version) {
84773
85080
  return new Promise((resolve8) => {
84774
85081
  const { cmd, isBun: isBun3 } = pm;
84775
85082
  const args = ["install", "-g", `${PACKAGE_NAME2}@${version}`];
84776
- log44.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
85083
+ log45.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
84777
85084
  const child = spawn4(cmd, args, {
84778
85085
  stdio: ["ignore", "pipe", "pipe"],
84779
85086
  env: {
@@ -84791,7 +85098,7 @@ async function installVersion(version) {
84791
85098
  });
84792
85099
  child.on("close", (code) => {
84793
85100
  if (code === 0) {
84794
- log44.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
85101
+ log45.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
84795
85102
  saveUpdateState({
84796
85103
  previousVersion: VERSION,
84797
85104
  targetVersion: version,
@@ -84801,20 +85108,20 @@ async function installVersion(version) {
84801
85108
  resolve8({ success: true });
84802
85109
  } else {
84803
85110
  const errorMsg = stderr || stdout || `Exit code: ${code}`;
84804
- log44.error(`❌ Installation failed: ${errorMsg}`);
85111
+ log45.error(`❌ Installation failed: ${errorMsg}`);
84805
85112
  clearUpdateState();
84806
85113
  resolve8({ success: false, error: errorMsg });
84807
85114
  }
84808
85115
  });
84809
85116
  child.on("error", (err) => {
84810
- log44.error(`❌ Failed to spawn npm: ${err}`);
85117
+ log45.error(`❌ Failed to spawn npm: ${err}`);
84811
85118
  clearUpdateState();
84812
85119
  resolve8({ success: false, error: err.message });
84813
85120
  });
84814
85121
  setTimeout(() => {
84815
85122
  if (child.exitCode === null) {
84816
85123
  child.kill();
84817
- log44.error("❌ Installation timed out");
85124
+ log45.error("❌ Installation timed out");
84818
85125
  clearUpdateState();
84819
85126
  resolve8({ success: false, error: "Installation timed out" });
84820
85127
  }
@@ -84859,8 +85166,8 @@ class UpdateInstaller {
84859
85166
  init_logger();
84860
85167
  import { spawn as spawn5 } from "child_process";
84861
85168
  import { existsSync as existsSync17, statSync as statSync4 } from "fs";
84862
- import { delimiter, join as join14 } from "path";
84863
- var log45 = createLogger("respawn");
85169
+ import { delimiter, join as join15 } from "path";
85170
+ var log46 = createLogger("respawn");
84864
85171
  function decideRespawn(env5 = process.env, isTTY = !!process.stdout.isTTY) {
84865
85172
  if (env5.CLAUDE_THREADS_BIN) {
84866
85173
  return { kind: "exit-for-supervisor", supervisor: "claude-threads-daemon" };
@@ -84885,16 +85192,16 @@ function resolveClaudeThreadsBin(_env = process.env, _existsSync = existsSync17,
84885
85192
  const path10 = _env.PATH || _env.Path || "";
84886
85193
  const dirs = path10.split(delimiter).filter(Boolean);
84887
85194
  const home = _env.HOME || _env.USERPROFILE;
84888
- const bunRoot = _env.BUN_INSTALL || (home ? join14(home, ".bun") : null);
85195
+ const bunRoot = _env.BUN_INSTALL || (home ? join15(home, ".bun") : null);
84889
85196
  if (bunRoot) {
84890
- const bunBin = join14(bunRoot, "bin");
85197
+ const bunBin = join15(bunRoot, "bin");
84891
85198
  if (!dirs.includes(bunBin)) {
84892
85199
  dirs.push(bunBin);
84893
85200
  }
84894
85201
  }
84895
85202
  for (const dir of dirs) {
84896
85203
  for (const name of names) {
84897
- const candidate = join14(dir, name);
85204
+ const candidate = join15(dir, name);
84898
85205
  if (_existsSync(candidate) && _isFileExecutable(candidate)) {
84899
85206
  return candidate;
84900
85207
  }
@@ -84916,7 +85223,7 @@ function isFileExecutable(path10) {
84916
85223
  }
84917
85224
  function spawnReplacement(argv = process.argv.slice(2), binPath = resolveClaudeThreadsBin()) {
84918
85225
  if (!binPath) {
84919
- log45.error("Could not resolve claude-threads on PATH; self-respawn aborted");
85226
+ log46.error("Could not resolve claude-threads on PATH; self-respawn aborted");
84920
85227
  return false;
84921
85228
  }
84922
85229
  if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
@@ -84937,23 +85244,23 @@ function spawnReplacement(argv = process.argv.slice(2), binPath = resolveClaudeT
84937
85244
  shell: useShell
84938
85245
  });
84939
85246
  } catch (err) {
84940
- log45.error(`spawn() threw: ${err instanceof Error ? err.message : String(err)}`);
85247
+ log46.error(`spawn() threw: ${err instanceof Error ? err.message : String(err)}`);
84941
85248
  return false;
84942
85249
  }
84943
85250
  child.once("error", (err) => {
84944
- log45.error(`Replacement process error: ${err.message}`);
85251
+ log46.error(`Replacement process error: ${err.message}`);
84945
85252
  });
84946
85253
  if (child.pid === undefined) {
84947
- log45.error("Spawn returned no pid (binary likely not executable)");
85254
+ log46.error("Spawn returned no pid (binary likely not executable)");
84948
85255
  return false;
84949
85256
  }
84950
85257
  child.unref();
84951
- log45.info(`Spawned replacement pid=${child.pid} from ${binPath}`);
85258
+ log46.info(`Spawned replacement pid=${child.pid} from ${binPath}`);
84952
85259
  return true;
84953
85260
  }
84954
85261
 
84955
85262
  // src/auto-update/manager.ts
84956
- var log46 = createLogger("updater");
85263
+ var log47 = createLogger("updater");
84957
85264
 
84958
85265
  class AutoUpdateManager extends EventEmitter9 {
84959
85266
  config;
@@ -84976,23 +85283,23 @@ class AutoUpdateManager extends EventEmitter9 {
84976
85283
  }
84977
85284
  start() {
84978
85285
  if (!this.config.enabled) {
84979
- log46.info("Auto-update is disabled");
85286
+ log47.info("Auto-update is disabled");
84980
85287
  return;
84981
85288
  }
84982
85289
  const updateResult = this.installer.checkJustUpdated();
84983
85290
  if (updateResult) {
84984
- log46.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
85291
+ log47.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
84985
85292
  this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
84986
- log46.warn(`Failed to broadcast update notification: ${err}`);
85293
+ log47.warn(`Failed to broadcast update notification: ${err}`);
84987
85294
  });
84988
85295
  }
84989
85296
  this.checker.start();
84990
- log46.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
85297
+ log47.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
84991
85298
  }
84992
85299
  stop() {
84993
85300
  this.checker.stop();
84994
85301
  this.scheduler.stop();
84995
- log46.debug("Auto-update manager stopped");
85302
+ log47.debug("Auto-update manager stopped");
84996
85303
  }
84997
85304
  getState() {
84998
85305
  return { ...this.state };
@@ -85006,10 +85313,10 @@ class AutoUpdateManager extends EventEmitter9 {
85006
85313
  async forceUpdate() {
85007
85314
  const updateInfo = this.state.updateInfo || await this.checker.check();
85008
85315
  if (!updateInfo) {
85009
- log46.info("No update available");
85316
+ log47.info("No update available");
85010
85317
  return;
85011
85318
  }
85012
- log46.info("Forcing immediate update");
85319
+ log47.info("Forcing immediate update");
85013
85320
  await this.performUpdate(updateInfo);
85014
85321
  }
85015
85322
  deferUpdate(minutes = 60) {
@@ -85075,11 +85382,11 @@ class AutoUpdateManager extends EventEmitter9 {
85075
85382
  await this.callbacks.prepareForRestart();
85076
85383
  } catch (err) {
85077
85384
  const reason = err instanceof Error ? err.message : String(err);
85078
- log46.error(`prepareForRestart failed: ${reason}`);
85385
+ log47.error(`prepareForRestart failed: ${reason}`);
85079
85386
  await this.callbacks.broadcastUpdate((fmt) => `⚠️ ${fmt.formatBold("Restart aborted")}: shutdown sequence failed (${reason}). Sessions may be in an inconsistent state; please run ${fmt.formatCode("claude-threads")} manually.`).catch(() => {});
85080
85387
  process.exit(1);
85081
85388
  }
85082
- log46.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
85389
+ log47.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
85083
85390
  process.stdout.write("\x1B[2J\x1B[H");
85084
85391
  process.stdout.write("\x1B[?25h");
85085
85392
  if (decision.kind === "self-respawn") {
@@ -85088,14 +85395,14 @@ class AutoUpdateManager extends EventEmitter9 {
85088
85395
  if (ok) {
85089
85396
  process.exit(0);
85090
85397
  }
85091
- log46.error("Self-respawn launch failed after binary resolution succeeded");
85398
+ log47.error("Self-respawn launch failed after binary resolution succeeded");
85092
85399
  await this.callbacks.broadcastUpdate((fmt) => `⚠️ ${fmt.formatBold("Auto-restart failed")} after install: please run ${fmt.formatCode("claude-threads")} to bring the bot back. Sessions are persisted and will resume.`).catch(() => {});
85093
85400
  } else {
85094
- log46.error("claude-threads not found on PATH; manual restart required");
85401
+ log47.error("claude-threads not found on PATH; manual restart required");
85095
85402
  }
85096
85403
  process.exit(0);
85097
85404
  }
85098
- log46.debug(`Restart handled by supervisor: ${decision.supervisor}`);
85405
+ log47.debug(`Restart handled by supervisor: ${decision.supervisor}`);
85099
85406
  process.exit(RESTART_EXIT_CODE);
85100
85407
  } else {
85101
85408
  const errorMsg = result.error ?? "Unknown error";
@@ -85489,6 +85796,7 @@ async function startWithoutDaemon() {
85489
85796
  });
85490
85797
  const client = createPlatformClient(platformConfig);
85491
85798
  platforms.set(platformConfig.id, client);
85799
+ configureAuditLog(platformConfig.id, resolveAuditLogEnabled(platformConfig.auditLog, `platforms[${platformConfig.id}].auditLog`));
85492
85800
  session.addPlatform(platformConfig.id, client, {
85493
85801
  sessionHeader: resolveOverheadVisibility(platformConfig.sessionHeader, `platforms[${platformConfig.id}].sessionHeader`),
85494
85802
  stickyMessage: resolveOverheadVisibility(platformConfig.stickyMessage, `platforms[${platformConfig.id}].stickyMessage`)
@@ -85509,6 +85817,7 @@ async function startWithoutDaemon() {
85509
85817
  platformType: "mattermost",
85510
85818
  enabled: true
85511
85819
  });
85820
+ configureAuditLog(dmConfig.id, resolveAuditLogEnabled(dmConfig.auditLog, `dm[${dmConfig.id}].auditLog`));
85512
85821
  session.addPlatform(dmConfig.id, dmClient, {
85513
85822
  sessionHeader: resolveOverheadVisibility(dmConfig.sessionHeader, `dm[${dmConfig.id}].sessionHeader`),
85514
85823
  stickyMessage: "hidden"
@@ -85553,10 +85862,10 @@ async function startWithoutDaemon() {
85553
85862
  enabled: false
85554
85863
  });
85555
85864
  }
85556
- const enabledPlatforms = Array.from(platforms.entries()).filter(([id]) => platformEnabledState.get(id) ?? true);
85557
- const disabledCount = platforms.size - enabledPlatforms.length;
85558
- ui.addLog({ level: "info", component: "init", message: `Connecting ${enabledPlatforms.length} platform(s)...${disabledCount > 0 ? ` (${disabledCount} disabled)` : ""}` });
85559
- const connectionResults = await Promise.allSettled(enabledPlatforms.map(async ([id, client]) => {
85865
+ const enabledPlatforms2 = Array.from(platforms.entries()).filter(([id]) => platformEnabledState.get(id) ?? true);
85866
+ const disabledCount = platforms.size - enabledPlatforms2.length;
85867
+ ui.addLog({ level: "info", component: "init", message: `Connecting ${enabledPlatforms2.length} platform(s)...${disabledCount > 0 ? ` (${disabledCount} disabled)` : ""}` });
85868
+ const connectionResults = await Promise.allSettled(enabledPlatforms2.map(async ([id, client]) => {
85560
85869
  ui.addLog({ level: "debug", component: "init", message: `Connecting to ${id}...` });
85561
85870
  try {
85562
85871
  await client.connect();