claude-threads 1.17.2 → 1.18.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
@@ -8051,15 +8051,16 @@ var require_range = __commonJS((exports, module) => {
8051
8051
  };
8052
8052
  var replaceTilde = (comp, options) => {
8053
8053
  const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE];
8054
+ const z = options.includePrerelease ? "-0" : "";
8054
8055
  return comp.replace(r, (_, M, m, p, pr) => {
8055
8056
  debug("tilde", comp, _, M, m, p, pr);
8056
8057
  let ret;
8057
8058
  if (isX(M)) {
8058
8059
  ret = "";
8059
8060
  } else if (isX(m)) {
8060
- ret = `>=${M}.0.0 <${+M + 1}.0.0-0`;
8061
+ ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`;
8061
8062
  } else if (isX(p)) {
8062
- ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`;
8063
+ ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`;
8063
8064
  } else if (pr) {
8064
8065
  debug("replaceTilde pr", pr);
8065
8066
  ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`;
@@ -9581,6 +9582,7 @@ var require_receiver = __commonJS((exports, module) => {
9581
9582
  this._opcode = 0;
9582
9583
  this._totalPayloadLength = 0;
9583
9584
  this._messageLength = 0;
9585
+ this._numFragments = 0;
9584
9586
  this._fragments = [];
9585
9587
  this._errored = false;
9586
9588
  this._loop = false;
@@ -9790,17 +9792,17 @@ var require_receiver = __commonJS((exports, module) => {
9790
9792
  this.controlMessage(data, cb);
9791
9793
  return;
9792
9794
  }
9795
+ if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
9796
+ const error = this.createError(RangeError, "Too many message fragments", false, 1008, "WS_ERR_TOO_MANY_BUFFERED_PARTS");
9797
+ cb(error);
9798
+ return;
9799
+ }
9793
9800
  if (this._compressed) {
9794
9801
  this._state = INFLATING;
9795
9802
  this.decompress(data, cb);
9796
9803
  return;
9797
9804
  }
9798
9805
  if (data.length) {
9799
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
9800
- const error = this.createError(RangeError, "Too many message fragments", false, 1008, "WS_ERR_TOO_MANY_BUFFERED_PARTS");
9801
- cb(error);
9802
- return;
9803
- }
9804
9806
  this._messageLength = this._totalPayloadLength;
9805
9807
  this._fragments.push(data);
9806
9808
  }
@@ -9818,11 +9820,6 @@ var require_receiver = __commonJS((exports, module) => {
9818
9820
  cb(error);
9819
9821
  return;
9820
9822
  }
9821
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
9822
- const error = this.createError(RangeError, "Too many message fragments", false, 1008, "WS_ERR_TOO_MANY_BUFFERED_PARTS");
9823
- cb(error);
9824
- return;
9825
- }
9826
9823
  this._fragments.push(buf);
9827
9824
  }
9828
9825
  this.dataMessage(cb);
@@ -9840,6 +9837,7 @@ var require_receiver = __commonJS((exports, module) => {
9840
9837
  this._totalPayloadLength = 0;
9841
9838
  this._messageLength = 0;
9842
9839
  this._fragmented = 0;
9840
+ this._numFragments = 0;
9843
9841
  this._fragments = [];
9844
9842
  if (this._opcode === 2) {
9845
9843
  let data;
@@ -10974,8 +10972,8 @@ var require_websocket = __commonJS((exports, module) => {
10974
10972
  autoPong: true,
10975
10973
  closeTimeout: CLOSE_TIMEOUT,
10976
10974
  protocolVersion: protocolVersions[1],
10977
- maxBufferedChunks: 1024 * 1024,
10978
- maxFragments: 128 * 1024,
10975
+ maxBufferedChunks: 256 * 1024,
10976
+ maxFragments: 16 * 1024,
10979
10977
  maxPayload: 100 * 1024 * 1024,
10980
10978
  skipUTF8Validation: false,
10981
10979
  perMessageDeflate: true,
@@ -11551,8 +11549,8 @@ var require_websocket_server = __commonJS((exports, module) => {
11551
11549
  options = {
11552
11550
  allowSynchronousEvents: true,
11553
11551
  autoPong: true,
11554
- maxBufferedChunks: 1024 * 1024,
11555
- maxFragments: 128 * 1024,
11552
+ maxBufferedChunks: 256 * 1024,
11553
+ maxFragments: 16 * 1024,
11556
11554
  maxPayload: 100 * 1024 * 1024,
11557
11555
  skipUTF8Validation: false,
11558
11556
  perMessageDeflate: false,
@@ -11906,8 +11904,8 @@ import * as fs from "fs/promises";
11906
11904
  import { homedir as homedir5 } from "os";
11907
11905
  async function execGit(args, cwd) {
11908
11906
  const cmd = `git ${args.join(" ")}`;
11909
- log10.debug(`Executing: ${cmd}`);
11910
- return new Promise((resolve3, reject) => {
11907
+ log12.debug(`Executing: ${cmd}`);
11908
+ return new Promise((resolve4, reject) => {
11911
11909
  const proc = crossSpawn("git", args, { cwd });
11912
11910
  let stdout = "";
11913
11911
  let stderr = "";
@@ -11919,15 +11917,15 @@ async function execGit(args, cwd) {
11919
11917
  });
11920
11918
  proc.on("close", (code) => {
11921
11919
  if (code === 0) {
11922
- log10.debug(`${cmd} → success`);
11923
- resolve3(stdout.trim());
11920
+ log12.debug(`${cmd} → success`);
11921
+ resolve4(stdout.trim());
11924
11922
  } else {
11925
- log10.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
11923
+ log12.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
11926
11924
  reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
11927
11925
  }
11928
11926
  });
11929
11927
  proc.on("error", (err) => {
11930
- log10.warn(`${cmd} → error: ${err}`);
11928
+ log12.warn(`${cmd} → error: ${err}`);
11931
11929
  reject(err);
11932
11930
  });
11933
11931
  });
@@ -11937,7 +11935,7 @@ async function isGitRepository(dir) {
11937
11935
  await execGit(["rev-parse", "--git-dir"], dir);
11938
11936
  return true;
11939
11937
  } catch (err) {
11940
- log10.debug(`Not a git repository: ${dir} (${err})`);
11938
+ log12.debug(`Not a git repository: ${dir} (${err})`);
11941
11939
  return false;
11942
11940
  }
11943
11941
  }
@@ -12072,7 +12070,7 @@ async function detectWorktreeInfo(workingDir) {
12072
12070
  const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
12073
12071
  const branch = branchOutput?.trim();
12074
12072
  if (!branch) {
12075
- log10.debug(`Could not detect branch for worktree at ${workingDir}`);
12073
+ log12.debug(`Could not detect branch for worktree at ${workingDir}`);
12076
12074
  return null;
12077
12075
  }
12078
12076
  const gitDirOutput = await execGit(["rev-parse", "--git-common-dir"], workingDir);
@@ -12084,45 +12082,45 @@ async function detectWorktreeInfo(workingDir) {
12084
12082
  repoRoot = repoRoot.slice(0, -4);
12085
12083
  }
12086
12084
  }
12087
- log10.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
12085
+ log12.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
12088
12086
  return {
12089
12087
  worktreePath: workingDir,
12090
12088
  branch,
12091
12089
  repoRoot: repoRoot || workingDir
12092
12090
  };
12093
12091
  } catch (err) {
12094
- log10.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
12092
+ log12.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
12095
12093
  return null;
12096
12094
  }
12097
12095
  }
12098
12096
  async function createWorktree(repoRoot, branch, targetDir) {
12099
- log10.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
12097
+ log12.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
12100
12098
  const parentDir = path.dirname(targetDir);
12101
- log10.debug(`Creating parent directory: ${parentDir}`);
12099
+ log12.debug(`Creating parent directory: ${parentDir}`);
12102
12100
  await fs.mkdir(parentDir, { recursive: true });
12103
12101
  const exists = await branchExists(repoRoot, branch);
12104
12102
  if (exists) {
12105
- log10.debug(`Branch '${branch}' exists, adding worktree`);
12103
+ log12.debug(`Branch '${branch}' exists, adding worktree`);
12106
12104
  await execGit(["worktree", "add", targetDir, branch], repoRoot);
12107
12105
  } else {
12108
- log10.debug(`Branch '${branch}' does not exist, creating with worktree`);
12106
+ log12.debug(`Branch '${branch}' does not exist, creating with worktree`);
12109
12107
  await execGit(["worktree", "add", "-b", branch, targetDir], repoRoot);
12110
12108
  }
12111
- log10.info(`Worktree created successfully: ${targetDir}`);
12109
+ log12.info(`Worktree created successfully: ${targetDir}`);
12112
12110
  return targetDir;
12113
12111
  }
12114
12112
  async function removeWorktree(repoRoot, worktreePath) {
12115
- log10.info(`Removing worktree: ${worktreePath}`);
12113
+ log12.info(`Removing worktree: ${worktreePath}`);
12116
12114
  try {
12117
12115
  await execGit(["worktree", "remove", worktreePath], repoRoot);
12118
- log10.debug("Worktree removed cleanly");
12116
+ log12.debug("Worktree removed cleanly");
12119
12117
  } catch (err) {
12120
- log10.debug(`Clean remove failed (${err}), trying force remove`);
12118
+ log12.debug(`Clean remove failed (${err}), trying force remove`);
12121
12119
  await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
12122
12120
  }
12123
- log10.debug("Pruning stale worktree references");
12121
+ log12.debug("Pruning stale worktree references");
12124
12122
  await execGit(["worktree", "prune"], repoRoot);
12125
- log10.info("Worktree removed and pruned successfully");
12123
+ log12.info("Worktree removed and pruned successfully");
12126
12124
  }
12127
12125
  async function findWorktreeByBranch(repoRoot, branch) {
12128
12126
  const worktrees = await listWorktrees(repoRoot);
@@ -12163,14 +12161,14 @@ async function writeMetadataStore(store) {
12163
12161
  await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
12164
12162
  await fs.chmod(METADATA_STORE_PATH, 384);
12165
12163
  } catch (err) {
12166
- log10.warn(`Failed to write worktree metadata store: ${err}`);
12164
+ log12.warn(`Failed to write worktree metadata store: ${err}`);
12167
12165
  }
12168
12166
  }
12169
12167
  async function writeWorktreeMetadata(worktreePath, metadata) {
12170
12168
  const store = await readMetadataStore();
12171
12169
  store[worktreePath] = metadata;
12172
12170
  await writeMetadataStore(store);
12173
- log10.debug(`Wrote worktree metadata for: ${worktreePath}`);
12171
+ log12.debug(`Wrote worktree metadata for: ${worktreePath}`);
12174
12172
  }
12175
12173
  async function readWorktreeMetadata(worktreePath) {
12176
12174
  const store = await readMetadataStore();
@@ -12193,14 +12191,14 @@ async function removeWorktreeMetadata(worktreePath) {
12193
12191
  if (store[worktreePath]) {
12194
12192
  delete store[worktreePath];
12195
12193
  await writeMetadataStore(store);
12196
- log10.debug(`Removed worktree metadata for: ${worktreePath}`);
12194
+ log12.debug(`Removed worktree metadata for: ${worktreePath}`);
12197
12195
  }
12198
12196
  }
12199
- var log10, WORKTREES_DIR, METADATA_STORE_PATH;
12197
+ var log12, WORKTREES_DIR, METADATA_STORE_PATH;
12200
12198
  var init_worktree = __esm(() => {
12201
12199
  init_spawn();
12202
12200
  init_logger();
12203
- log10 = createLogger("git-wt");
12201
+ log12 = createLogger("git-wt");
12204
12202
  WORKTREES_DIR = path.join(homedir5(), ".claude-threads", "worktrees");
12205
12203
  METADATA_STORE_PATH = path.join(homedir5(), ".claude-threads", "worktree-metadata.json");
12206
12204
  });
@@ -20445,7 +20443,7 @@ var require_react_reconciler_development = __commonJS((exports, module) => {
20445
20443
  return hook.checkDCE ? true : false;
20446
20444
  }
20447
20445
  function setIsStrictModeForDevtools(newIsStrictMode) {
20448
- typeof log36 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
20446
+ typeof log37 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
20449
20447
  if (injectedHook && typeof injectedHook.setStrictMode === "function")
20450
20448
  try {
20451
20449
  injectedHook.setStrictMode(rendererID, newIsStrictMode);
@@ -28529,7 +28527,7 @@ Check the render method of %s.`, getComponentNameFromFiber(current) || "Unknown"
28529
28527
  var fiberStack = [];
28530
28528
  var index$jscomp$0 = -1, emptyContextObject = {};
28531
28529
  Object.freeze(emptyContextObject);
28532
- 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, log36 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
28530
+ 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, log37 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
28533
28531
  if (typeof performance === "object" && typeof performance.now === "function") {
28534
28532
  var localPerformance = performance;
28535
28533
  var getCurrentTime = function() {
@@ -52405,6 +52403,9 @@ class BasePlatformClient extends EventEmitter {
52405
52403
  maxReconnectAttempts = 10;
52406
52404
  reconnectDelay = 1000;
52407
52405
  reconnectTimeout = null;
52406
+ getPostPermalink(post) {
52407
+ return this.getThreadLink(post.rootId || post.id, post.id);
52408
+ }
52408
52409
  isUserAllowed(username) {
52409
52410
  if (this.allowedUsers.length === 0) {
52410
52411
  return true;
@@ -54043,6 +54044,9 @@ class SlackClient extends BasePlatformClient {
54043
54044
  }
54044
54045
  return `#${targetTs}`;
54045
54046
  }
54047
+ getPostPermalink(post) {
54048
+ return this.getThreadLink(post.rootId || post.id, undefined, post.id);
54049
+ }
54046
54050
  async createPost(message, threadId, options) {
54047
54051
  const shouldUnfurl = options?.unfurl ?? threadId !== undefined;
54048
54052
  const truncatedMessage = this.truncateMessageIfNeeded(message);
@@ -55353,952 +55357,267 @@ class GitHubEmailsStore {
55353
55357
 
55354
55358
  // src/claude/account-pool.ts
55355
55359
  init_logger();
55356
- var log8 = createLogger("account-pool");
55357
- function hashThreadId(threadId) {
55358
- let h = 2166136261;
55359
- for (let i2 = 0;i2 < threadId.length; i2++) {
55360
- h ^= threadId.charCodeAt(i2);
55361
- h = Math.imul(h, 16777619);
55362
- }
55363
- return h >>> 0;
55364
- }
55365
55360
 
55366
- class AccountPool {
55367
- accounts;
55368
- byId;
55369
- activeCounts = new Map;
55370
- coolingUntil = new Map;
55371
- roundRobinIndex = 0;
55372
- constructor(accounts) {
55373
- this.accounts = (accounts ?? []).filter((acc) => {
55374
- const hasAuth = !!acc.home || !!acc.apiKey;
55375
- if (!hasAuth) {
55376
- log8.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
55377
- return false;
55378
- }
55379
- if (acc.home && acc.apiKey) {
55380
- log8.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
55381
- return false;
55382
- }
55383
- return true;
55384
- });
55385
- this.byId = new Map(this.accounts.map((acc) => [acc.id, acc]));
55386
- for (const acc of this.accounts) {
55387
- this.activeCounts.set(acc.id, 0);
55361
+ // src/claude/usage-probe.ts
55362
+ init_spawn();
55363
+
55364
+ // src/claude/cli.ts
55365
+ init_spawn();
55366
+ init_logger();
55367
+ import { EventEmitter as EventEmitter2 } from "events";
55368
+ import { resolve as resolve3, dirname as dirname4 } from "path";
55369
+ import { fileURLToPath as fileURLToPath3 } from "url";
55370
+ import { existsSync as existsSync7, readFileSync as readFileSync6, watchFile, unwatchFile, unlinkSync, statSync, readdirSync, writeFileSync as writeFileSync4 } from "fs";
55371
+ import { tmpdir } from "os";
55372
+ import { join as join5 } from "path";
55373
+
55374
+ // src/mcp/outbound-env.ts
55375
+ var OUTBOUND_ENV = {
55376
+ SESSION_WORKING_DIR: "SESSION_WORKING_DIR",
55377
+ SESSION_UPLOAD_DIR: "SESSION_UPLOAD_DIR",
55378
+ OUTBOUND_FILES_ENABLED: "OUTBOUND_FILES_ENABLED",
55379
+ OUTBOUND_FILES_MAX_BYTES: "OUTBOUND_FILES_MAX_BYTES"
55380
+ };
55381
+
55382
+ // src/claude/rate-limit-detector.ts
55383
+ var RATE_LIMIT_PHRASES = [
55384
+ /usage limit reached/i,
55385
+ /rate[_\s-]?limit[_\s-]?error/i,
55386
+ /you have hit the rate limit/i,
55387
+ /quota (has been )?exceeded/i,
55388
+ /\b429\b.*(rate|limit|quota)/i
55389
+ ];
55390
+ var DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
55391
+ function detectRateLimit(text, now = Date.now()) {
55392
+ if (!text)
55393
+ return { detected: false };
55394
+ let matched;
55395
+ for (const phrase of RATE_LIMIT_PHRASES) {
55396
+ const m = text.match(phrase);
55397
+ if (m) {
55398
+ matched = m[0];
55399
+ break;
55388
55400
  }
55389
55401
  }
55390
- get isEmpty() {
55391
- return this.accounts.length === 0;
55392
- }
55393
- get size() {
55394
- return this.accounts.length;
55395
- }
55396
- acquire(preferredId, threadId) {
55397
- if (this.isEmpty)
55398
- return null;
55399
- if (preferredId) {
55400
- const preferred = this.byId.get(preferredId);
55401
- if (preferred) {
55402
- this.incrementActive(preferred.id);
55403
- return preferred;
55404
- }
55405
- log8.warn(`Preferred account "${preferredId}" not in pool — falling back to round-robin`);
55406
- }
55407
- const now = Date.now();
55408
- const n = this.accounts.length;
55409
- if (threadId) {
55410
- const sticky = this.accounts[hashThreadId(threadId) % n];
55411
- const cooling = this.coolingUntil.get(sticky.id) ?? 0;
55412
- if (cooling <= now) {
55413
- this.incrementActive(sticky.id);
55414
- return sticky;
55415
- }
55416
- }
55417
- for (let i2 = 0;i2 < n; i2++) {
55418
- const idx = (this.roundRobinIndex + i2) % n;
55419
- const candidate = this.accounts[idx];
55420
- const cooling = this.coolingUntil.get(candidate.id) ?? 0;
55421
- if (cooling <= now) {
55422
- this.roundRobinIndex = (idx + 1) % n;
55423
- this.incrementActive(candidate.id);
55424
- return candidate;
55425
- }
55426
- }
55427
- log8.warn(`All ${n} accounts are in rate-limit cooldown`);
55428
- return null;
55402
+ if (!matched)
55403
+ return { detected: false };
55404
+ const resetAtEpochMs = extractResetAt(text, now);
55405
+ return { detected: true, matched, resetAtEpochMs };
55406
+ }
55407
+ function cooldownDeadline(hit, now = Date.now()) {
55408
+ if (!hit.detected)
55409
+ return now;
55410
+ return hit.resetAtEpochMs ?? now + DEFAULT_COOLDOWN_MS;
55411
+ }
55412
+ function extractResetAt(text, now) {
55413
+ const relative = text.match(/(?:retry[_\s-]?after|resets?\s+in)\s+(\d+)\s*(second|minute|hour|day)s?/i);
55414
+ if (relative) {
55415
+ const value = parseInt(relative[1], 10);
55416
+ const unit = relative[2].toLowerCase();
55417
+ const unitMs = {
55418
+ second: 1000,
55419
+ minute: 60000,
55420
+ hour: 3600000,
55421
+ day: 86400000
55422
+ };
55423
+ return now + value * unitMs[unit];
55429
55424
  }
55430
- release(accountId) {
55431
- const current = this.activeCounts.get(accountId);
55432
- if (current === undefined)
55433
- return;
55434
- this.activeCounts.set(accountId, Math.max(0, current - 1));
55425
+ const unix = text.match(/\breset(?:_at)?\b\s*["']?\s*[:=]\s*(\d{10,13})/);
55426
+ if (unix) {
55427
+ const raw = parseInt(unix[1], 10);
55428
+ return unix[1].length === 13 ? raw : raw * 1000;
55435
55429
  }
55436
- markCooling(accountId, untilEpochMs) {
55437
- if (!this.byId.has(accountId)) {
55438
- log8.warn(`markCooling called for unknown account "${accountId}"`);
55439
- return;
55440
- }
55441
- const existing = this.coolingUntil.get(accountId) ?? 0;
55442
- if (untilEpochMs > existing) {
55443
- this.coolingUntil.set(accountId, untilEpochMs);
55444
- const minutes = Math.ceil((untilEpochMs - Date.now()) / 60000);
55445
- log8.info(`Account "${accountId}" cooling for ~${minutes}min`);
55430
+ const clock = text.match(/resets?\s+at\s+(\d{1,2}):(\d{2})\s*(utc|gmt)?/i);
55431
+ if (clock) {
55432
+ const hh = parseInt(clock[1], 10);
55433
+ const mm = parseInt(clock[2], 10);
55434
+ if (hh < 24 && mm < 60) {
55435
+ const reference = new Date(now);
55436
+ const target = new Date(Date.UTC(reference.getUTCFullYear(), reference.getUTCMonth(), reference.getUTCDate(), hh, mm)).getTime();
55437
+ return target > now ? target : target + 86400000;
55446
55438
  }
55447
55439
  }
55448
- get(accountId) {
55449
- return this.byId.get(accountId);
55450
- }
55451
- status() {
55452
- const now = Date.now();
55453
- return this.accounts.map((acc) => {
55454
- const cooling = this.coolingUntil.get(acc.id) ?? 0;
55455
- return {
55456
- id: acc.id,
55457
- displayName: acc.displayName ?? acc.id,
55458
- activeSessions: this.activeCounts.get(acc.id) ?? 0,
55459
- coolingUntil: cooling > now ? cooling : null
55460
- };
55461
- });
55462
- }
55463
- incrementActive(accountId) {
55464
- this.activeCounts.set(accountId, (this.activeCounts.get(accountId) ?? 0) + 1);
55465
- }
55440
+ return;
55466
55441
  }
55467
55442
 
55468
- // src/cleanup/scheduler.ts
55469
- init_logger();
55470
- import { existsSync as existsSync8 } from "fs";
55471
- import { readdir, rm } from "fs/promises";
55472
- import { join as join7 } from "path";
55473
-
55474
- // src/persistence/thread-logger.ts
55475
- init_logger();
55476
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, appendFileSync, readdirSync, statSync, unlinkSync, rmdirSync, readFileSync as readFileSync6, chmodSync as chmodSync4 } from "fs";
55477
- import { homedir as homedir4 } from "os";
55478
- import { join as join5, dirname as dirname4 } from "path";
55479
- var log9 = createLogger("thread-log");
55480
- var LOGS_BASE_DIR = join5(homedir4(), ".claude-threads", "logs");
55481
-
55482
- class ThreadLoggerImpl {
55483
- platformId;
55484
- threadId;
55485
- claudeSessionId;
55486
- enabled;
55487
- bufferSize;
55488
- flushIntervalMs;
55489
- logPath;
55490
- buffer = [];
55491
- flushTimer = null;
55492
- isClosed = false;
55493
- constructor(platformId, threadId, claudeSessionId, options) {
55494
- this.platformId = platformId;
55495
- this.threadId = threadId;
55496
- this.claudeSessionId = claudeSessionId;
55497
- this.enabled = options?.enabled ?? true;
55498
- this.bufferSize = options?.bufferSize ?? 10;
55499
- this.flushIntervalMs = options?.flushIntervalMs ?? 1000;
55500
- this.logPath = join5(LOGS_BASE_DIR, platformId, `${claudeSessionId}.jsonl`);
55501
- if (this.enabled) {
55502
- const dir = dirname4(this.logPath);
55503
- if (!existsSync7(dir)) {
55504
- mkdirSync4(dir, { recursive: true });
55443
+ // src/claude/cli.ts
55444
+ var log8 = createLogger("claude");
55445
+ function cleanupBrowserBridgeSockets() {
55446
+ try {
55447
+ const tempDir = tmpdir();
55448
+ const files = readdirSync(tempDir);
55449
+ for (const file of files) {
55450
+ if (file.startsWith("claude-mcp-browser-bridge-")) {
55451
+ const filePath = join5(tempDir, file);
55452
+ try {
55453
+ const stats = statSync(filePath);
55454
+ if (stats.isSocket()) {
55455
+ unlinkSync(filePath);
55456
+ log8.debug(`Removed stale browser bridge socket: ${file}`);
55457
+ }
55458
+ } catch {}
55505
55459
  }
55506
- this.flushTimer = setInterval(() => {
55507
- this.flushSync();
55508
- }, this.flushIntervalMs);
55509
- log9.debug(`Thread logger initialized: ${this.logPath}`);
55510
55460
  }
55461
+ } catch (err) {
55462
+ log8.debug(`Browser bridge cleanup failed: ${err}`);
55511
55463
  }
55512
- isEnabled() {
55513
- return this.enabled;
55464
+ }
55465
+ function buildClaudeChildEnv(parentEnv, account) {
55466
+ const env = { ...parentEnv };
55467
+ if (env.MCP_CONNECTION_NONBLOCKING === undefined) {
55468
+ env.MCP_CONNECTION_NONBLOCKING = "true";
55514
55469
  }
55515
- getLogPath() {
55516
- return this.logPath;
55470
+ if (env.ENABLE_PROMPT_CACHING_1H === undefined) {
55471
+ env.ENABLE_PROMPT_CACHING_1H = "true";
55517
55472
  }
55518
- logEvent(event) {
55519
- if (!this.enabled || this.isClosed)
55520
- return;
55521
- const entry = {
55522
- ts: Date.now(),
55523
- sessionId: this.claudeSessionId,
55524
- type: "claude_event",
55525
- eventType: event.type,
55526
- event
55527
- };
55528
- this.addEntry(entry);
55473
+ if (account?.home) {
55474
+ env.HOME = account.home;
55475
+ env.USERPROFILE = account.home;
55476
+ delete env.ANTHROPIC_API_KEY;
55477
+ delete env.CLAUDE_CODE_OAUTH_TOKEN;
55478
+ } else if (account?.apiKey) {
55479
+ env.ANTHROPIC_API_KEY = account.apiKey;
55480
+ delete env.CLAUDE_CODE_OAUTH_TOKEN;
55529
55481
  }
55530
- logUserMessage(username, message, displayName, hasFiles) {
55531
- if (!this.enabled || this.isClosed)
55532
- return;
55533
- const entry = {
55534
- ts: Date.now(),
55535
- sessionId: this.claudeSessionId,
55536
- type: "user_message",
55537
- username,
55538
- displayName,
55539
- message,
55540
- hasFiles
55541
- };
55542
- this.addEntry(entry);
55482
+ return env;
55483
+ }
55484
+ function isErrorResultEvent(event) {
55485
+ const ev = event;
55486
+ if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
55487
+ return true;
55488
+ if (ev.is_error === true)
55489
+ return true;
55490
+ return false;
55491
+ }
55492
+ function materializeMcpConfig(config, sessionId, opts = {}) {
55493
+ if (opts.inline) {
55494
+ return { mode: "inline", value: JSON.stringify(config) };
55543
55495
  }
55544
- logLifecycle(action, details) {
55545
- if (!this.enabled || this.isClosed)
55546
- return;
55547
- const entry = {
55548
- ts: Date.now(),
55549
- sessionId: this.claudeSessionId,
55550
- type: "lifecycle",
55551
- action,
55552
- ...details
55553
- };
55554
- this.addEntry(entry);
55496
+ const dir = opts.tmpDirOverride ?? tmpdir();
55497
+ const path = join5(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
55498
+ writeFileSync4(path, JSON.stringify(config), { mode: 384 });
55499
+ return { mode: "file", path };
55500
+ }
55501
+ function buildPermissionArgs(opts) {
55502
+ const args = [];
55503
+ if (opts.permissionMode === "bypass" && !opts.platformConfig) {
55504
+ args.push("--dangerously-skip-permissions");
55505
+ return { args, tempFile: null };
55555
55506
  }
55556
- logCommand(command, args, username) {
55557
- if (!this.enabled || this.isClosed)
55558
- return;
55559
- const entry = {
55560
- ts: Date.now(),
55561
- sessionId: this.claudeSessionId,
55562
- type: "command",
55563
- command,
55564
- args,
55565
- username
55566
- };
55567
- this.addEntry(entry);
55507
+ if (!opts.platformConfig) {
55508
+ throw new Error(`platformConfig is required when permissionMode is '${opts.permissionMode}'`);
55568
55509
  }
55569
- logPermission(action, permission, username) {
55570
- if (!this.enabled || this.isClosed)
55571
- return;
55572
- const entry = {
55573
- ts: Date.now(),
55574
- sessionId: this.claudeSessionId,
55575
- type: "permission",
55576
- action,
55577
- permission,
55578
- username
55579
- };
55580
- this.addEntry(entry);
55510
+ const mcpEnv = {
55511
+ PLATFORM_TYPE: opts.platformConfig.type,
55512
+ PLATFORM_URL: opts.platformConfig.url,
55513
+ PLATFORM_TOKEN: opts.platformConfig.token,
55514
+ PLATFORM_CHANNEL_ID: opts.platformConfig.channelId,
55515
+ PLATFORM_THREAD_ID: opts.threadId || "",
55516
+ ALLOWED_USERS: opts.platformConfig.allowedUsers.join(","),
55517
+ DEBUG: opts.debug ? "1" : "",
55518
+ PERMISSION_TIMEOUT_MS: String(opts.permissionTimeoutMs),
55519
+ SESSION_OWNER_USERNAME: opts.sessionOwnerUsername || ""
55520
+ };
55521
+ if (opts.platformConfig.appToken) {
55522
+ mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
55581
55523
  }
55582
- logReaction(action, username, emoji, answer) {
55583
- if (!this.enabled || this.isClosed)
55584
- return;
55585
- const entry = {
55586
- ts: Date.now(),
55587
- sessionId: this.claudeSessionId,
55588
- type: "reaction",
55589
- action,
55590
- username,
55591
- emoji,
55592
- answer
55593
- };
55594
- this.addEntry(entry);
55524
+ if (opts.workingDir) {
55525
+ mcpEnv[OUTBOUND_ENV.SESSION_WORKING_DIR] = opts.workingDir;
55595
55526
  }
55596
- logExecutor(executor, operation, postId, details, method) {
55597
- if (!this.enabled || this.isClosed)
55598
- return;
55599
- const entry = {
55600
- ts: Date.now(),
55601
- sessionId: this.claudeSessionId,
55602
- type: "executor",
55603
- executor,
55604
- operation,
55605
- postId,
55606
- method,
55607
- details
55608
- };
55609
- this.addEntry(entry);
55527
+ if (opts.uploadDir) {
55528
+ mcpEnv[OUTBOUND_ENV.SESSION_UPLOAD_DIR] = opts.uploadDir;
55610
55529
  }
55611
- async flush() {
55612
- this.flushSync();
55530
+ if (opts.outboundFiles?.enabled === false) {
55531
+ mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_ENABLED] = "0";
55613
55532
  }
55614
- async close() {
55615
- if (this.isClosed)
55616
- return;
55617
- this.isClosed = true;
55618
- if (this.flushTimer) {
55619
- clearInterval(this.flushTimer);
55620
- this.flushTimer = null;
55621
- }
55622
- this.flushSync();
55623
- log9.debug(`Thread logger closed: ${this.logPath}`);
55533
+ if (typeof opts.outboundFiles?.maxBytes === "number" && Number.isFinite(opts.outboundFiles.maxBytes) && opts.outboundFiles.maxBytes > 0) {
55534
+ mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_MAX_BYTES] = String(opts.outboundFiles.maxBytes);
55624
55535
  }
55625
- addEntry(entry) {
55626
- this.buffer.push(entry);
55627
- if (this.buffer.length >= this.bufferSize) {
55628
- this.flushSync();
55536
+ const mcpConfig = {
55537
+ mcpServers: {
55538
+ "claude-threads-mcp": {
55539
+ type: "stdio",
55540
+ command: "node",
55541
+ args: [opts.mcpServerPath],
55542
+ env: mcpEnv
55543
+ }
55629
55544
  }
55545
+ };
55546
+ const materialized = materializeMcpConfig(mcpConfig, opts.sessionId, { inline: opts.inline });
55547
+ let tempFile = null;
55548
+ if (materialized.mode === "file") {
55549
+ tempFile = materialized.path;
55550
+ args.push("--mcp-config", materialized.path);
55551
+ } else {
55552
+ args.push("--mcp-config", materialized.value);
55630
55553
  }
55631
- flushSync() {
55632
- if (this.buffer.length === 0)
55633
- return;
55634
- try {
55635
- const lines = this.buffer.map((entry) => JSON.stringify(entry)).join(`
55636
- `) + `
55637
- `;
55638
- const isNewFile = !existsSync7(this.logPath);
55639
- appendFileSync(this.logPath, lines, { encoding: "utf8", mode: 384 });
55640
- if (isNewFile) {
55641
- chmodSync4(this.logPath, 384);
55642
- }
55643
- this.buffer = [];
55644
- } catch (err) {
55645
- log9.error(`Failed to flush thread log: ${err}`);
55554
+ if (opts.permissionMode === "bypass") {
55555
+ args.push("--dangerously-skip-permissions");
55556
+ } else {
55557
+ args.push("--permission-prompt-tool", "mcp__claude-threads-mcp__permission_prompt");
55558
+ if (opts.permissionMode === "auto") {
55559
+ args.push("--permission-mode", "auto");
55646
55560
  }
55647
55561
  }
55562
+ return { args, tempFile };
55648
55563
  }
55564
+ var STDERR_PER_INSTANCE_CAP = 10240;
55565
+ var STDERR_AGGREGATE_SOFT_CAP = 10 * 1024 * 1024;
55566
+ var totalStderrBytes = 0;
55649
55567
 
55650
- class DisabledThreadLogger {
55651
- logEvent() {}
55652
- logUserMessage() {}
55653
- logLifecycle() {}
55654
- logCommand() {}
55655
- logPermission() {}
55656
- logReaction() {}
55657
- logExecutor() {}
55658
- async flush() {}
55659
- async close() {}
55660
- isEnabled() {
55661
- return false;
55662
- }
55663
- getLogPath() {
55664
- return "";
55568
+ class ClaudeCli extends EventEmitter2 {
55569
+ process = null;
55570
+ options;
55571
+ buffer = "";
55572
+ debug = process.env.DEBUG === "1" || process.argv.includes("--debug");
55573
+ statusFilePath = null;
55574
+ lastStatusData = null;
55575
+ stderrBuffer = "";
55576
+ mcpConfigTempFile = null;
55577
+ lastEmittedRateLimitDeadline = 0;
55578
+ log;
55579
+ constructor(options) {
55580
+ super();
55581
+ this.options = options;
55582
+ this.log = options.logSessionId ? createLogger("claude").forSession(options.logSessionId) : createLogger("claude");
55665
55583
  }
55666
- }
55667
- function createThreadLogger(platformId, threadId, claudeSessionId, options) {
55668
- if (options?.enabled === false) {
55669
- return new DisabledThreadLogger;
55584
+ getStatusFilePath() {
55585
+ return this.statusFilePath;
55670
55586
  }
55671
- return new ThreadLoggerImpl(platformId, threadId, claudeSessionId, options);
55672
- }
55673
- function cleanupOldLogs(retentionDays = 30) {
55674
- const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
55675
- let deletedCount = 0;
55676
- if (!existsSync7(LOGS_BASE_DIR)) {
55677
- return 0;
55587
+ getStatusData() {
55588
+ if (!this.statusFilePath)
55589
+ return null;
55590
+ try {
55591
+ if (existsSync7(this.statusFilePath)) {
55592
+ const data = readFileSync6(this.statusFilePath, "utf8");
55593
+ this.lastStatusData = JSON.parse(data);
55594
+ }
55595
+ } catch (err) {
55596
+ this.log.debug(`Failed to read status file: ${err}`);
55597
+ }
55598
+ return this.lastStatusData;
55678
55599
  }
55679
- try {
55680
- const platformDirs = readdirSync(LOGS_BASE_DIR);
55681
- for (const platformId of platformDirs) {
55682
- const platformDir = join5(LOGS_BASE_DIR, platformId);
55683
- const stat = statSync(platformDir);
55684
- if (!stat.isDirectory())
55685
- continue;
55686
- const logFiles = readdirSync(platformDir);
55687
- for (const file of logFiles) {
55688
- if (!file.endsWith(".jsonl"))
55689
- continue;
55690
- const filePath = join5(platformDir, file);
55691
- try {
55692
- const fileStat = statSync(filePath);
55693
- if (fileStat.mtimeMs < cutoffMs) {
55694
- unlinkSync(filePath);
55695
- deletedCount++;
55696
- log9.debug(`Deleted old log file: ${filePath}`);
55697
- }
55698
- } catch (err) {
55699
- log9.warn(`Failed to check/delete log file ${filePath}: ${err}`);
55700
- }
55600
+ startStatusWatch() {
55601
+ if (!this.statusFilePath) {
55602
+ this.log.debug("No status file path, skipping status watch");
55603
+ return;
55604
+ }
55605
+ this.log.debug(`Starting status watch: ${this.statusFilePath}`);
55606
+ const checkStatus = () => {
55607
+ const data = this.getStatusData();
55608
+ if (data && data.timestamp !== this.lastStatusData?.timestamp) {
55609
+ this.lastStatusData = data;
55610
+ this.emit("status", data);
55701
55611
  }
55612
+ };
55613
+ watchFile(this.statusFilePath, { interval: 1000 }, checkStatus);
55614
+ }
55615
+ stopStatusWatch() {
55616
+ if (this.statusFilePath) {
55617
+ unwatchFile(this.statusFilePath);
55702
55618
  try {
55703
- const remaining = readdirSync(platformDir);
55704
- if (remaining.length === 0) {
55705
- rmdirSync(platformDir);
55706
- log9.debug(`Removed empty platform log directory: ${platformDir}`);
55707
- }
55708
- } catch {}
55709
- }
55710
- if (deletedCount > 0) {
55711
- log9.info(`Cleaned up ${deletedCount} old log file(s)`);
55712
- }
55713
- } catch (err) {
55714
- log9.error(`Failed to clean up old logs: ${err}`);
55715
- }
55716
- return deletedCount;
55717
- }
55718
- function getLogFilePath(platformId, sessionId) {
55719
- return join5(LOGS_BASE_DIR, platformId, `${sessionId}.jsonl`);
55720
- }
55721
- function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
55722
- const logPath = getLogFilePath(platformId, sessionId);
55723
- log9.debug(`Reading log entries from: ${logPath}`);
55724
- if (!existsSync7(logPath)) {
55725
- log9.debug(`Log file does not exist: ${logPath}`);
55726
- return [];
55727
- }
55728
- try {
55729
- const content = readFileSync6(logPath, "utf8");
55730
- const lines = content.trim().split(`
55731
- `);
55732
- log9.debug(`Log file has ${lines.length} lines`);
55733
- const recentLines = lines.slice(-maxLines);
55734
- const entries = [];
55735
- for (const line of recentLines) {
55736
- if (!line.trim())
55737
- continue;
55738
- try {
55739
- entries.push(JSON.parse(line));
55740
- } catch {}
55741
- }
55742
- log9.debug(`Parsed ${entries.length} log entries`);
55743
- return entries;
55744
- } catch (err) {
55745
- log9.error(`Failed to read log file: ${err}`);
55746
- return [];
55747
- }
55748
- }
55749
-
55750
- // src/cleanup/scheduler.ts
55751
- init_worktree();
55752
- var log11 = createLogger("cleanup");
55753
- var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
55754
- var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
55755
-
55756
- class CleanupScheduler {
55757
- intervalMs;
55758
- logRetentionDays;
55759
- threadLogsEnabled;
55760
- sessionStore;
55761
- maxWorktreeAgeMs;
55762
- cleanupWorktrees;
55763
- timer = null;
55764
- isRunning = false;
55765
- constructor(options) {
55766
- this.intervalMs = options.intervalMs ?? DEFAULT_CLEANUP_INTERVAL_MS;
55767
- this.logRetentionDays = options.logRetentionDays ?? 30;
55768
- this.threadLogsEnabled = options.threadLogsEnabled ?? true;
55769
- this.sessionStore = options.sessionStore;
55770
- this.maxWorktreeAgeMs = options.maxWorktreeAgeMs ?? MAX_WORKTREE_AGE_MS;
55771
- this.cleanupWorktrees = options.cleanupWorktrees ?? true;
55772
- }
55773
- start() {
55774
- if (this.isRunning) {
55775
- log11.debug("Cleanup scheduler already running");
55776
- return;
55777
- }
55778
- this.isRunning = true;
55779
- log11.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
55780
- this.runCleanup().catch((err) => {
55781
- log11.warn(`Initial cleanup failed: ${err}`);
55782
- });
55783
- this.timer = setInterval(() => {
55784
- this.runCleanup().catch((err) => {
55785
- log11.warn(`Periodic cleanup failed: ${err}`);
55786
- });
55787
- }, this.intervalMs);
55788
- }
55789
- stop() {
55790
- if (this.timer) {
55791
- clearInterval(this.timer);
55792
- this.timer = null;
55793
- }
55794
- this.isRunning = false;
55795
- log11.debug("Cleanup scheduler stopped");
55796
- }
55797
- async runCleanup() {
55798
- const startTime = Date.now();
55799
- log11.debug("Running background cleanup...");
55800
- const stats = {
55801
- logsDeleted: 0,
55802
- worktreesCleaned: 0,
55803
- metadataCleaned: 0,
55804
- errors: []
55805
- };
55806
- const cleanupTasks = [
55807
- this.cleanupLogs().catch((err) => {
55808
- stats.errors.push(`Log cleanup: ${err}`);
55809
- return 0;
55810
- })
55811
- ];
55812
- if (this.cleanupWorktrees) {
55813
- cleanupTasks.push(this.cleanupOrphanedWorktrees().catch((err) => {
55814
- stats.errors.push(`Worktree cleanup: ${err}`);
55815
- return { cleaned: 0, metadata: 0 };
55816
- }));
55817
- }
55818
- const [logStats, worktreeStats = { cleaned: 0, metadata: 0 }] = await Promise.all(cleanupTasks);
55819
- stats.logsDeleted = logStats;
55820
- stats.worktreesCleaned = worktreeStats.cleaned;
55821
- stats.metadataCleaned = worktreeStats.metadata;
55822
- const elapsed = Date.now() - startTime;
55823
- const totalCleaned = stats.logsDeleted + stats.worktreesCleaned + stats.metadataCleaned;
55824
- if (totalCleaned > 0 || stats.errors.length > 0) {
55825
- log11.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
55826
- } else {
55827
- log11.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
55828
- }
55829
- return stats;
55830
- }
55831
- async cleanupLogs() {
55832
- if (!this.threadLogsEnabled) {
55833
- return 0;
55834
- }
55835
- return new Promise((resolve3) => {
55836
- try {
55837
- const deleted = cleanupOldLogs(this.logRetentionDays);
55838
- resolve3(deleted);
55839
- } catch (err) {
55840
- log11.warn(`Log cleanup error: ${err}`);
55841
- resolve3(0);
55842
- }
55843
- });
55844
- }
55845
- async cleanupOrphanedWorktrees() {
55846
- const worktreesDir = getWorktreesDir();
55847
- const result = { cleaned: 0, metadata: 0 };
55848
- if (!existsSync8(worktreesDir)) {
55849
- log11.debug("No worktrees directory exists, nothing to clean");
55850
- return result;
55851
- }
55852
- const persisted = this.sessionStore.load();
55853
- const activeWorktrees = new Set;
55854
- for (const session of persisted.values()) {
55855
- if (session.worktreeInfo?.worktreePath) {
55856
- activeWorktrees.add(session.worktreeInfo.worktreePath);
55857
- }
55858
- }
55859
- const now = Date.now();
55860
- try {
55861
- const entries = await readdir(worktreesDir, { withFileTypes: true });
55862
- for (const entry of entries) {
55863
- if (!entry.isDirectory())
55864
- continue;
55865
- const worktreePath = join7(worktreesDir, entry.name);
55866
- if (activeWorktrees.has(worktreePath)) {
55867
- log11.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
55868
- continue;
55869
- }
55870
- const meta = await readWorktreeMetadata(worktreePath);
55871
- let shouldCleanup = false;
55872
- let cleanupReason = "";
55873
- if (meta) {
55874
- const lastActivity = new Date(meta.lastActivityAt).getTime();
55875
- const age = now - lastActivity;
55876
- if (meta.sessionId && age < this.maxWorktreeAgeMs) {
55877
- log11.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
55878
- continue;
55879
- }
55880
- const merged = age >= this.maxWorktreeAgeMs ? await isBranchMerged(meta.repoRoot, meta.branch).catch(() => false) : false;
55881
- if (merged) {
55882
- shouldCleanup = true;
55883
- cleanupReason = `branch "${meta.branch}" was merged`;
55884
- } else if (age >= this.maxWorktreeAgeMs) {
55885
- shouldCleanup = true;
55886
- cleanupReason = `inactive for ${Math.round(age / 3600000)}h`;
55887
- } else {
55888
- log11.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
55889
- continue;
55890
- }
55891
- } else {
55892
- shouldCleanup = true;
55893
- cleanupReason = "no metadata";
55894
- }
55895
- if (!shouldCleanup)
55896
- continue;
55897
- log11.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
55898
- try {
55899
- if (meta?.repoRoot) {
55900
- await removeWorktree(meta.repoRoot, worktreePath);
55901
- } else {
55902
- await rm(worktreePath, { recursive: true, force: true });
55903
- }
55904
- result.cleaned++;
55905
- await removeWorktreeMetadata(worktreePath);
55906
- result.metadata++;
55907
- } catch (err) {
55908
- log11.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
55909
- try {
55910
- await rm(worktreePath, { recursive: true, force: true });
55911
- result.cleaned++;
55912
- await removeWorktreeMetadata(worktreePath);
55913
- result.metadata++;
55914
- } catch (rmErr) {
55915
- log11.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
55916
- }
55917
- }
55918
- }
55919
- } catch (err) {
55920
- log11.warn(`Failed to scan worktrees directory: ${err}`);
55921
- }
55922
- return result;
55923
- }
55924
- }
55925
- // src/operations/monitor/handler.ts
55926
- init_logger();
55927
-
55928
- // src/session/lifecycle-fsm.ts
55929
- init_logger();
55930
- var log12 = createLogger("fsm");
55931
- var ALLOWED_TRANSITIONS = {
55932
- starting: new Set(["active", "paused", "interrupted", "cancelling", "restarting"]),
55933
- active: new Set([
55934
- "active",
55935
- "processing",
55936
- "paused",
55937
- "interrupted",
55938
- "restarting",
55939
- "cancelling",
55940
- "ending"
55941
- ]),
55942
- processing: new Set([
55943
- "active",
55944
- "paused",
55945
- "interrupted",
55946
- "restarting",
55947
- "cancelling"
55948
- ]),
55949
- paused: new Set(["active", "cancelling", "restarting"]),
55950
- interrupted: new Set(["active", "cancelling", "restarting", "paused"]),
55951
- restarting: new Set(["active", "paused", "cancelling"]),
55952
- cancelling: new Set(["ending"]),
55953
- ending: new Set
55954
- };
55955
- function checkTransition(from, to, sessionId) {
55956
- const allowed = ALLOWED_TRANSITIONS[from];
55957
- if (allowed.has(to))
55958
- return;
55959
- const msg = `illegal lifecycle transition ${from} -> ${to}`;
55960
- const payload = {
55961
- event: "fsm.illegal_transition",
55962
- from,
55963
- to,
55964
- sessionId
55965
- };
55966
- if (process.env.CLAUDE_THREADS_FSM_STRICT === "1") {
55967
- throw new Error(`${msg} (sessionId=${sessionId})`);
55968
- }
55969
- log12.warn(msg, payload);
55970
- }
55971
-
55972
- // src/session/timer-manager.ts
55973
- function createSessionTimers() {
55974
- return {
55975
- updateTimer: null,
55976
- typingTimer: null,
55977
- statusBarTimer: null
55978
- };
55979
- }
55980
- function clearAllTimers(timers) {
55981
- if (timers.updateTimer) {
55982
- clearTimeout(timers.updateTimer);
55983
- timers.updateTimer = null;
55984
- }
55985
- if (timers.typingTimer) {
55986
- clearInterval(timers.typingTimer);
55987
- timers.typingTimer = null;
55988
- }
55989
- if (timers.statusBarTimer) {
55990
- clearInterval(timers.statusBarTimer);
55991
- timers.statusBarTimer = null;
55992
- }
55993
- }
55994
-
55995
- // src/session/types.ts
55996
- function createSessionLifecycle() {
55997
- return {
55998
- state: "starting",
55999
- resumeFailCount: 0,
56000
- hasClaudeResponded: false
56001
- };
56002
- }
56003
- function createResumedLifecycle(resumeFailCount = 0) {
56004
- return {
56005
- state: "active",
56006
- resumeFailCount,
56007
- hasClaudeResponded: true
56008
- };
56009
- }
56010
- function isSessionRestarting(session) {
56011
- return session.lifecycle.state === "restarting";
56012
- }
56013
- function isSessionCancelled(session) {
56014
- return session.lifecycle.state === "cancelling";
56015
- }
56016
- function transitionTo(session, newState) {
56017
- checkTransition(session.lifecycle.state, newState, session.sessionId);
56018
- session.lifecycle.state = newState;
56019
- }
56020
- function markClaudeResponded(session) {
56021
- session.lifecycle.hasClaudeResponded = true;
56022
- if (session.lifecycle.state === "starting") {
56023
- session.lifecycle.state = "active";
56024
- }
56025
- }
56026
- function getSessionStatus(session) {
56027
- if (session.isProcessing) {
56028
- return session.lifecycle.hasClaudeResponded ? "active" : "starting";
56029
- }
56030
- return "idle";
56031
- }
56032
-
56033
- // src/session/authorization.ts
56034
- function isAuthorizedForSession(check) {
56035
- const { username, platform, sessionAllowedUsers } = check;
56036
- if (!username || username === "unknown") {
56037
- return false;
56038
- }
56039
- if (platform.isUserAllowed(username)) {
56040
- return true;
56041
- }
56042
- return sessionAllowedUsers?.has(username) ?? false;
56043
- }
56044
-
56045
- // src/claude/cli.ts
56046
- init_spawn();
56047
- init_logger();
56048
- import { EventEmitter as EventEmitter2 } from "events";
56049
- import { resolve as resolve3, dirname as dirname6 } from "path";
56050
- import { fileURLToPath as fileURLToPath3 } from "url";
56051
- import { existsSync as existsSync9, readFileSync as readFileSync7, watchFile, unwatchFile, unlinkSync as unlinkSync2, statSync as statSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
56052
- import { tmpdir } from "os";
56053
- import { join as join8 } from "path";
56054
-
56055
- // src/mcp/outbound-env.ts
56056
- var OUTBOUND_ENV = {
56057
- SESSION_WORKING_DIR: "SESSION_WORKING_DIR",
56058
- SESSION_UPLOAD_DIR: "SESSION_UPLOAD_DIR",
56059
- OUTBOUND_FILES_ENABLED: "OUTBOUND_FILES_ENABLED",
56060
- OUTBOUND_FILES_MAX_BYTES: "OUTBOUND_FILES_MAX_BYTES"
56061
- };
56062
-
56063
- // src/claude/rate-limit-detector.ts
56064
- var RATE_LIMIT_PHRASES = [
56065
- /usage limit reached/i,
56066
- /rate[_\s-]?limit[_\s-]?error/i,
56067
- /you have hit the rate limit/i,
56068
- /quota (has been )?exceeded/i,
56069
- /\b429\b.*(rate|limit|quota)/i
56070
- ];
56071
- var DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
56072
- function detectRateLimit(text, now = Date.now()) {
56073
- if (!text)
56074
- return { detected: false };
56075
- let matched;
56076
- for (const phrase of RATE_LIMIT_PHRASES) {
56077
- const m = text.match(phrase);
56078
- if (m) {
56079
- matched = m[0];
56080
- break;
56081
- }
56082
- }
56083
- if (!matched)
56084
- return { detected: false };
56085
- const resetAtEpochMs = extractResetAt(text, now);
56086
- return { detected: true, matched, resetAtEpochMs };
56087
- }
56088
- function cooldownDeadline(hit, now = Date.now()) {
56089
- if (!hit.detected)
56090
- return now;
56091
- return hit.resetAtEpochMs ?? now + DEFAULT_COOLDOWN_MS;
56092
- }
56093
- function extractResetAt(text, now) {
56094
- const relative = text.match(/(?:retry[_\s-]?after|resets?\s+in)\s+(\d+)\s*(second|minute|hour|day)s?/i);
56095
- if (relative) {
56096
- const value = parseInt(relative[1], 10);
56097
- const unit = relative[2].toLowerCase();
56098
- const unitMs = {
56099
- second: 1000,
56100
- minute: 60000,
56101
- hour: 3600000,
56102
- day: 86400000
56103
- };
56104
- return now + value * unitMs[unit];
56105
- }
56106
- const unix = text.match(/\breset(?:_at)?\b\s*["']?\s*[:=]\s*(\d{10,13})/);
56107
- if (unix) {
56108
- const raw = parseInt(unix[1], 10);
56109
- return unix[1].length === 13 ? raw : raw * 1000;
56110
- }
56111
- const clock = text.match(/resets?\s+at\s+(\d{1,2}):(\d{2})\s*(utc|gmt)?/i);
56112
- if (clock) {
56113
- const hh = parseInt(clock[1], 10);
56114
- const mm = parseInt(clock[2], 10);
56115
- if (hh < 24 && mm < 60) {
56116
- const reference = new Date(now);
56117
- const target = new Date(Date.UTC(reference.getUTCFullYear(), reference.getUTCMonth(), reference.getUTCDate(), hh, mm)).getTime();
56118
- return target > now ? target : target + 86400000;
56119
- }
56120
- }
56121
- return;
56122
- }
56123
-
56124
- // src/claude/cli.ts
56125
- var log13 = createLogger("claude");
56126
- function cleanupBrowserBridgeSockets() {
56127
- try {
56128
- const tempDir = tmpdir();
56129
- const files = readdirSync2(tempDir);
56130
- for (const file of files) {
56131
- if (file.startsWith("claude-mcp-browser-bridge-")) {
56132
- const filePath = join8(tempDir, file);
56133
- try {
56134
- const stats = statSync2(filePath);
56135
- if (stats.isSocket()) {
56136
- unlinkSync2(filePath);
56137
- log13.debug(`Removed stale browser bridge socket: ${file}`);
56138
- }
56139
- } catch {}
56140
- }
56141
- }
56142
- } catch (err) {
56143
- log13.debug(`Browser bridge cleanup failed: ${err}`);
56144
- }
56145
- }
56146
- function buildClaudeChildEnv(parentEnv, account) {
56147
- const env = { ...parentEnv };
56148
- if (env.MCP_CONNECTION_NONBLOCKING === undefined) {
56149
- env.MCP_CONNECTION_NONBLOCKING = "true";
56150
- }
56151
- if (env.ENABLE_PROMPT_CACHING_1H === undefined) {
56152
- env.ENABLE_PROMPT_CACHING_1H = "true";
56153
- }
56154
- if (account?.home) {
56155
- env.HOME = account.home;
56156
- env.USERPROFILE = account.home;
56157
- delete env.ANTHROPIC_API_KEY;
56158
- delete env.CLAUDE_CODE_OAUTH_TOKEN;
56159
- } else if (account?.apiKey) {
56160
- env.ANTHROPIC_API_KEY = account.apiKey;
56161
- delete env.CLAUDE_CODE_OAUTH_TOKEN;
56162
- }
56163
- return env;
56164
- }
56165
- function isErrorResultEvent(event) {
56166
- const ev = event;
56167
- if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
56168
- return true;
56169
- if (ev.is_error === true)
56170
- return true;
56171
- return false;
56172
- }
56173
- function materializeMcpConfig(config, sessionId, opts = {}) {
56174
- if (opts.inline) {
56175
- return { mode: "inline", value: JSON.stringify(config) };
56176
- }
56177
- const dir = opts.tmpDirOverride ?? tmpdir();
56178
- const path2 = join8(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
56179
- writeFileSync4(path2, JSON.stringify(config), { mode: 384 });
56180
- return { mode: "file", path: path2 };
56181
- }
56182
- function buildPermissionArgs(opts) {
56183
- const args = [];
56184
- if (opts.permissionMode === "bypass" && !opts.platformConfig) {
56185
- args.push("--dangerously-skip-permissions");
56186
- return { args, tempFile: null };
56187
- }
56188
- if (!opts.platformConfig) {
56189
- throw new Error(`platformConfig is required when permissionMode is '${opts.permissionMode}'`);
56190
- }
56191
- const mcpEnv = {
56192
- PLATFORM_TYPE: opts.platformConfig.type,
56193
- PLATFORM_URL: opts.platformConfig.url,
56194
- PLATFORM_TOKEN: opts.platformConfig.token,
56195
- PLATFORM_CHANNEL_ID: opts.platformConfig.channelId,
56196
- PLATFORM_THREAD_ID: opts.threadId || "",
56197
- ALLOWED_USERS: opts.platformConfig.allowedUsers.join(","),
56198
- DEBUG: opts.debug ? "1" : "",
56199
- PERMISSION_TIMEOUT_MS: String(opts.permissionTimeoutMs),
56200
- SESSION_OWNER_USERNAME: opts.sessionOwnerUsername || ""
56201
- };
56202
- if (opts.platformConfig.appToken) {
56203
- mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
56204
- }
56205
- if (opts.workingDir) {
56206
- mcpEnv[OUTBOUND_ENV.SESSION_WORKING_DIR] = opts.workingDir;
56207
- }
56208
- if (opts.uploadDir) {
56209
- mcpEnv[OUTBOUND_ENV.SESSION_UPLOAD_DIR] = opts.uploadDir;
56210
- }
56211
- if (opts.outboundFiles?.enabled === false) {
56212
- mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_ENABLED] = "0";
56213
- }
56214
- if (typeof opts.outboundFiles?.maxBytes === "number" && Number.isFinite(opts.outboundFiles.maxBytes) && opts.outboundFiles.maxBytes > 0) {
56215
- mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_MAX_BYTES] = String(opts.outboundFiles.maxBytes);
56216
- }
56217
- const mcpConfig = {
56218
- mcpServers: {
56219
- "claude-threads-mcp": {
56220
- type: "stdio",
56221
- command: "node",
56222
- args: [opts.mcpServerPath],
56223
- env: mcpEnv
56224
- }
56225
- }
56226
- };
56227
- const materialized = materializeMcpConfig(mcpConfig, opts.sessionId, { inline: opts.inline });
56228
- let tempFile = null;
56229
- if (materialized.mode === "file") {
56230
- tempFile = materialized.path;
56231
- args.push("--mcp-config", materialized.path);
56232
- } else {
56233
- args.push("--mcp-config", materialized.value);
56234
- }
56235
- if (opts.permissionMode === "bypass") {
56236
- args.push("--dangerously-skip-permissions");
56237
- } else {
56238
- args.push("--permission-prompt-tool", "mcp__claude-threads-mcp__permission_prompt");
56239
- if (opts.permissionMode === "auto") {
56240
- args.push("--permission-mode", "auto");
56241
- }
56242
- }
56243
- return { args, tempFile };
56244
- }
56245
- var STDERR_PER_INSTANCE_CAP = 10240;
56246
- var STDERR_AGGREGATE_SOFT_CAP = 10 * 1024 * 1024;
56247
- var totalStderrBytes = 0;
56248
-
56249
- class ClaudeCli extends EventEmitter2 {
56250
- process = null;
56251
- options;
56252
- buffer = "";
56253
- debug = process.env.DEBUG === "1" || process.argv.includes("--debug");
56254
- statusFilePath = null;
56255
- lastStatusData = null;
56256
- stderrBuffer = "";
56257
- mcpConfigTempFile = null;
56258
- lastEmittedRateLimitDeadline = 0;
56259
- log;
56260
- constructor(options) {
56261
- super();
56262
- this.options = options;
56263
- this.log = options.logSessionId ? createLogger("claude").forSession(options.logSessionId) : createLogger("claude");
56264
- }
56265
- getStatusFilePath() {
56266
- return this.statusFilePath;
56267
- }
56268
- getStatusData() {
56269
- if (!this.statusFilePath)
56270
- return null;
56271
- try {
56272
- if (existsSync9(this.statusFilePath)) {
56273
- const data = readFileSync7(this.statusFilePath, "utf8");
56274
- this.lastStatusData = JSON.parse(data);
56275
- }
56276
- } catch (err) {
56277
- this.log.debug(`Failed to read status file: ${err}`);
56278
- }
56279
- return this.lastStatusData;
56280
- }
56281
- startStatusWatch() {
56282
- if (!this.statusFilePath) {
56283
- this.log.debug("No status file path, skipping status watch");
56284
- return;
56285
- }
56286
- this.log.debug(`Starting status watch: ${this.statusFilePath}`);
56287
- const checkStatus = () => {
56288
- const data = this.getStatusData();
56289
- if (data && data.timestamp !== this.lastStatusData?.timestamp) {
56290
- this.lastStatusData = data;
56291
- this.emit("status", data);
56292
- }
56293
- };
56294
- watchFile(this.statusFilePath, { interval: 1000 }, checkStatus);
56295
- }
56296
- stopStatusWatch() {
56297
- if (this.statusFilePath) {
56298
- unwatchFile(this.statusFilePath);
56299
- try {
56300
- if (existsSync9(this.statusFilePath)) {
56301
- unlinkSync2(this.statusFilePath);
55619
+ if (existsSync7(this.statusFilePath)) {
55620
+ unlinkSync(this.statusFilePath);
56302
55621
  }
56303
55622
  } catch {}
56304
55623
  }
@@ -56348,7 +55667,7 @@ class ClaudeCli extends EventEmitter2 {
56348
55667
  args.push("--append-system-prompt", this.options.appendSystemPrompt);
56349
55668
  }
56350
55669
  if (this.options.sessionId) {
56351
- this.statusFilePath = join8(tmpdir(), `claude-threads-status-${this.options.sessionId}.json`);
55670
+ this.statusFilePath = join5(tmpdir(), `claude-threads-status-${this.options.sessionId}.json`);
56352
55671
  const statusLineWriterPath = this.getStatusLineWriterPath();
56353
55672
  const statusLineSettings = {
56354
55673
  statusLine: {
@@ -56398,170 +55717,1001 @@ class ClaudeCli extends EventEmitter2 {
56398
55717
  this.buffer = "";
56399
55718
  totalStderrBytes -= this.stderrBuffer.length;
56400
55719
  if (this.mcpConfigTempFile) {
56401
- const path2 = this.mcpConfigTempFile;
55720
+ const path = this.mcpConfigTempFile;
56402
55721
  this.mcpConfigTempFile = null;
56403
55722
  try {
56404
- unlinkSync2(path2);
55723
+ unlinkSync(path);
56405
55724
  } catch {}
56406
55725
  }
56407
55726
  this.emit("exit", code);
56408
55727
  });
56409
55728
  }
56410
- sendMessage(content) {
56411
- if (!this.process?.stdin)
56412
- throw new Error("Not running");
56413
- const msg = JSON.stringify({
56414
- type: "user",
56415
- message: { role: "user", content }
56416
- }) + `
56417
- `;
56418
- const preview = content.substring(0, 50);
56419
- this.log.debug(`Sending: ${preview}...`);
56420
- if (process.env.INTEGRATION_TEST === "1") {
56421
- const stack = new Error().stack?.split(`
56422
- `).slice(2, 6).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
56423
- process.stderr.write(`[claude-cli sendMessage pid=${this.process.pid}] ${preview} | ${stack}
56424
- `);
55729
+ sendMessage(content) {
55730
+ if (!this.process?.stdin)
55731
+ throw new Error("Not running");
55732
+ const msg = JSON.stringify({
55733
+ type: "user",
55734
+ message: { role: "user", content }
55735
+ }) + `
55736
+ `;
55737
+ const preview = content.substring(0, 50);
55738
+ this.log.debug(`Sending: ${preview}...`);
55739
+ if (process.env.INTEGRATION_TEST === "1") {
55740
+ const stack = new Error().stack?.split(`
55741
+ `).slice(2, 6).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
55742
+ process.stderr.write(`[claude-cli sendMessage pid=${this.process.pid}] ${preview} | ${stack}
55743
+ `);
55744
+ }
55745
+ this.process.stdin.write(msg);
55746
+ }
55747
+ sendToolResult(toolUseId, content) {
55748
+ if (!this.process?.stdin)
55749
+ throw new Error("Not running");
55750
+ const msg = JSON.stringify({
55751
+ type: "user",
55752
+ message: {
55753
+ role: "user",
55754
+ content: [{
55755
+ type: "tool_result",
55756
+ tool_use_id: toolUseId,
55757
+ content: typeof content === "string" ? content : JSON.stringify(content)
55758
+ }]
55759
+ }
55760
+ }) + `
55761
+ `;
55762
+ this.log.debug(`Sending tool_result for ${toolUseId}`);
55763
+ this.process.stdin.write(msg);
55764
+ }
55765
+ parseOutput(data) {
55766
+ this.buffer += data;
55767
+ const lines = this.buffer.split(`
55768
+ `);
55769
+ this.buffer = lines.pop() || "";
55770
+ for (const line of lines) {
55771
+ const trimmed = line.trim();
55772
+ if (!trimmed)
55773
+ continue;
55774
+ try {
55775
+ const event = JSON.parse(trimmed);
55776
+ this.emit("event", event);
55777
+ if (event.type === "result" && isErrorResultEvent(event)) {
55778
+ this.maybeEmitRateLimit(trimmed);
55779
+ }
55780
+ } catch {}
55781
+ }
55782
+ }
55783
+ maybeEmitRateLimit(text) {
55784
+ const hit = detectRateLimit(text);
55785
+ if (!hit.detected)
55786
+ return;
55787
+ const newDeadline = cooldownDeadline(hit);
55788
+ const MIN_ADVANCE_MS = 60000;
55789
+ if (newDeadline - this.lastEmittedRateLimitDeadline < MIN_ADVANCE_MS)
55790
+ return;
55791
+ this.lastEmittedRateLimitDeadline = newDeadline;
55792
+ this.log.warn(`Rate limit detected: ${hit.matched ?? "(no match text)"}`);
55793
+ this.emit("rate-limit", hit);
55794
+ }
55795
+ isRunning() {
55796
+ return this.process !== null;
55797
+ }
55798
+ getLastStderr() {
55799
+ return this.stderrBuffer;
55800
+ }
55801
+ isPermanentFailure() {
55802
+ const stderr = this.stderrBuffer;
55803
+ if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
55804
+ return true;
55805
+ }
55806
+ if (stderr.includes("No conversation found with session ID")) {
55807
+ return true;
55808
+ }
55809
+ return false;
55810
+ }
55811
+ getPermanentFailureReason() {
55812
+ const stderr = this.stderrBuffer;
55813
+ if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
55814
+ return "Claude browser bridge state from a previous session is no longer accessible. This typically happens when a session with Chrome integration is resumed after a restart.";
55815
+ }
55816
+ if (stderr.includes("No conversation found with session ID")) {
55817
+ return "The conversation history for this session no longer exists. This can happen if Claude's history was cleared or if the session was created on a different machine.";
55818
+ }
55819
+ return null;
55820
+ }
55821
+ kill() {
55822
+ this.stopStatusWatch();
55823
+ if (!this.process) {
55824
+ this.log.debug("Kill called but process not running");
55825
+ return Promise.resolve();
55826
+ }
55827
+ const proc = this.process;
55828
+ const pid = proc.pid;
55829
+ this.process = null;
55830
+ this.log.debug(`Killing Claude process (pid=${pid})`);
55831
+ return new Promise((resolve4) => {
55832
+ this.log.debug("Sending first SIGINT");
55833
+ proc.kill("SIGINT");
55834
+ const secondSigint = setTimeout(() => {
55835
+ try {
55836
+ this.log.debug("Sending second SIGINT");
55837
+ proc.kill("SIGINT");
55838
+ } catch {}
55839
+ }, 100);
55840
+ const forceKillTimeout = setTimeout(() => {
55841
+ try {
55842
+ this.log.debug("Sending SIGTERM (force kill)");
55843
+ proc.kill("SIGTERM");
55844
+ } catch {}
55845
+ }, 2000);
55846
+ proc.once("exit", (code) => {
55847
+ this.log.debug(`Claude process exited (code=${code})`);
55848
+ clearTimeout(secondSigint);
55849
+ clearTimeout(forceKillTimeout);
55850
+ resolve4();
55851
+ });
55852
+ });
55853
+ }
55854
+ interrupt() {
55855
+ if (!this.process) {
55856
+ this.log.debug("Interrupt called but process not running");
55857
+ return false;
55858
+ }
55859
+ this.log.debug(`Interrupting Claude process (pid=${this.process.pid})`);
55860
+ this.process.kill("SIGINT");
55861
+ return true;
55862
+ }
55863
+ buildChildEnv() {
55864
+ return buildClaudeChildEnv(process.env, this.options.account);
55865
+ }
55866
+ getMcpServerPath() {
55867
+ const __filename2 = fileURLToPath3(import.meta.url);
55868
+ const __dirname4 = dirname4(__filename2);
55869
+ const bundledPath = resolve3(__dirname4, "mcp", "mcp-server.js");
55870
+ if (existsSync7(bundledPath)) {
55871
+ return bundledPath;
55872
+ }
55873
+ return resolve3(__dirname4, "..", "mcp", "mcp-server.js");
55874
+ }
55875
+ getStatusLineWriterPath() {
55876
+ const __filename2 = fileURLToPath3(import.meta.url);
55877
+ const __dirname4 = dirname4(__filename2);
55878
+ const bundledPath = resolve3(__dirname4, "statusline", "writer.js");
55879
+ if (existsSync7(bundledPath)) {
55880
+ return bundledPath;
55881
+ }
55882
+ return resolve3(__dirname4, "..", "statusline", "writer.js");
55883
+ }
55884
+ }
55885
+
55886
+ // src/claude/usage-probe.ts
55887
+ init_logger();
55888
+ var log9 = createLogger("usage-probe");
55889
+ var DEFAULT_USAGE_PROBE_TIMEOUT_MS = 30000;
55890
+ function parseUsageOutput(text) {
55891
+ if (!text)
55892
+ return null;
55893
+ const sessionMatch = text.match(/Current session:\s*(\d+)%\s*used(?:\s*·\s*resets\s*([^\n]+?))?\s*(?:\n|$)/i);
55894
+ const weekAllMatch = text.match(/Current week(?: \(all models\))?:\s*(\d+)%\s*used(?:\s*·\s*resets\s*([^\n]+?))?\s*(?:\n|$)/i);
55895
+ if (!sessionMatch && !weekAllMatch)
55896
+ return null;
55897
+ const sessionPct = sessionMatch ? clampPct(Number(sessionMatch[1])) : 0;
55898
+ const weekAllModelsPct = weekAllMatch ? clampPct(Number(weekAllMatch[1])) : 0;
55899
+ let weekPerModelPct = null;
55900
+ const perModelRe = /Current week \((?!all models\))[^)]+\):\s*(\d+)%\s*used/gi;
55901
+ for (const m of text.matchAll(perModelRe)) {
55902
+ const pct = clampPct(Number(m[1]));
55903
+ weekPerModelPct = weekPerModelPct === null ? pct : Math.max(weekPerModelPct, pct);
55904
+ }
55905
+ return {
55906
+ sessionPct,
55907
+ weekAllModelsPct,
55908
+ weekPerModelPct,
55909
+ sessionResetsAt: sessionMatch?.[2]?.trim() || null,
55910
+ weekResetsAt: weekAllMatch?.[2]?.trim() || null
55911
+ };
55912
+ }
55913
+ function usageLoadScore(usage) {
55914
+ return Math.max(usage.sessionPct, usage.weekAllModelsPct, usage.weekPerModelPct ?? 0);
55915
+ }
55916
+ function clampPct(n) {
55917
+ if (!Number.isFinite(n))
55918
+ return 0;
55919
+ return Math.max(0, Math.min(100, Math.round(n)));
55920
+ }
55921
+ async function probeAccountUsage(account, opts = {}) {
55922
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_USAGE_PROBE_TIMEOUT_MS;
55923
+ const claudePath = getClaudePath();
55924
+ const env = buildClaudeChildEnv(process.env, account);
55925
+ return new Promise((resolve4) => {
55926
+ let settled = false;
55927
+ const finish = (value) => {
55928
+ if (settled)
55929
+ return;
55930
+ settled = true;
55931
+ clearTimeout(timer);
55932
+ resolve4(value);
55933
+ };
55934
+ let child;
55935
+ try {
55936
+ child = crossSpawn(claudePath, ["-p", "/usage", "--output-format", "json"], {
55937
+ env,
55938
+ stdio: ["ignore", "pipe", "pipe"]
55939
+ });
55940
+ } catch (err) {
55941
+ log9.warn(`Failed to spawn /usage probe for "${account.id}": ${err}`);
55942
+ resolve4(null);
55943
+ return;
55944
+ }
55945
+ const timer = setTimeout(() => {
55946
+ log9.warn(`/usage probe for "${account.id}" timed out after ${timeoutMs}ms`);
55947
+ try {
55948
+ child.kill("SIGKILL");
55949
+ } catch {}
55950
+ finish(null);
55951
+ }, timeoutMs);
55952
+ let stdout = "";
55953
+ child.stdout?.on("data", (chunk) => {
55954
+ stdout += chunk.toString();
55955
+ });
55956
+ child.stderr?.on("data", () => {});
55957
+ child.on("error", (err) => {
55958
+ log9.warn(`/usage probe for "${account.id}" errored: ${err}`);
55959
+ finish(null);
55960
+ });
55961
+ child.on("close", () => {
55962
+ const usage = extractUsage(stdout);
55963
+ if (!usage) {
55964
+ log9.debug(`/usage probe for "${account.id}" returned no parseable usage`);
55965
+ }
55966
+ finish(usage);
55967
+ });
55968
+ });
55969
+ }
55970
+ function extractUsage(stdout) {
55971
+ const trimmed = stdout.trim();
55972
+ if (!trimmed)
55973
+ return null;
55974
+ let text = trimmed;
55975
+ try {
55976
+ const parsed = JSON.parse(trimmed);
55977
+ if (typeof parsed.result === "string") {
55978
+ text = parsed.result;
55979
+ }
55980
+ } catch {}
55981
+ return parseUsageOutput(text);
55982
+ }
55983
+
55984
+ // src/claude/account-pool.ts
55985
+ var log10 = createLogger("account-pool");
55986
+ var ACTIVE_SESSION_LOAD_PENALTY = 5;
55987
+ function hashThreadId(threadId) {
55988
+ let h = 2166136261;
55989
+ for (let i2 = 0;i2 < threadId.length; i2++) {
55990
+ h ^= threadId.charCodeAt(i2);
55991
+ h = Math.imul(h, 16777619);
55992
+ }
55993
+ return h >>> 0;
55994
+ }
55995
+
55996
+ class AccountPool {
55997
+ accounts;
55998
+ byId;
55999
+ orderIndex;
56000
+ activeCounts = new Map;
56001
+ coolingUntil = new Map;
56002
+ usage = new Map;
56003
+ rrCursor = 0;
56004
+ constructor(accounts) {
56005
+ this.accounts = (accounts ?? []).filter((acc) => {
56006
+ const hasAuth = !!acc.home || !!acc.apiKey;
56007
+ if (!hasAuth) {
56008
+ log10.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
56009
+ return false;
56010
+ }
56011
+ if (acc.home && acc.apiKey) {
56012
+ log10.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
56013
+ return false;
56014
+ }
56015
+ return true;
56016
+ });
56017
+ this.byId = new Map(this.accounts.map((acc) => [acc.id, acc]));
56018
+ this.orderIndex = new Map(this.accounts.map((acc, i2) => [acc.id, i2]));
56019
+ for (const acc of this.accounts) {
56020
+ this.activeCounts.set(acc.id, 0);
56021
+ this.usage.set(acc.id, null);
56022
+ }
56023
+ }
56024
+ get isEmpty() {
56025
+ return this.accounts.length === 0;
56026
+ }
56027
+ get size() {
56028
+ return this.accounts.length;
56029
+ }
56030
+ get all() {
56031
+ return this.accounts;
56032
+ }
56033
+ acquire(preferredId, threadId, opts) {
56034
+ if (this.isEmpty)
56035
+ return null;
56036
+ if (preferredId) {
56037
+ const preferred = this.byId.get(preferredId);
56038
+ if (preferred) {
56039
+ this.incrementActive(preferred.id);
56040
+ return preferred;
56041
+ }
56042
+ log10.warn(`Preferred account "${preferredId}" not in pool — falling back to usage balancing`);
56043
+ }
56044
+ const now = Date.now();
56045
+ const n = this.accounts.length;
56046
+ if (threadId && !opts?.balanceByUsage) {
56047
+ const sticky = this.accounts[hashThreadId(threadId) % n];
56048
+ const cooling = this.coolingUntil.get(sticky.id) ?? 0;
56049
+ if (cooling <= now) {
56050
+ this.incrementActive(sticky.id);
56051
+ return sticky;
56052
+ }
56053
+ }
56054
+ const chosen = this.selectLeastLoaded(now);
56055
+ if (!chosen) {
56056
+ log10.warn(`All ${n} accounts are in rate-limit cooldown`);
56057
+ return null;
56058
+ }
56059
+ this.incrementActive(chosen.id);
56060
+ return chosen;
56061
+ }
56062
+ selectLeastLoaded(now) {
56063
+ const n = this.accounts.length;
56064
+ let best = null;
56065
+ let bestScore = Number.POSITIVE_INFINITY;
56066
+ let bestIdx = -1;
56067
+ for (let k = 0;k < n; k++) {
56068
+ const idx = (this.rrCursor + k) % n;
56069
+ const acc = this.accounts[idx];
56070
+ if ((this.coolingUntil.get(acc.id) ?? 0) > now)
56071
+ continue;
56072
+ const score = this.effectiveLoad(acc.id);
56073
+ if (best === null || score < bestScore) {
56074
+ best = acc;
56075
+ bestScore = score;
56076
+ bestIdx = idx;
56077
+ }
56078
+ }
56079
+ if (bestIdx >= 0)
56080
+ this.rrCursor = (bestIdx + 1) % n;
56081
+ return best;
56082
+ }
56083
+ loadScore(accountId) {
56084
+ const u = this.usage.get(accountId);
56085
+ return u ? usageLoadScore(u) : Number.POSITIVE_INFINITY;
56086
+ }
56087
+ effectiveLoad(accountId) {
56088
+ const base = this.loadScore(accountId);
56089
+ const active = this.activeCounts.get(accountId) ?? 0;
56090
+ return base + ACTIVE_SESSION_LOAD_PENALTY * active;
56091
+ }
56092
+ release(accountId) {
56093
+ const current = this.activeCounts.get(accountId);
56094
+ if (current === undefined)
56095
+ return;
56096
+ this.activeCounts.set(accountId, Math.max(0, current - 1));
56097
+ }
56098
+ setUsage(accountId, usage) {
56099
+ if (!this.byId.has(accountId))
56100
+ return;
56101
+ this.usage.set(accountId, usage);
56102
+ if (usage) {
56103
+ log10.debug(`Account "${accountId}" usage: ${usageLoadScore(usage)}% (load score)`);
56104
+ }
56105
+ }
56106
+ markCooling(accountId, untilEpochMs) {
56107
+ if (!this.byId.has(accountId)) {
56108
+ log10.warn(`markCooling called for unknown account "${accountId}"`);
56109
+ return;
56110
+ }
56111
+ const existing = this.coolingUntil.get(accountId) ?? 0;
56112
+ if (untilEpochMs > existing) {
56113
+ this.coolingUntil.set(accountId, untilEpochMs);
56114
+ const minutes = Math.ceil((untilEpochMs - Date.now()) / 60000);
56115
+ log10.info(`Account "${accountId}" cooling for ~${minutes}min`);
56116
+ }
56117
+ }
56118
+ get(accountId) {
56119
+ return this.byId.get(accountId);
56120
+ }
56121
+ status() {
56122
+ const now = Date.now();
56123
+ return this.accounts.map((acc) => {
56124
+ const cooling = this.coolingUntil.get(acc.id) ?? 0;
56125
+ const usage = this.usage.get(acc.id) ?? null;
56126
+ return {
56127
+ id: acc.id,
56128
+ displayName: acc.displayName ?? acc.id,
56129
+ activeSessions: this.activeCounts.get(acc.id) ?? 0,
56130
+ coolingUntil: cooling > now ? cooling : null,
56131
+ usagePercent: usage ? usageLoadScore(usage) : null
56132
+ };
56133
+ });
56134
+ }
56135
+ incrementActive(accountId) {
56136
+ this.activeCounts.set(accountId, (this.activeCounts.get(accountId) ?? 0) + 1);
56137
+ }
56138
+ }
56139
+
56140
+ // src/cleanup/scheduler.ts
56141
+ init_logger();
56142
+ import { existsSync as existsSync9 } from "fs";
56143
+ import { readdir, rm } from "fs/promises";
56144
+ import { join as join8 } from "path";
56145
+
56146
+ // src/persistence/thread-logger.ts
56147
+ init_logger();
56148
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, appendFileSync, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, rmdirSync, readFileSync as readFileSync7, chmodSync as chmodSync4 } from "fs";
56149
+ import { homedir as homedir4 } from "os";
56150
+ import { join as join6, dirname as dirname5 } from "path";
56151
+ var log11 = createLogger("thread-log");
56152
+ var LOGS_BASE_DIR = join6(homedir4(), ".claude-threads", "logs");
56153
+
56154
+ class ThreadLoggerImpl {
56155
+ platformId;
56156
+ threadId;
56157
+ claudeSessionId;
56158
+ enabled;
56159
+ bufferSize;
56160
+ flushIntervalMs;
56161
+ logPath;
56162
+ buffer = [];
56163
+ flushTimer = null;
56164
+ isClosed = false;
56165
+ constructor(platformId, threadId, claudeSessionId, options) {
56166
+ this.platformId = platformId;
56167
+ this.threadId = threadId;
56168
+ this.claudeSessionId = claudeSessionId;
56169
+ this.enabled = options?.enabled ?? true;
56170
+ this.bufferSize = options?.bufferSize ?? 10;
56171
+ this.flushIntervalMs = options?.flushIntervalMs ?? 1000;
56172
+ this.logPath = join6(LOGS_BASE_DIR, platformId, `${claudeSessionId}.jsonl`);
56173
+ if (this.enabled) {
56174
+ const dir = dirname5(this.logPath);
56175
+ if (!existsSync8(dir)) {
56176
+ mkdirSync4(dir, { recursive: true });
56177
+ }
56178
+ this.flushTimer = setInterval(() => {
56179
+ this.flushSync();
56180
+ }, this.flushIntervalMs);
56181
+ log11.debug(`Thread logger initialized: ${this.logPath}`);
56182
+ }
56183
+ }
56184
+ isEnabled() {
56185
+ return this.enabled;
56186
+ }
56187
+ getLogPath() {
56188
+ return this.logPath;
56189
+ }
56190
+ logEvent(event) {
56191
+ if (!this.enabled || this.isClosed)
56192
+ return;
56193
+ const entry = {
56194
+ ts: Date.now(),
56195
+ sessionId: this.claudeSessionId,
56196
+ type: "claude_event",
56197
+ eventType: event.type,
56198
+ event
56199
+ };
56200
+ this.addEntry(entry);
56201
+ }
56202
+ logUserMessage(username, message, displayName, hasFiles) {
56203
+ if (!this.enabled || this.isClosed)
56204
+ return;
56205
+ const entry = {
56206
+ ts: Date.now(),
56207
+ sessionId: this.claudeSessionId,
56208
+ type: "user_message",
56209
+ username,
56210
+ displayName,
56211
+ message,
56212
+ hasFiles
56213
+ };
56214
+ this.addEntry(entry);
56215
+ }
56216
+ logLifecycle(action, details) {
56217
+ if (!this.enabled || this.isClosed)
56218
+ return;
56219
+ const entry = {
56220
+ ts: Date.now(),
56221
+ sessionId: this.claudeSessionId,
56222
+ type: "lifecycle",
56223
+ action,
56224
+ ...details
56225
+ };
56226
+ this.addEntry(entry);
56227
+ }
56228
+ logCommand(command, args, username) {
56229
+ if (!this.enabled || this.isClosed)
56230
+ return;
56231
+ const entry = {
56232
+ ts: Date.now(),
56233
+ sessionId: this.claudeSessionId,
56234
+ type: "command",
56235
+ command,
56236
+ args,
56237
+ username
56238
+ };
56239
+ this.addEntry(entry);
56240
+ }
56241
+ logPermission(action, permission, username) {
56242
+ if (!this.enabled || this.isClosed)
56243
+ return;
56244
+ const entry = {
56245
+ ts: Date.now(),
56246
+ sessionId: this.claudeSessionId,
56247
+ type: "permission",
56248
+ action,
56249
+ permission,
56250
+ username
56251
+ };
56252
+ this.addEntry(entry);
56253
+ }
56254
+ logReaction(action, username, emoji, answer) {
56255
+ if (!this.enabled || this.isClosed)
56256
+ return;
56257
+ const entry = {
56258
+ ts: Date.now(),
56259
+ sessionId: this.claudeSessionId,
56260
+ type: "reaction",
56261
+ action,
56262
+ username,
56263
+ emoji,
56264
+ answer
56265
+ };
56266
+ this.addEntry(entry);
56267
+ }
56268
+ logExecutor(executor, operation, postId, details, method) {
56269
+ if (!this.enabled || this.isClosed)
56270
+ return;
56271
+ const entry = {
56272
+ ts: Date.now(),
56273
+ sessionId: this.claudeSessionId,
56274
+ type: "executor",
56275
+ executor,
56276
+ operation,
56277
+ postId,
56278
+ method,
56279
+ details
56280
+ };
56281
+ this.addEntry(entry);
56282
+ }
56283
+ async flush() {
56284
+ this.flushSync();
56285
+ }
56286
+ async close() {
56287
+ if (this.isClosed)
56288
+ return;
56289
+ this.isClosed = true;
56290
+ if (this.flushTimer) {
56291
+ clearInterval(this.flushTimer);
56292
+ this.flushTimer = null;
56425
56293
  }
56426
- this.process.stdin.write(msg);
56294
+ this.flushSync();
56295
+ log11.debug(`Thread logger closed: ${this.logPath}`);
56427
56296
  }
56428
- sendToolResult(toolUseId, content) {
56429
- if (!this.process?.stdin)
56430
- throw new Error("Not running");
56431
- const msg = JSON.stringify({
56432
- type: "user",
56433
- message: {
56434
- role: "user",
56435
- content: [{
56436
- type: "tool_result",
56437
- tool_use_id: toolUseId,
56438
- content: typeof content === "string" ? content : JSON.stringify(content)
56439
- }]
56440
- }
56441
- }) + `
56297
+ addEntry(entry) {
56298
+ this.buffer.push(entry);
56299
+ if (this.buffer.length >= this.bufferSize) {
56300
+ this.flushSync();
56301
+ }
56302
+ }
56303
+ flushSync() {
56304
+ if (this.buffer.length === 0)
56305
+ return;
56306
+ try {
56307
+ const lines = this.buffer.map((entry) => JSON.stringify(entry)).join(`
56308
+ `) + `
56442
56309
  `;
56443
- this.log.debug(`Sending tool_result for ${toolUseId}`);
56444
- this.process.stdin.write(msg);
56310
+ const isNewFile = !existsSync8(this.logPath);
56311
+ appendFileSync(this.logPath, lines, { encoding: "utf8", mode: 384 });
56312
+ if (isNewFile) {
56313
+ chmodSync4(this.logPath, 384);
56314
+ }
56315
+ this.buffer = [];
56316
+ } catch (err) {
56317
+ log11.error(`Failed to flush thread log: ${err}`);
56318
+ }
56445
56319
  }
56446
- parseOutput(data) {
56447
- this.buffer += data;
56448
- const lines = this.buffer.split(`
56449
- `);
56450
- this.buffer = lines.pop() || "";
56451
- for (const line of lines) {
56452
- const trimmed = line.trim();
56453
- if (!trimmed)
56320
+ }
56321
+
56322
+ class DisabledThreadLogger {
56323
+ logEvent() {}
56324
+ logUserMessage() {}
56325
+ logLifecycle() {}
56326
+ logCommand() {}
56327
+ logPermission() {}
56328
+ logReaction() {}
56329
+ logExecutor() {}
56330
+ async flush() {}
56331
+ async close() {}
56332
+ isEnabled() {
56333
+ return false;
56334
+ }
56335
+ getLogPath() {
56336
+ return "";
56337
+ }
56338
+ }
56339
+ function createThreadLogger(platformId, threadId, claudeSessionId, options) {
56340
+ if (options?.enabled === false) {
56341
+ return new DisabledThreadLogger;
56342
+ }
56343
+ return new ThreadLoggerImpl(platformId, threadId, claudeSessionId, options);
56344
+ }
56345
+ function cleanupOldLogs(retentionDays = 30) {
56346
+ const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
56347
+ let deletedCount = 0;
56348
+ if (!existsSync8(LOGS_BASE_DIR)) {
56349
+ return 0;
56350
+ }
56351
+ try {
56352
+ const platformDirs = readdirSync2(LOGS_BASE_DIR);
56353
+ for (const platformId of platformDirs) {
56354
+ const platformDir = join6(LOGS_BASE_DIR, platformId);
56355
+ const stat = statSync2(platformDir);
56356
+ if (!stat.isDirectory())
56454
56357
  continue;
56358
+ const logFiles = readdirSync2(platformDir);
56359
+ for (const file of logFiles) {
56360
+ if (!file.endsWith(".jsonl"))
56361
+ continue;
56362
+ const filePath = join6(platformDir, file);
56363
+ try {
56364
+ const fileStat = statSync2(filePath);
56365
+ if (fileStat.mtimeMs < cutoffMs) {
56366
+ unlinkSync2(filePath);
56367
+ deletedCount++;
56368
+ log11.debug(`Deleted old log file: ${filePath}`);
56369
+ }
56370
+ } catch (err) {
56371
+ log11.warn(`Failed to check/delete log file ${filePath}: ${err}`);
56372
+ }
56373
+ }
56455
56374
  try {
56456
- const event = JSON.parse(trimmed);
56457
- this.emit("event", event);
56458
- if (event.type === "result" && isErrorResultEvent(event)) {
56459
- this.maybeEmitRateLimit(trimmed);
56375
+ const remaining = readdirSync2(platformDir);
56376
+ if (remaining.length === 0) {
56377
+ rmdirSync(platformDir);
56378
+ log11.debug(`Removed empty platform log directory: ${platformDir}`);
56460
56379
  }
56461
56380
  } catch {}
56462
56381
  }
56382
+ if (deletedCount > 0) {
56383
+ log11.info(`Cleaned up ${deletedCount} old log file(s)`);
56384
+ }
56385
+ } catch (err) {
56386
+ log11.error(`Failed to clean up old logs: ${err}`);
56463
56387
  }
56464
- maybeEmitRateLimit(text) {
56465
- const hit = detectRateLimit(text);
56466
- if (!hit.detected)
56467
- return;
56468
- const newDeadline = cooldownDeadline(hit);
56469
- const MIN_ADVANCE_MS = 60000;
56470
- if (newDeadline - this.lastEmittedRateLimitDeadline < MIN_ADVANCE_MS)
56471
- return;
56472
- this.lastEmittedRateLimitDeadline = newDeadline;
56473
- this.log.warn(`Rate limit detected: ${hit.matched ?? "(no match text)"}`);
56474
- this.emit("rate-limit", hit);
56388
+ return deletedCount;
56389
+ }
56390
+ function getLogFilePath(platformId, sessionId) {
56391
+ return join6(LOGS_BASE_DIR, platformId, `${sessionId}.jsonl`);
56392
+ }
56393
+ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
56394
+ const logPath = getLogFilePath(platformId, sessionId);
56395
+ log11.debug(`Reading log entries from: ${logPath}`);
56396
+ if (!existsSync8(logPath)) {
56397
+ log11.debug(`Log file does not exist: ${logPath}`);
56398
+ return [];
56475
56399
  }
56476
- isRunning() {
56477
- return this.process !== null;
56400
+ try {
56401
+ const content = readFileSync7(logPath, "utf8");
56402
+ const lines = content.trim().split(`
56403
+ `);
56404
+ log11.debug(`Log file has ${lines.length} lines`);
56405
+ const recentLines = lines.slice(-maxLines);
56406
+ const entries = [];
56407
+ for (const line of recentLines) {
56408
+ if (!line.trim())
56409
+ continue;
56410
+ try {
56411
+ entries.push(JSON.parse(line));
56412
+ } catch {}
56413
+ }
56414
+ log11.debug(`Parsed ${entries.length} log entries`);
56415
+ return entries;
56416
+ } catch (err) {
56417
+ log11.error(`Failed to read log file: ${err}`);
56418
+ return [];
56478
56419
  }
56479
- getLastStderr() {
56480
- return this.stderrBuffer;
56420
+ }
56421
+
56422
+ // src/cleanup/scheduler.ts
56423
+ init_worktree();
56424
+ var log13 = createLogger("cleanup");
56425
+ var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
56426
+ var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
56427
+
56428
+ class CleanupScheduler {
56429
+ intervalMs;
56430
+ logRetentionDays;
56431
+ threadLogsEnabled;
56432
+ sessionStore;
56433
+ maxWorktreeAgeMs;
56434
+ cleanupWorktrees;
56435
+ timer = null;
56436
+ isRunning = false;
56437
+ constructor(options) {
56438
+ this.intervalMs = options.intervalMs ?? DEFAULT_CLEANUP_INTERVAL_MS;
56439
+ this.logRetentionDays = options.logRetentionDays ?? 30;
56440
+ this.threadLogsEnabled = options.threadLogsEnabled ?? true;
56441
+ this.sessionStore = options.sessionStore;
56442
+ this.maxWorktreeAgeMs = options.maxWorktreeAgeMs ?? MAX_WORKTREE_AGE_MS;
56443
+ this.cleanupWorktrees = options.cleanupWorktrees ?? true;
56481
56444
  }
56482
- isPermanentFailure() {
56483
- const stderr = this.stderrBuffer;
56484
- if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
56485
- return true;
56445
+ start() {
56446
+ if (this.isRunning) {
56447
+ log13.debug("Cleanup scheduler already running");
56448
+ return;
56486
56449
  }
56487
- if (stderr.includes("No conversation found with session ID")) {
56488
- return true;
56450
+ this.isRunning = true;
56451
+ log13.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
56452
+ this.runCleanup().catch((err) => {
56453
+ log13.warn(`Initial cleanup failed: ${err}`);
56454
+ });
56455
+ this.timer = setInterval(() => {
56456
+ this.runCleanup().catch((err) => {
56457
+ log13.warn(`Periodic cleanup failed: ${err}`);
56458
+ });
56459
+ }, this.intervalMs);
56460
+ }
56461
+ stop() {
56462
+ if (this.timer) {
56463
+ clearInterval(this.timer);
56464
+ this.timer = null;
56489
56465
  }
56490
- return false;
56466
+ this.isRunning = false;
56467
+ log13.debug("Cleanup scheduler stopped");
56491
56468
  }
56492
- getPermanentFailureReason() {
56493
- const stderr = this.stderrBuffer;
56494
- if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
56495
- return "Claude browser bridge state from a previous session is no longer accessible. This typically happens when a session with Chrome integration is resumed after a restart.";
56469
+ async runCleanup() {
56470
+ const startTime = Date.now();
56471
+ log13.debug("Running background cleanup...");
56472
+ const stats = {
56473
+ logsDeleted: 0,
56474
+ worktreesCleaned: 0,
56475
+ metadataCleaned: 0,
56476
+ errors: []
56477
+ };
56478
+ const cleanupTasks = [
56479
+ this.cleanupLogs().catch((err) => {
56480
+ stats.errors.push(`Log cleanup: ${err}`);
56481
+ return 0;
56482
+ })
56483
+ ];
56484
+ if (this.cleanupWorktrees) {
56485
+ cleanupTasks.push(this.cleanupOrphanedWorktrees().catch((err) => {
56486
+ stats.errors.push(`Worktree cleanup: ${err}`);
56487
+ return { cleaned: 0, metadata: 0 };
56488
+ }));
56496
56489
  }
56497
- if (stderr.includes("No conversation found with session ID")) {
56498
- return "The conversation history for this session no longer exists. This can happen if Claude's history was cleared or if the session was created on a different machine.";
56490
+ const [logStats, worktreeStats = { cleaned: 0, metadata: 0 }] = await Promise.all(cleanupTasks);
56491
+ stats.logsDeleted = logStats;
56492
+ stats.worktreesCleaned = worktreeStats.cleaned;
56493
+ stats.metadataCleaned = worktreeStats.metadata;
56494
+ const elapsed = Date.now() - startTime;
56495
+ const totalCleaned = stats.logsDeleted + stats.worktreesCleaned + stats.metadataCleaned;
56496
+ if (totalCleaned > 0 || stats.errors.length > 0) {
56497
+ log13.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
56498
+ } else {
56499
+ log13.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
56499
56500
  }
56500
- return null;
56501
+ return stats;
56501
56502
  }
56502
- kill() {
56503
- this.stopStatusWatch();
56504
- if (!this.process) {
56505
- this.log.debug("Kill called but process not running");
56506
- return Promise.resolve();
56503
+ async cleanupLogs() {
56504
+ if (!this.threadLogsEnabled) {
56505
+ return 0;
56507
56506
  }
56508
- const proc = this.process;
56509
- const pid = proc.pid;
56510
- this.process = null;
56511
- this.log.debug(`Killing Claude process (pid=${pid})`);
56512
56507
  return new Promise((resolve4) => {
56513
- this.log.debug("Sending first SIGINT");
56514
- proc.kill("SIGINT");
56515
- const secondSigint = setTimeout(() => {
56516
- try {
56517
- this.log.debug("Sending second SIGINT");
56518
- proc.kill("SIGINT");
56519
- } catch {}
56520
- }, 100);
56521
- const forceKillTimeout = setTimeout(() => {
56522
- try {
56523
- this.log.debug("Sending SIGTERM (force kill)");
56524
- proc.kill("SIGTERM");
56525
- } catch {}
56526
- }, 2000);
56527
- proc.once("exit", (code) => {
56528
- this.log.debug(`Claude process exited (code=${code})`);
56529
- clearTimeout(secondSigint);
56530
- clearTimeout(forceKillTimeout);
56531
- resolve4();
56532
- });
56508
+ try {
56509
+ const deleted = cleanupOldLogs(this.logRetentionDays);
56510
+ resolve4(deleted);
56511
+ } catch (err) {
56512
+ log13.warn(`Log cleanup error: ${err}`);
56513
+ resolve4(0);
56514
+ }
56533
56515
  });
56534
56516
  }
56535
- interrupt() {
56536
- if (!this.process) {
56537
- this.log.debug("Interrupt called but process not running");
56538
- return false;
56517
+ async cleanupOrphanedWorktrees() {
56518
+ const worktreesDir = getWorktreesDir();
56519
+ const result = { cleaned: 0, metadata: 0 };
56520
+ if (!existsSync9(worktreesDir)) {
56521
+ log13.debug("No worktrees directory exists, nothing to clean");
56522
+ return result;
56539
56523
  }
56540
- this.log.debug(`Interrupting Claude process (pid=${this.process.pid})`);
56541
- this.process.kill("SIGINT");
56542
- return true;
56524
+ const persisted = this.sessionStore.load();
56525
+ const activeWorktrees = new Set;
56526
+ for (const session of persisted.values()) {
56527
+ if (session.worktreeInfo?.worktreePath) {
56528
+ activeWorktrees.add(session.worktreeInfo.worktreePath);
56529
+ }
56530
+ }
56531
+ const now = Date.now();
56532
+ try {
56533
+ const entries = await readdir(worktreesDir, { withFileTypes: true });
56534
+ for (const entry of entries) {
56535
+ if (!entry.isDirectory())
56536
+ continue;
56537
+ const worktreePath = join8(worktreesDir, entry.name);
56538
+ if (activeWorktrees.has(worktreePath)) {
56539
+ log13.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
56540
+ continue;
56541
+ }
56542
+ const meta = await readWorktreeMetadata(worktreePath);
56543
+ let shouldCleanup = false;
56544
+ let cleanupReason = "";
56545
+ if (meta) {
56546
+ const lastActivity = new Date(meta.lastActivityAt).getTime();
56547
+ const age = now - lastActivity;
56548
+ if (meta.sessionId && age < this.maxWorktreeAgeMs) {
56549
+ log13.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
56550
+ continue;
56551
+ }
56552
+ const merged = age >= this.maxWorktreeAgeMs ? await isBranchMerged(meta.repoRoot, meta.branch).catch(() => false) : false;
56553
+ if (merged) {
56554
+ shouldCleanup = true;
56555
+ cleanupReason = `branch "${meta.branch}" was merged`;
56556
+ } else if (age >= this.maxWorktreeAgeMs) {
56557
+ shouldCleanup = true;
56558
+ cleanupReason = `inactive for ${Math.round(age / 3600000)}h`;
56559
+ } else {
56560
+ log13.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
56561
+ continue;
56562
+ }
56563
+ } else {
56564
+ shouldCleanup = true;
56565
+ cleanupReason = "no metadata";
56566
+ }
56567
+ if (!shouldCleanup)
56568
+ continue;
56569
+ log13.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
56570
+ try {
56571
+ if (meta?.repoRoot) {
56572
+ await removeWorktree(meta.repoRoot, worktreePath);
56573
+ } else {
56574
+ await rm(worktreePath, { recursive: true, force: true });
56575
+ }
56576
+ result.cleaned++;
56577
+ await removeWorktreeMetadata(worktreePath);
56578
+ result.metadata++;
56579
+ } catch (err) {
56580
+ log13.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
56581
+ try {
56582
+ await rm(worktreePath, { recursive: true, force: true });
56583
+ result.cleaned++;
56584
+ await removeWorktreeMetadata(worktreePath);
56585
+ result.metadata++;
56586
+ } catch (rmErr) {
56587
+ log13.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
56588
+ }
56589
+ }
56590
+ }
56591
+ } catch (err) {
56592
+ log13.warn(`Failed to scan worktrees directory: ${err}`);
56593
+ }
56594
+ return result;
56543
56595
  }
56544
- buildChildEnv() {
56545
- return buildClaudeChildEnv(process.env, this.options.account);
56596
+ }
56597
+ // src/operations/monitor/handler.ts
56598
+ init_logger();
56599
+
56600
+ // src/session/lifecycle-fsm.ts
56601
+ init_logger();
56602
+ var log14 = createLogger("fsm");
56603
+ var ALLOWED_TRANSITIONS = {
56604
+ starting: new Set(["active", "paused", "interrupted", "cancelling", "restarting"]),
56605
+ active: new Set([
56606
+ "active",
56607
+ "processing",
56608
+ "paused",
56609
+ "interrupted",
56610
+ "restarting",
56611
+ "cancelling",
56612
+ "ending"
56613
+ ]),
56614
+ processing: new Set([
56615
+ "active",
56616
+ "paused",
56617
+ "interrupted",
56618
+ "restarting",
56619
+ "cancelling"
56620
+ ]),
56621
+ paused: new Set(["active", "cancelling", "restarting"]),
56622
+ interrupted: new Set(["active", "cancelling", "restarting", "paused"]),
56623
+ restarting: new Set(["active", "paused", "cancelling"]),
56624
+ cancelling: new Set(["ending"]),
56625
+ ending: new Set
56626
+ };
56627
+ function checkTransition(from, to, sessionId) {
56628
+ const allowed = ALLOWED_TRANSITIONS[from];
56629
+ if (allowed.has(to))
56630
+ return;
56631
+ const msg = `illegal lifecycle transition ${from} -> ${to}`;
56632
+ const payload = {
56633
+ event: "fsm.illegal_transition",
56634
+ from,
56635
+ to,
56636
+ sessionId
56637
+ };
56638
+ if (process.env.CLAUDE_THREADS_FSM_STRICT === "1") {
56639
+ throw new Error(`${msg} (sessionId=${sessionId})`);
56546
56640
  }
56547
- getMcpServerPath() {
56548
- const __filename2 = fileURLToPath3(import.meta.url);
56549
- const __dirname4 = dirname6(__filename2);
56550
- const bundledPath = resolve3(__dirname4, "mcp", "mcp-server.js");
56551
- if (existsSync9(bundledPath)) {
56552
- return bundledPath;
56553
- }
56554
- return resolve3(__dirname4, "..", "mcp", "mcp-server.js");
56641
+ log14.warn(msg, payload);
56642
+ }
56643
+
56644
+ // src/session/timer-manager.ts
56645
+ function createSessionTimers() {
56646
+ return {
56647
+ updateTimer: null,
56648
+ typingTimer: null,
56649
+ statusBarTimer: null
56650
+ };
56651
+ }
56652
+ function clearAllTimers(timers) {
56653
+ if (timers.updateTimer) {
56654
+ clearTimeout(timers.updateTimer);
56655
+ timers.updateTimer = null;
56555
56656
  }
56556
- getStatusLineWriterPath() {
56557
- const __filename2 = fileURLToPath3(import.meta.url);
56558
- const __dirname4 = dirname6(__filename2);
56559
- const bundledPath = resolve3(__dirname4, "statusline", "writer.js");
56560
- if (existsSync9(bundledPath)) {
56561
- return bundledPath;
56562
- }
56563
- return resolve3(__dirname4, "..", "statusline", "writer.js");
56657
+ if (timers.typingTimer) {
56658
+ clearInterval(timers.typingTimer);
56659
+ timers.typingTimer = null;
56660
+ }
56661
+ if (timers.statusBarTimer) {
56662
+ clearInterval(timers.statusBarTimer);
56663
+ timers.statusBarTimer = null;
56664
+ }
56665
+ }
56666
+
56667
+ // src/session/types.ts
56668
+ function createSessionLifecycle() {
56669
+ return {
56670
+ state: "starting",
56671
+ resumeFailCount: 0,
56672
+ hasClaudeResponded: false
56673
+ };
56674
+ }
56675
+ function createResumedLifecycle(resumeFailCount = 0) {
56676
+ return {
56677
+ state: "active",
56678
+ resumeFailCount,
56679
+ hasClaudeResponded: true
56680
+ };
56681
+ }
56682
+ function isSessionRestarting(session) {
56683
+ return session.lifecycle.state === "restarting";
56684
+ }
56685
+ function isSessionCancelled(session) {
56686
+ return session.lifecycle.state === "cancelling";
56687
+ }
56688
+ function transitionTo(session, newState) {
56689
+ checkTransition(session.lifecycle.state, newState, session.sessionId);
56690
+ session.lifecycle.state = newState;
56691
+ }
56692
+ function markClaudeResponded(session) {
56693
+ session.lifecycle.hasClaudeResponded = true;
56694
+ if (session.lifecycle.state === "starting") {
56695
+ session.lifecycle.state = "active";
56696
+ }
56697
+ }
56698
+ function getSessionStatus(session) {
56699
+ if (session.isProcessing) {
56700
+ return session.lifecycle.hasClaudeResponded ? "active" : "starting";
56701
+ }
56702
+ return "idle";
56703
+ }
56704
+
56705
+ // src/session/authorization.ts
56706
+ function isAuthorizedForSession(check) {
56707
+ const { username, platform, sessionAllowedUsers } = check;
56708
+ if (!username || username === "unknown") {
56709
+ return false;
56710
+ }
56711
+ if (platform.isUserAllowed(username)) {
56712
+ return true;
56564
56713
  }
56714
+ return sessionAllowedUsers?.has(username) ?? false;
56565
56715
  }
56566
56716
 
56567
56717
  // src/commands/registry.ts
@@ -57348,7 +57498,7 @@ async function handleDynamicSlashCommand(command, args, ctx) {
57348
57498
  }
57349
57499
  // src/commands/system-prompt-generator.ts
57350
57500
  init_logger();
57351
- var log14 = createLogger("system-prompt");
57501
+ var log15 = createLogger("system-prompt");
57352
57502
  function formatUserCommand(cmd) {
57353
57503
  const cmdStr = cmd.args ? `\`!${cmd.command} ${cmd.args}\`` : `\`!${cmd.command}\``;
57354
57504
  const description = cmd.description;
@@ -57387,7 +57537,7 @@ async function resolveCollaborators(platform, platformId, ownerUsername, allowed
57387
57537
  continue;
57388
57538
  const email = githubEmailsStore.get(platformId, username);
57389
57539
  if (!email) {
57390
- log14.debug(`Collaborator @${username} has no registered GitHub noreply email — skipping`);
57540
+ log15.debug(`Collaborator @${username} has no registered GitHub noreply email — skipping`);
57391
57541
  continue;
57392
57542
  }
57393
57543
  let name = username;
@@ -57396,7 +57546,7 @@ async function resolveCollaborators(platform, platformId, ownerUsername, allowed
57396
57546
  if (user)
57397
57547
  name = user.displayName || user.username;
57398
57548
  } catch (err) {
57399
- log14.debug(`Display name lookup failed for @${username}: ${err.message}`);
57549
+ log15.debug(`Display name lookup failed for @${username}: ${err.message}`);
57400
57550
  }
57401
57551
  resolved.push({ username, name, email });
57402
57552
  }
@@ -57502,7 +57652,7 @@ import { existsSync as existsSync12 } from "fs";
57502
57652
  // src/utils/keep-alive.ts
57503
57653
  init_logger();
57504
57654
  import { spawn as spawn2 } from "child_process";
57505
- var log15 = createLogger("keepalive");
57655
+ var log16 = createLogger("keepalive");
57506
57656
 
57507
57657
  class KeepAliveManager {
57508
57658
  activeSessionCount = 0;
@@ -57517,7 +57667,7 @@ class KeepAliveManager {
57517
57667
  if (!enabled && this.keepAliveProcess) {
57518
57668
  this.stopKeepAlive();
57519
57669
  }
57520
- log15.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
57670
+ log16.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
57521
57671
  }
57522
57672
  isEnabled() {
57523
57673
  return this.enabled;
@@ -57527,7 +57677,7 @@ class KeepAliveManager {
57527
57677
  }
57528
57678
  sessionStarted() {
57529
57679
  this.activeSessionCount++;
57530
- log15.debug(`Session started (${this.activeSessionCount} active)`);
57680
+ log16.debug(`Session started (${this.activeSessionCount} active)`);
57531
57681
  if (this.activeSessionCount === 1) {
57532
57682
  this.startKeepAlive();
57533
57683
  }
@@ -57536,7 +57686,7 @@ class KeepAliveManager {
57536
57686
  if (this.activeSessionCount > 0) {
57537
57687
  this.activeSessionCount--;
57538
57688
  }
57539
- log15.debug(`Session ended (${this.activeSessionCount} active)`);
57689
+ log16.debug(`Session ended (${this.activeSessionCount} active)`);
57540
57690
  if (this.activeSessionCount === 0) {
57541
57691
  this.stopKeepAlive();
57542
57692
  }
@@ -57550,11 +57700,11 @@ class KeepAliveManager {
57550
57700
  }
57551
57701
  startKeepAlive() {
57552
57702
  if (!this.enabled) {
57553
- log15.debug("Keep-alive disabled, skipping");
57703
+ log16.debug("Keep-alive disabled, skipping");
57554
57704
  return;
57555
57705
  }
57556
57706
  if (this.keepAliveProcess) {
57557
- log15.debug("Keep-alive already running");
57707
+ log16.debug("Keep-alive already running");
57558
57708
  return;
57559
57709
  }
57560
57710
  switch (this.platform) {
@@ -57568,12 +57718,12 @@ class KeepAliveManager {
57568
57718
  this.startWindowsKeepAlive();
57569
57719
  break;
57570
57720
  default:
57571
- log15.warn(`Keep-alive not supported on ${this.platform}`);
57721
+ log16.warn(`Keep-alive not supported on ${this.platform}`);
57572
57722
  }
57573
57723
  }
57574
57724
  stopKeepAlive() {
57575
57725
  if (this.keepAliveProcess) {
57576
- log15.debug("Stopping keep-alive");
57726
+ log16.debug("Stopping keep-alive");
57577
57727
  this.keepAliveProcess.kill();
57578
57728
  this.keepAliveProcess = null;
57579
57729
  }
@@ -57585,18 +57735,18 @@ class KeepAliveManager {
57585
57735
  detached: false
57586
57736
  });
57587
57737
  this.keepAliveProcess.on("error", (err) => {
57588
- log15.error(`Failed to start caffeinate: ${err.message}`);
57738
+ log16.error(`Failed to start caffeinate: ${err.message}`);
57589
57739
  this.keepAliveProcess = null;
57590
57740
  });
57591
57741
  this.keepAliveProcess.on("exit", (code) => {
57592
57742
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
57593
- log15.debug(`caffeinate exited with code ${code}`);
57743
+ log16.debug(`caffeinate exited with code ${code}`);
57594
57744
  }
57595
57745
  this.keepAliveProcess = null;
57596
57746
  });
57597
- log15.info("Sleep prevention active (caffeinate)");
57747
+ log16.info("Sleep prevention active (caffeinate)");
57598
57748
  } catch (err) {
57599
- log15.error(`Failed to start caffeinate: ${err}`);
57749
+ log16.error(`Failed to start caffeinate: ${err}`);
57600
57750
  }
57601
57751
  }
57602
57752
  startLinuxKeepAlive() {
@@ -57612,19 +57762,19 @@ class KeepAliveManager {
57612
57762
  detached: false
57613
57763
  });
57614
57764
  this.keepAliveProcess.on("error", (err) => {
57615
- log15.debug(`systemd-inhibit not available: ${err.message}`);
57765
+ log16.debug(`systemd-inhibit not available: ${err.message}`);
57616
57766
  this.keepAliveProcess = null;
57617
57767
  this.startLinuxKeepAliveFallback();
57618
57768
  });
57619
57769
  this.keepAliveProcess.on("exit", (code) => {
57620
57770
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
57621
- log15.debug(`systemd-inhibit exited with code ${code}`);
57771
+ log16.debug(`systemd-inhibit exited with code ${code}`);
57622
57772
  }
57623
57773
  this.keepAliveProcess = null;
57624
57774
  });
57625
- log15.info("Sleep prevention active (systemd-inhibit)");
57775
+ log16.info("Sleep prevention active (systemd-inhibit)");
57626
57776
  } catch (err) {
57627
- log15.debug(`Failed to start systemd-inhibit: ${err}`);
57777
+ log16.debug(`Failed to start systemd-inhibit: ${err}`);
57628
57778
  this.startLinuxKeepAliveFallback();
57629
57779
  }
57630
57780
  }
@@ -57638,15 +57788,15 @@ class KeepAliveManager {
57638
57788
  detached: false
57639
57789
  });
57640
57790
  this.keepAliveProcess.on("error", (err) => {
57641
- log15.warn(`Linux keep-alive fallback not available: ${err.message}`);
57791
+ log16.warn(`Linux keep-alive fallback not available: ${err.message}`);
57642
57792
  this.keepAliveProcess = null;
57643
57793
  });
57644
57794
  this.keepAliveProcess.on("exit", () => {
57645
57795
  this.keepAliveProcess = null;
57646
57796
  });
57647
- log15.info("Sleep prevention active (xdg-screensaver)");
57797
+ log16.info("Sleep prevention active (xdg-screensaver)");
57648
57798
  } catch (err) {
57649
- log15.warn(`Linux keep-alive not available: ${err}`);
57799
+ log16.warn(`Linux keep-alive not available: ${err}`);
57650
57800
  }
57651
57801
  }
57652
57802
  startWindowsKeepAlive() {
@@ -57671,18 +57821,18 @@ class KeepAliveManager {
57671
57821
  windowsHide: true
57672
57822
  });
57673
57823
  this.keepAliveProcess.on("error", (err) => {
57674
- log15.warn(`Windows keep-alive not available: ${err.message}`);
57824
+ log16.warn(`Windows keep-alive not available: ${err.message}`);
57675
57825
  this.keepAliveProcess = null;
57676
57826
  });
57677
57827
  this.keepAliveProcess.on("exit", (code) => {
57678
57828
  if (code !== null && code !== 0 && this.activeSessionCount > 0) {
57679
- log15.debug(`PowerShell keep-alive exited with code ${code}`);
57829
+ log16.debug(`PowerShell keep-alive exited with code ${code}`);
57680
57830
  }
57681
57831
  this.keepAliveProcess = null;
57682
57832
  });
57683
- log15.info("Sleep prevention active (SetThreadExecutionState)");
57833
+ log16.info("Sleep prevention active (SetThreadExecutionState)");
57684
57834
  } catch (err) {
57685
- log15.warn(`Windows keep-alive not available: ${err}`);
57835
+ log16.warn(`Windows keep-alive not available: ${err}`);
57686
57836
  }
57687
57837
  }
57688
57838
  }
@@ -57690,7 +57840,7 @@ var keepAlive = new KeepAliveManager;
57690
57840
 
57691
57841
  // src/utils/error-handler/index.ts
57692
57842
  init_logger();
57693
- var log16 = createLogger("error");
57843
+ var log17 = createLogger("error");
57694
57844
 
57695
57845
  class SessionError extends Error {
57696
57846
  sessionId;
@@ -57716,19 +57866,19 @@ async function handleError(error, context, severity = "recoverable") {
57716
57866
  const sessionPart = sessionId ? ` (${formatShortId(sessionId)})` : "";
57717
57867
  const logMessage = `${context.action}${sessionPart}: ${message}`;
57718
57868
  if (severity === "recoverable") {
57719
- log16.warn(logMessage);
57869
+ log17.warn(logMessage);
57720
57870
  } else {
57721
- log16.error(logMessage, error instanceof Error ? error : undefined);
57871
+ log17.error(logMessage, error instanceof Error ? error : undefined);
57722
57872
  }
57723
57873
  if (context.details) {
57724
- log16.debugJson("Error details", context.details);
57874
+ log17.debugJson("Error details", context.details);
57725
57875
  }
57726
57876
  if (context.notifyUser && context.session) {
57727
57877
  try {
57728
57878
  const fmt = context.session.platform.getFormatter();
57729
57879
  await context.session.platform.createPost(`⚠️ ${fmt.formatBold("Error")}: ${context.action} failed - ${message}`, context.session.threadId);
57730
57880
  } catch (notifyError) {
57731
- log16.warn(`Could not notify user: ${notifyError}`);
57881
+ log17.warn(`Could not notify user: ${notifyError}`);
57732
57882
  }
57733
57883
  }
57734
57884
  if (severity === "session-fatal" || severity === "system-fatal") {
@@ -57755,7 +57905,7 @@ async function logAndNotify(error, context) {
57755
57905
  }
57756
57906
  function logSilentError(context, error) {
57757
57907
  const message = error instanceof Error ? error.message : String(error);
57758
- log16.debug(`[${context}] Silently caught: ${message}`);
57908
+ log17.debug(`[${context}] Silently caught: ${message}`);
57759
57909
  }
57760
57910
 
57761
57911
  // src/session/lifecycle.ts
@@ -57775,8 +57925,8 @@ function createSessionLog(baseLog) {
57775
57925
  init_logger();
57776
57926
  init_emoji();
57777
57927
  init_worktree();
57778
- var log17 = createLogger("helpers");
57779
- var sessionLog = createSessionLog(log17);
57928
+ var log18 = createLogger("helpers");
57929
+ var sessionLog = createSessionLog(log18);
57780
57930
  var POST_TYPES = {
57781
57931
  info: "",
57782
57932
  success: "✅",
@@ -57862,7 +58012,7 @@ init_logger();
57862
58012
  import { lstat, mkdir as mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
57863
58013
  import { tmpdir as tmpdir2 } from "os";
57864
58014
  import { join as join9 } from "path";
57865
- var log18 = createLogger("streaming");
58015
+ var log19 = createLogger("streaming");
57866
58016
  var UPLOAD_ROOT_DIR = "claude-threads-uploads";
57867
58017
  function safeIdSegment(id) {
57868
58018
  return id.replace(/[^A-Za-z0-9._-]/g, "_");
@@ -57877,7 +58027,7 @@ async function cleanupSessionUploads(platformId, threadId) {
57877
58027
  try {
57878
58028
  await rm2(dir, { recursive: true, force: true });
57879
58029
  } catch (err) {
57880
- log18.debug(`Upload cleanup for ${platformId}:${threadId} failed (ignored): ${err}`);
58030
+ log19.debug(`Upload cleanup for ${platformId}:${threadId} failed (ignored): ${err}`);
57881
58031
  }
57882
58032
  }
57883
58033
  function sanitizeForPrompt(value) {
@@ -57898,7 +58048,7 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
57898
58048
  for (const file of files) {
57899
58049
  skipped.push({ name: file.name, reason: "Refusing to write under symlinked upload directory" });
57900
58050
  }
57901
- log18.error(`Upload dir is a symlink, refusing all writes: ${uploadDir}`);
58051
+ log19.error(`Upload dir is a symlink, refusing all writes: ${uploadDir}`);
57902
58052
  return { saved, skipped };
57903
58053
  }
57904
58054
  const messageDir = await mkdtemp(join9(uploadDir, `${Date.now().toString(36)}-`));
@@ -57916,11 +58066,11 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
57916
58066
  size: buffer.length
57917
58067
  });
57918
58068
  if (debug) {
57919
- log18.debug(`Saved ${file.name} → ${absolutePath} (${formatBytes(buffer.length)})`);
58069
+ log19.debug(`Saved ${file.name} → ${absolutePath} (${formatBytes(buffer.length)})`);
57920
58070
  }
57921
58071
  } catch (err) {
57922
58072
  const message = err instanceof Error ? err.message : String(err);
57923
- log18.error(`Failed to save uploaded file ${file.name}: ${message}`);
58073
+ log19.error(`Failed to save uploaded file ${file.name}: ${message}`);
57924
58074
  skipped.push({
57925
58075
  name: file.name,
57926
58076
  reason: `Download failed: ${message}`
@@ -65719,7 +65869,7 @@ class BugReportExecutor extends BaseExecutor {
65719
65869
  // src/operations/executors/worktree-prompt.ts
65720
65870
  init_emoji();
65721
65871
  init_logger();
65722
- var log19 = createLogger("wt-prompt");
65872
+ var log20 = createLogger("wt-prompt");
65723
65873
  // src/operations/message-manager.ts
65724
65874
  init_logger();
65725
65875
 
@@ -65751,7 +65901,7 @@ function createMessageManagerEvents() {
65751
65901
  }
65752
65902
 
65753
65903
  // src/operations/message-manager.ts
65754
- var log20 = createLogger("msg-mgr");
65904
+ var log21 = createLogger("msg-mgr");
65755
65905
 
65756
65906
  class MessageManager {
65757
65907
  platform;
@@ -65841,7 +65991,7 @@ class MessageManager {
65841
65991
  });
65842
65992
  }
65843
65993
  async handleEvent(event) {
65844
- const logger = log20.forSession(this.sessionId);
65994
+ const logger = log21.forSession(this.sessionId);
65845
65995
  const transformCtx = {
65846
65996
  sessionId: this.sessionId,
65847
65997
  formatter: this.platform.getFormatter(),
@@ -65891,7 +66041,7 @@ class MessageManager {
65891
66041
  }
65892
66042
  }
65893
66043
  async executeOperation(op) {
65894
- const logger = log20.forSession(this.sessionId);
66044
+ const logger = log21.forSession(this.sessionId);
65895
66045
  const ctx = this.getExecutorContext();
65896
66046
  try {
65897
66047
  if (isContentOp(op)) {
@@ -65959,7 +66109,7 @@ class MessageManager {
65959
66109
  threadId: this.threadId,
65960
66110
  platform: this.platform,
65961
66111
  formatter: this.platform.getFormatter(),
65962
- logger: log20.forSession(this.sessionId),
66112
+ logger: log21.forSession(this.sessionId),
65963
66113
  postTracker: this.postTracker,
65964
66114
  contentBreaker: this.contentBreaker,
65965
66115
  threadLogger: this.session.threadLogger,
@@ -66170,13 +66320,13 @@ class MessageManager {
66170
66320
  return this.systemExecutor.postSuccess(message, this.getExecutorContext());
66171
66321
  }
66172
66322
  async prepareForUserMessage() {
66173
- const logger = log20.forSession(this.sessionId);
66323
+ const logger = log21.forSession(this.sessionId);
66174
66324
  logger.debug("Preparing for new user message");
66175
66325
  await this.closeCurrentPost();
66176
66326
  await this.bumpTaskList();
66177
66327
  }
66178
66328
  async handleUserMessage(message, files, username, displayName) {
66179
- const logger = log20.forSession(this.sessionId);
66329
+ const logger = log21.forSession(this.sessionId);
66180
66330
  if (!this.session.claude.isRunning()) {
66181
66331
  logger.debug("Claude not running, ignoring user message");
66182
66332
  return false;
@@ -66213,7 +66363,7 @@ class MessageManager {
66213
66363
  ];
66214
66364
  }
66215
66365
  async handleReaction(postId, emoji, user, action) {
66216
- const logger = log20.forSession(this.sessionId);
66366
+ const logger = log21.forSession(this.sessionId);
66217
66367
  const ctx = this.getExecutorContext();
66218
66368
  logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
66219
66369
  for (const { name, executor } of this.reactionDispatchList()) {
@@ -66248,7 +66398,7 @@ class MessageManager {
66248
66398
  }
66249
66399
  // src/operations/sticky-message/handler.ts
66250
66400
  init_logger();
66251
- var log21 = createLogger("sticky");
66401
+ var log22 = createLogger("sticky");
66252
66402
  var botStartedAt = new Date;
66253
66403
  function getPendingPrompts(session) {
66254
66404
  const prompts2 = [];
@@ -66323,21 +66473,21 @@ function initialize(store) {
66323
66473
  stickyPostIds.set(platformId, postId);
66324
66474
  }
66325
66475
  if (persistedIds.size > 0) {
66326
- log21.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
66476
+ log22.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
66327
66477
  }
66328
66478
  }
66329
66479
  function setPlatformPaused(platformId, paused) {
66330
66480
  if (paused) {
66331
66481
  pausedPlatforms.set(platformId, true);
66332
- log21.debug(`Platform ${platformId} marked as paused`);
66482
+ log22.debug(`Platform ${platformId} marked as paused`);
66333
66483
  } else {
66334
66484
  pausedPlatforms.delete(platformId);
66335
- log21.debug(`Platform ${platformId} marked as active`);
66485
+ log22.debug(`Platform ${platformId} marked as active`);
66336
66486
  }
66337
66487
  }
66338
66488
  function setShuttingDown(shuttingDown) {
66339
66489
  isShuttingDown = shuttingDown;
66340
- log21.debug(`Bot shutdown state: ${shuttingDown}`);
66490
+ log22.debug(`Bot shutdown state: ${shuttingDown}`);
66341
66491
  }
66342
66492
  function getTaskContent(session) {
66343
66493
  const taskState = session.messageManager?.getTaskListState();
@@ -66438,7 +66588,13 @@ async function buildStatusBar(sessionCount, config, formatter, platformId) {
66438
66588
  const total = config.accountPoolStatus.length;
66439
66589
  const cooling = config.accountPoolStatus.filter((a) => a.coolingUntil !== null).length;
66440
66590
  const available = total - cooling;
66441
- const label = cooling > 0 ? `\uD83D\uDD11 ${available}/${total} accounts (${cooling} cooling)` : `\uD83D\uDD11 ${total} account${total === 1 ? "" : "s"}`;
66591
+ let label = cooling > 0 ? `\uD83D\uDD11 ${available}/${total} accounts (${cooling} cooling)` : `\uD83D\uDD11 ${total} account${total === 1 ? "" : "s"}`;
66592
+ const pcts = config.accountPoolStatus.map((a) => a.usagePercent).filter((p) => p !== null);
66593
+ if (pcts.length > 0) {
66594
+ const min = Math.min(...pcts);
66595
+ const max = Math.max(...pcts);
66596
+ label += min === max ? ` · ${max}% used` : ` · ${min}–${max}% used`;
66597
+ }
66442
66598
  items.push(formatter.formatCode(label));
66443
66599
  }
66444
66600
  items.push(formatter.formatCode(permissionModeDisplay(config.permissionMode).chip));
@@ -66655,12 +66811,12 @@ async function validateLastMessageIds(platform, sessions) {
66655
66811
  try {
66656
66812
  const post2 = await platform.getPost(lastMessageId);
66657
66813
  if (!post2) {
66658
- log21.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
66814
+ log22.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
66659
66815
  session.lastMessageId = undefined;
66660
66816
  session.lastMessageTs = undefined;
66661
66817
  }
66662
66818
  } catch (err) {
66663
- log21.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
66819
+ log22.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
66664
66820
  session.lastMessageId = undefined;
66665
66821
  session.lastMessageTs = undefined;
66666
66822
  }
@@ -66677,7 +66833,7 @@ async function updateStickyMessageImpl(platform, sessions, config) {
66677
66833
  hiddenCleanupDone.add(platform.platformId);
66678
66834
  const existing = stickyPostIds.get(platform.platformId);
66679
66835
  if (existing) {
66680
- log21.info(`sticky[${platform.platformId}] hidden mode: removing leftover ${formatShortId(existing)}`);
66836
+ log22.info(`sticky[${platform.platformId}] hidden mode: removing leftover ${formatShortId(existing)}`);
66681
66837
  try {
66682
66838
  await platform.unpinPost(existing);
66683
66839
  } catch {}
@@ -66697,63 +66853,63 @@ async function updateStickyMessageImpl(platform, sessions, config) {
66697
66853
  return;
66698
66854
  }
66699
66855
  const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
66700
- log21.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
66856
+ log22.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
66701
66857
  for (const s of platformSessions) {
66702
- log21.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
66858
+ log22.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
66703
66859
  }
66704
66860
  await validateLastMessageIds(platform, platformSessions);
66705
66861
  const formatter = platform.getFormatter();
66706
66862
  const content = await buildStickyMessage(sessions, platform.platformId, config, formatter, (threadId) => platform.getThreadLink(threadId));
66707
66863
  const existingPostId = stickyPostIds.get(platform.platformId);
66708
66864
  const shouldBump = needsBump.get(platform.platformId) ?? false;
66709
- log21.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
66865
+ log22.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
66710
66866
  try {
66711
66867
  if (existingPostId && !shouldBump) {
66712
- log21.debug(`Updating existing post in place...`);
66868
+ log22.debug(`Updating existing post in place...`);
66713
66869
  try {
66714
66870
  await platform.updatePost(existingPostId, content);
66715
66871
  try {
66716
66872
  await platform.pinPost(existingPostId);
66717
- log21.debug(`Re-pinned post`);
66873
+ log22.debug(`Re-pinned post`);
66718
66874
  } catch (pinErr) {
66719
- log21.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
66875
+ log22.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
66720
66876
  }
66721
- log21.debug(`Updated successfully`);
66877
+ log22.debug(`Updated successfully`);
66722
66878
  return;
66723
66879
  } catch (err) {
66724
- log21.debug(`Update failed, will create new: ${err}`);
66880
+ log22.debug(`Update failed, will create new: ${err}`);
66725
66881
  }
66726
66882
  }
66727
66883
  needsBump.set(platform.platformId, false);
66728
66884
  if (existingPostId) {
66729
- log21.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
66885
+ log22.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
66730
66886
  try {
66731
66887
  await platform.unpinPost(existingPostId);
66732
- log21.debug(`Unpinned successfully`);
66888
+ log22.debug(`Unpinned successfully`);
66733
66889
  } catch (err) {
66734
- log21.debug(`Unpin failed (probably already unpinned): ${err}`);
66890
+ log22.debug(`Unpin failed (probably already unpinned): ${err}`);
66735
66891
  }
66736
66892
  try {
66737
66893
  await platform.deletePost(existingPostId);
66738
- log21.debug(`Deleted successfully`);
66894
+ log22.debug(`Deleted successfully`);
66739
66895
  } catch (err) {
66740
- log21.debug(`Delete failed (probably already deleted): ${err}`);
66896
+ log22.debug(`Delete failed (probably already deleted): ${err}`);
66741
66897
  }
66742
66898
  stickyPostIds.delete(platform.platformId);
66743
66899
  }
66744
- log21.debug(`Creating new post...`);
66900
+ log22.debug(`Creating new post...`);
66745
66901
  const post2 = await platform.createPost(content);
66746
66902
  stickyPostIds.set(platform.platformId, post2.id);
66747
66903
  try {
66748
66904
  await platform.pinPost(post2.id);
66749
- log21.debug(`Pinned post successfully`);
66905
+ log22.debug(`Pinned post successfully`);
66750
66906
  } catch (err) {
66751
- log21.debug(`Failed to pin post: ${err}`);
66907
+ log22.debug(`Failed to pin post: ${err}`);
66752
66908
  }
66753
66909
  if (sessionStore) {
66754
66910
  sessionStore.saveStickyPostId(platform.platformId, post2.id);
66755
66911
  }
66756
- log21.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
66912
+ log22.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
66757
66913
  const excludePostIds = new Set;
66758
66914
  if (sessionStore) {
66759
66915
  for (const session of sessionStore.load().values()) {
@@ -66769,10 +66925,10 @@ async function updateStickyMessageImpl(platform, sessions, config) {
66769
66925
  }
66770
66926
  const botUser = await platform.getBotUser();
66771
66927
  cleanupOldStickyMessages(platform, botUser.id, false, excludePostIds).catch((err) => {
66772
- log21.debug(`Background cleanup failed: ${err}`);
66928
+ log22.debug(`Background cleanup failed: ${err}`);
66773
66929
  });
66774
66930
  } catch (err) {
66775
- log21.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
66931
+ log22.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
66776
66932
  }
66777
66933
  }
66778
66934
  async function updateAllStickyMessages(platforms, sessions, config, overheadByPlatform) {
@@ -66800,7 +66956,7 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
66800
66956
  if (!forceRun) {
66801
66957
  const lastRun = lastCleanupTime.get(platformId) || 0;
66802
66958
  if (now - lastRun < CLEANUP_THROTTLE_MS) {
66803
- log21.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
66959
+ log22.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
66804
66960
  return;
66805
66961
  }
66806
66962
  }
@@ -66810,37 +66966,37 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
66810
66966
  const pinnedPostIds = await platform.getPinnedPosts();
66811
66967
  const recentPinnedIds = pinnedPostIds.filter((id) => id !== currentStickyId && !excludePostIds?.has(id) && isRecentPost(id));
66812
66968
  if (recentPinnedIds.length === 0) {
66813
- log21.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
66969
+ log22.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
66814
66970
  return;
66815
66971
  }
66816
- log21.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
66972
+ log22.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
66817
66973
  for (const postId of recentPinnedIds) {
66818
66974
  try {
66819
66975
  const post2 = await platform.getPost(postId);
66820
66976
  if (!post2)
66821
66977
  continue;
66822
66978
  if (post2.userId === botUserId) {
66823
- log21.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
66979
+ log22.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
66824
66980
  try {
66825
66981
  await platform.unpinPost(postId);
66826
66982
  await platform.deletePost(postId);
66827
- log21.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
66983
+ log22.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
66828
66984
  } catch (err) {
66829
- log21.debug(`Failed to cleanup ${postId}: ${err}`);
66985
+ log22.debug(`Failed to cleanup ${postId}: ${err}`);
66830
66986
  }
66831
66987
  }
66832
66988
  } catch (err) {
66833
- log21.debug(`Could not check post ${postId}: ${err}`);
66989
+ log22.debug(`Could not check post ${postId}: ${err}`);
66834
66990
  }
66835
66991
  }
66836
66992
  } catch (err) {
66837
- log21.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
66993
+ log22.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
66838
66994
  }
66839
66995
  }
66840
66996
  // src/claude/quick-query.ts
66841
66997
  init_spawn();
66842
66998
  init_logger();
66843
- var log22 = createLogger("query");
66999
+ var log23 = createLogger("query");
66844
67000
  async function quickQuery(options) {
66845
67001
  const {
66846
67002
  prompt,
@@ -66856,7 +67012,7 @@ async function quickQuery(options) {
66856
67012
  args.push("--system-prompt", systemPrompt);
66857
67013
  }
66858
67014
  args.push(prompt);
66859
- log22.debug(`Quick query: model=${model}, timeout=${timeout2}ms, prompt="${prompt.substring(0, 50)}..."`);
67015
+ log23.debug(`Quick query: model=${model}, timeout=${timeout2}ms, prompt="${prompt.substring(0, 50)}..."`);
66860
67016
  return new Promise((resolve5) => {
66861
67017
  let stdout = "";
66862
67018
  let stderr = "";
@@ -66870,7 +67026,7 @@ async function quickQuery(options) {
66870
67026
  if (!resolved) {
66871
67027
  resolved = true;
66872
67028
  proc.kill("SIGTERM");
66873
- log22.debug(`Quick query timed out after ${timeout2}ms`);
67029
+ log23.debug(`Quick query timed out after ${timeout2}ms`);
66874
67030
  resolve5({
66875
67031
  success: false,
66876
67032
  error: "timeout",
@@ -66888,7 +67044,7 @@ async function quickQuery(options) {
66888
67044
  if (!resolved) {
66889
67045
  resolved = true;
66890
67046
  clearTimeout(timeoutId);
66891
- log22.debug(`Quick query error: ${err.message}`);
67047
+ log23.debug(`Quick query error: ${err.message}`);
66892
67048
  resolve5({
66893
67049
  success: false,
66894
67050
  error: err.message,
@@ -66902,14 +67058,14 @@ async function quickQuery(options) {
66902
67058
  clearTimeout(timeoutId);
66903
67059
  const durationMs = Date.now() - startTime;
66904
67060
  if (code === 0 && stdout.trim()) {
66905
- log22.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
67061
+ log23.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
66906
67062
  resolve5({
66907
67063
  success: true,
66908
67064
  response: stdout.trim(),
66909
67065
  durationMs
66910
67066
  });
66911
67067
  } else {
66912
- log22.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
67068
+ log23.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
66913
67069
  resolve5({
66914
67070
  success: false,
66915
67071
  error: stderr || `exit code ${code}`,
@@ -66928,7 +67084,7 @@ init_logger();
66928
67084
  import { exec as exec3 } from "child_process";
66929
67085
  import { promisify as promisify3 } from "util";
66930
67086
  var execAsync2 = promisify3(exec3);
66931
- var log23 = createLogger("branch");
67087
+ var log24 = createLogger("branch");
66932
67088
  var SUGGESTION_TIMEOUT = 15000;
66933
67089
  var MAX_SUGGESTIONS = 3;
66934
67090
  async function getCurrentBranch3(workingDir) {
@@ -66977,7 +67133,7 @@ function parseBranchSuggestions(response) {
66977
67133
  return lines.slice(0, MAX_SUGGESTIONS);
66978
67134
  }
66979
67135
  async function suggestBranchNames(workingDir, userMessage) {
66980
- log23.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
67136
+ log24.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
66981
67137
  try {
66982
67138
  const [currentBranch, recentCommits] = await Promise.all([
66983
67139
  getCurrentBranch3(workingDir),
@@ -66991,14 +67147,14 @@ async function suggestBranchNames(workingDir, userMessage) {
66991
67147
  workingDir
66992
67148
  });
66993
67149
  if (!result.success || !result.response) {
66994
- log23.debug(`Branch suggestion failed: ${result.error || "no response"}`);
67150
+ log24.debug(`Branch suggestion failed: ${result.error || "no response"}`);
66995
67151
  return [];
66996
67152
  }
66997
67153
  const suggestions = parseBranchSuggestions(result.response);
66998
- log23.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
67154
+ log24.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
66999
67155
  return suggestions;
67000
67156
  } catch (err) {
67001
- log23.debug(`Branch suggestion error: ${err}`);
67157
+ log24.debug(`Branch suggestion error: ${err}`);
67002
67158
  return [];
67003
67159
  }
67004
67160
  }
@@ -67007,8 +67163,8 @@ async function suggestBranchNames(workingDir, userMessage) {
67007
67163
  init_worktree();
67008
67164
  import { randomUUID as randomUUID2 } from "crypto";
67009
67165
  init_logger();
67010
- var log24 = createLogger("worktree");
67011
- var sessionLog2 = createSessionLog(log24);
67166
+ var log25 = createLogger("worktree");
67167
+ var sessionLog2 = createSessionLog(log25);
67012
67168
  function parseWorktreeError(error) {
67013
67169
  const message = error instanceof Error ? error.message : String(error);
67014
67170
  const lowerMessage = message.toLowerCase();
@@ -67574,8 +67730,8 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
67574
67730
  }
67575
67731
  // src/operations/events/handler.ts
67576
67732
  init_logger();
67577
- var log25 = createLogger("events");
67578
- var sessionLog3 = createSessionLog(log25);
67733
+ var log26 = createLogger("events");
67734
+ var sessionLog3 = createSessionLog(log26);
67579
67735
  function detectAndExecuteClaudeCommands(text, session, ctx) {
67580
67736
  const parsed = parseClaudeCommand(text);
67581
67737
  if (parsed && isClaudeAllowedCommand(parsed.command)) {
@@ -67823,8 +67979,8 @@ function createSessionContext(config, state, ops) {
67823
67979
  // src/operations/context-prompt/handler.ts
67824
67980
  init_emoji();
67825
67981
  init_logger();
67826
- var log26 = createLogger("context");
67827
- var sessionLog4 = createSessionLog(log26);
67982
+ var log27 = createLogger("context");
67983
+ var sessionLog4 = createSessionLog(log27);
67828
67984
  var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
67829
67985
  var CONTEXT_OPTIONS = [3, 5, 10];
67830
67986
  var contextPromptTimeouts = new Map;
@@ -68046,7 +68202,7 @@ async function offerContextPrompt(session, queuedPrompt, queuedFiles, ctx, exclu
68046
68202
  }
68047
68203
  // src/operations/suggestions/tag.ts
68048
68204
  init_logger();
68049
- var log27 = createLogger("tags");
68205
+ var log28 = createLogger("tags");
68050
68206
  var SUGGESTION_TIMEOUT2 = 15000;
68051
68207
  var MAX_TAGS = 3;
68052
68208
  var VALID_TAGS = [
@@ -68078,7 +68234,7 @@ function parseTags(response) {
68078
68234
  return [...new Set(tags)].slice(0, MAX_TAGS);
68079
68235
  }
68080
68236
  async function suggestSessionTags(userMessage) {
68081
- log27.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
68237
+ log28.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
68082
68238
  try {
68083
68239
  const result = await quickQuery({
68084
68240
  prompt: buildTagPrompt(userMessage),
@@ -68086,20 +68242,20 @@ async function suggestSessionTags(userMessage) {
68086
68242
  timeout: SUGGESTION_TIMEOUT2
68087
68243
  });
68088
68244
  if (!result.success || !result.response) {
68089
- log27.debug(`Tag suggestion failed: ${result.error || "no response"}`);
68245
+ log28.debug(`Tag suggestion failed: ${result.error || "no response"}`);
68090
68246
  return [];
68091
68247
  }
68092
68248
  const tags = parseTags(result.response);
68093
- log27.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
68249
+ log28.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
68094
68250
  return tags;
68095
68251
  } catch (err) {
68096
- log27.debug(`Tag suggestion error: ${err}`);
68252
+ log28.debug(`Tag suggestion error: ${err}`);
68097
68253
  return [];
68098
68254
  }
68099
68255
  }
68100
68256
  // src/operations/suggestions/title.ts
68101
68257
  init_logger();
68102
- var log28 = createLogger("title");
68258
+ var log29 = createLogger("title");
68103
68259
  var SUGGESTION_TIMEOUT3 = 15000;
68104
68260
  var MIN_TITLE_LENGTH = 3;
68105
68261
  var MAX_TITLE_LENGTH = 50;
@@ -68163,32 +68319,32 @@ function parseMetadata(response) {
68163
68319
  const titleMatch = response.match(/TITLE:\s*(.+)/i);
68164
68320
  const descMatch = response.match(/DESC:\s*(.+)/i);
68165
68321
  if (!titleMatch || !descMatch) {
68166
- log28.debug("Failed to parse title/description from response");
68322
+ log29.debug("Failed to parse title/description from response");
68167
68323
  return null;
68168
68324
  }
68169
68325
  let title = titleMatch[1].trim();
68170
68326
  let description = descMatch[1].trim();
68171
68327
  if (title.length < MIN_TITLE_LENGTH) {
68172
- log28.debug(`Title too short: ${title.length} chars`);
68328
+ log29.debug(`Title too short: ${title.length} chars`);
68173
68329
  return null;
68174
68330
  }
68175
68331
  if (title.length > MAX_TITLE_LENGTH) {
68176
- log28.debug(`Title too long (${title.length} chars), truncating`);
68332
+ log29.debug(`Title too long (${title.length} chars), truncating`);
68177
68333
  title = truncateAtWord(title, MAX_TITLE_LENGTH);
68178
68334
  }
68179
68335
  if (description.length < MIN_DESC_LENGTH) {
68180
- log28.debug(`Description too short: ${description.length} chars`);
68336
+ log29.debug(`Description too short: ${description.length} chars`);
68181
68337
  return null;
68182
68338
  }
68183
68339
  if (description.length > MAX_DESC_LENGTH) {
68184
- log28.debug(`Description too long (${description.length} chars), truncating`);
68340
+ log29.debug(`Description too long (${description.length} chars), truncating`);
68185
68341
  description = truncateAtWord(description, MAX_DESC_LENGTH);
68186
68342
  }
68187
68343
  return { title, description };
68188
68344
  }
68189
68345
  async function suggestSessionMetadata(context) {
68190
68346
  const logContext = typeof context === "string" ? context.substring(0, 50) : context.originalTask.substring(0, 50);
68191
- log28.debug(`Suggesting title for: "${logContext}..."`);
68347
+ log29.debug(`Suggesting title for: "${logContext}..."`);
68192
68348
  try {
68193
68349
  const result = await quickQuery({
68194
68350
  prompt: buildTitlePrompt(context),
@@ -68196,22 +68352,22 @@ async function suggestSessionMetadata(context) {
68196
68352
  timeout: SUGGESTION_TIMEOUT3
68197
68353
  });
68198
68354
  if (!result.success || !result.response) {
68199
- log28.debug(`Title suggestion failed: ${result.error || "no response"}`);
68355
+ log29.debug(`Title suggestion failed: ${result.error || "no response"}`);
68200
68356
  return null;
68201
68357
  }
68202
68358
  const metadata = parseMetadata(result.response);
68203
68359
  if (metadata) {
68204
- log28.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
68360
+ log29.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
68205
68361
  }
68206
68362
  return metadata;
68207
68363
  } catch (err) {
68208
- log28.debug(`Title suggestion error: ${err}`);
68364
+ log29.debug(`Title suggestion error: ${err}`);
68209
68365
  return null;
68210
68366
  }
68211
68367
  }
68212
68368
  // src/operations/commands/handler.ts
68213
- var log29 = createLogger("commands");
68214
- var sessionLog5 = createSessionLog(log29);
68369
+ var log30 = createLogger("commands");
68370
+ var sessionLog5 = createSessionLog(log30);
68215
68371
  function sessionAccountOption(session, ctx) {
68216
68372
  if (!session.claudeAccountId)
68217
68373
  return;
@@ -68866,8 +69022,8 @@ function formatRelativeTime(date) {
68866
69022
  }
68867
69023
  // src/session/lifecycle.ts
68868
69024
  init_worktree();
68869
- var log30 = createLogger("lifecycle");
68870
- var sessionLog6 = createSessionLog(log30);
69025
+ var log31 = createLogger("lifecycle");
69026
+ var sessionLog6 = createSessionLog(log31);
68871
69027
  function mutableSessions(ctx) {
68872
69028
  return ctx.state.sessions;
68873
69029
  }
@@ -69207,7 +69363,7 @@ function resumeSessionHeaderMode(persisted, platformConfigured) {
69207
69363
  function resolveSessionHeaderMode(configured, replyToPostId, platformId) {
69208
69364
  const mode = configured ?? DEFAULT_OVERHEAD_VISIBILITY;
69209
69365
  if (mode === "hidden" && !replyToPostId) {
69210
- log30.error(`sessionHeader: hidden requires a replyToPostId for ${platformId}; ` + `downgrading this session to 'minimal' so the header post is still short.`);
69366
+ log31.error(`sessionHeader: hidden requires a replyToPostId for ${platformId}; ` + `downgrading this session to 'minimal' so the header post is still short.`);
69211
69367
  return "minimal";
69212
69368
  }
69213
69369
  return mode;
@@ -69226,7 +69382,7 @@ async function startSession(options, username, displayName, replyToPostId, platf
69226
69382
  throw new Error(`Platform '${platformId}' not found. Call addPlatform() first.`);
69227
69383
  }
69228
69384
  if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers: undefined })) {
69229
- log30.warn(`auth.denied.startSession: @${username || "unknown"} not authorized to start session in ${threadId.substring(0, 8)}...`);
69385
+ log31.warn(`auth.denied.startSession: @${username || "unknown"} not authorized to start session in ${threadId.substring(0, 8)}...`);
69230
69386
  return;
69231
69387
  }
69232
69388
  const activeOrPending = ctx.state.sessions.size + pendingStartsCount;
@@ -69287,23 +69443,26 @@ async function startSession(options, username, displayName, replyToPostId, platf
69287
69443
  return;
69288
69444
  }
69289
69445
  workingDir = resolvedDir;
69290
- log30.info(`Starting session in directory: ${workingDir} (from !cd command)`);
69446
+ log31.info(`Starting session in directory: ${workingDir} (from !cd command)`);
69291
69447
  }
69292
69448
  if (initialOptions?.permissionMode) {
69293
69449
  permissionMode = initialOptions.permissionMode;
69294
69450
  forceInteractivePermissions = permissionMode === "default";
69295
69451
  sessionPermissionModeOverride = permissionMode;
69296
- log30.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
69452
+ log31.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
69297
69453
  } else if (initialOptions?.forceInteractivePermissions) {
69298
69454
  forceInteractivePermissions = true;
69299
69455
  permissionMode = "default";
69300
- log30.info(`Starting session with interactive permissions (from !permissions command)`);
69456
+ log31.info(`Starting session with interactive permissions (from !permissions command)`);
69301
69457
  }
69302
69458
  const systemPrompt = await buildAppendSystemPrompt(platform, platformId, workingDir, actualThreadId, username, [username], CHAT_PLATFORM_PROMPT, ctx.state.githubEmailsStore);
69303
69459
  const platformMcpConfig = platform.getMcpConfig();
69304
- const claudeAccount = ctx.ops.acquireClaudeAccount(undefined, actualThreadId);
69460
+ await ctx.ops.refreshClaudeAccountUsage();
69461
+ const claudeAccount = ctx.ops.acquireClaudeAccount(undefined, actualThreadId, {
69462
+ balanceByUsage: true
69463
+ });
69305
69464
  if (claudeAccount) {
69306
- log30.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
69465
+ log31.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
69307
69466
  }
69308
69467
  const cliOptions = {
69309
69468
  workingDir,
@@ -69417,28 +69576,28 @@ async function resumeSession(state, ctx) {
69417
69576
  !state.claudeSessionId && "claudeSessionId",
69418
69577
  !state.workingDir && "workingDir"
69419
69578
  ].filter(Boolean).join(", ");
69420
- log30.warn(`Skipping session with missing required fields: ${missing}`);
69579
+ log31.warn(`Skipping session with missing required fields: ${missing}`);
69421
69580
  return;
69422
69581
  }
69423
69582
  const shortId = state.threadId.substring(0, 8);
69424
69583
  const platforms = ctx.state.platforms;
69425
69584
  const platform = platforms.get(state.platformId);
69426
69585
  if (!platform) {
69427
- log30.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
69586
+ log31.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
69428
69587
  return;
69429
69588
  }
69430
69589
  const threadPost = await platform.getPost(state.threadId);
69431
69590
  if (!threadPost) {
69432
- log30.warn(`Thread ${shortId}... deleted, skipping resume`);
69591
+ log31.warn(`Thread ${shortId}... deleted, skipping resume`);
69433
69592
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
69434
69593
  return;
69435
69594
  }
69436
69595
  if (ctx.state.sessions.size >= ctx.config.maxSessions) {
69437
- log30.warn(`Max sessions reached, skipping resume for ${shortId}...`);
69596
+ log31.warn(`Max sessions reached, skipping resume for ${shortId}...`);
69438
69597
  return;
69439
69598
  }
69440
69599
  if (!existsSync12(state.workingDir)) {
69441
- log30.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
69600
+ log31.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
69442
69601
  ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
69443
69602
  const resumeFormatter = platform.getFormatter();
69444
69603
  const tempSession = {
@@ -69459,7 +69618,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
69459
69618
  const appendSystemPrompt = await buildAppendSystemPrompt(platform, state.platformId, state.workingDir, state.threadId, state.startedBy, state.sessionAllowedUsers || [state.startedBy], CHAT_PLATFORM_PROMPT, ctx.state.githubEmailsStore);
69460
69619
  const claudeAccount = ctx.ops.acquireClaudeAccount(state.claudeAccountId, state.threadId);
69461
69620
  if (state.claudeAccountId && !claudeAccount) {
69462
- log30.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
69621
+ log31.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
69463
69622
  }
69464
69623
  const cliOptions = {
69465
69624
  workingDir: state.workingDir,
@@ -69529,7 +69688,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
69529
69688
  worktreePath: detected.worktreePath,
69530
69689
  branch: detected.branch
69531
69690
  };
69532
- log30.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
69691
+ log31.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
69533
69692
  }
69534
69693
  }
69535
69694
  session.messageManager = createMessageManager(session, ctx);
@@ -69586,7 +69745,7 @@ ${sessionFormatter.formatItalic("Reconnected to Claude session. You can continue
69586
69745
  await postResumeCoAuthorOnboarding(session, ctx);
69587
69746
  ctx.ops.persistSession(session);
69588
69747
  } catch (err) {
69589
- log30.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
69748
+ log31.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
69590
69749
  session.messageManager?.dispose();
69591
69750
  ctx.ops.emitSessionRemove(sessionId);
69592
69751
  mutableSessions(ctx).delete(sessionId);
@@ -69633,28 +69792,28 @@ async function resumePausedSession(threadId, message, files, ctx, username) {
69633
69792
  const persisted = ctx.state.sessionStore.load();
69634
69793
  const state = findPersistedByThreadId(persisted, threadId);
69635
69794
  if (!state) {
69636
- log30.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
69795
+ log31.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
69637
69796
  return;
69638
69797
  }
69639
69798
  const shortId = threadId.substring(0, 8);
69640
69799
  const platform = ctx.state.platforms.get(state.platformId);
69641
69800
  if (!platform) {
69642
- log30.warn(`auth.denied.resume: platform '${state.platformId}' not found for ${shortId}...`);
69801
+ log31.warn(`auth.denied.resume: platform '${state.platformId}' not found for ${shortId}...`);
69643
69802
  return;
69644
69803
  }
69645
69804
  const sessionAllowedUsers = new Set(state.sessionAllowedUsers || [state.startedBy].filter(Boolean));
69646
69805
  if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers })) {
69647
- log30.warn(`auth.denied.resume: @${username || "unknown"} not authorized to resume ${shortId}...`);
69806
+ log31.warn(`auth.denied.resume: @${username || "unknown"} not authorized to resume ${shortId}...`);
69648
69807
  return;
69649
69808
  }
69650
- log30.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
69809
+ log31.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
69651
69810
  await resumeSession(state, ctx);
69652
69811
  const session = ctx.ops.findSessionByThreadId(threadId);
69653
69812
  if (session && session.claude.isRunning() && session.messageManager) {
69654
69813
  session.messageCount++;
69655
69814
  await session.messageManager.handleUserMessage(message, files, state.startedBy);
69656
69815
  } else {
69657
- log30.warn(`Failed to resume session ${shortId}..., could not send message`);
69816
+ log31.warn(`Failed to resume session ${shortId}..., could not send message`);
69658
69817
  }
69659
69818
  }
69660
69819
  async function handleExit(sessionId, code, ctx) {
@@ -69662,7 +69821,7 @@ async function handleExit(sessionId, code, ctx) {
69662
69821
  const shortId = sessionId.substring(0, 8);
69663
69822
  sessionLog6(session).debug(`handleExit called code=${code} isShuttingDown=${ctx.state.isShuttingDown}`);
69664
69823
  if (!session) {
69665
- log30.debug(`Session ${shortId}... not found (already cleaned up)`);
69824
+ log31.debug(`Session ${shortId}... not found (already cleaned up)`);
69666
69825
  return;
69667
69826
  }
69668
69827
  if (isSessionRestarting(session)) {
@@ -69855,7 +70014,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
69855
70014
  }
69856
70015
 
69857
70016
  // src/operations/monitor/handler.ts
69858
- var log31 = createLogger("monitor");
70017
+ var log32 = createLogger("monitor");
69859
70018
  var DEFAULT_INTERVAL_MS = 60 * 1000;
69860
70019
 
69861
70020
  class SessionMonitor {
@@ -69877,14 +70036,14 @@ class SessionMonitor {
69877
70036
  }
69878
70037
  start() {
69879
70038
  if (this.isRunning) {
69880
- log31.debug("Session monitor already running");
70039
+ log32.debug("Session monitor already running");
69881
70040
  return;
69882
70041
  }
69883
70042
  this.isRunning = true;
69884
- log31.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
70043
+ log32.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
69885
70044
  this.timer = setInterval(() => {
69886
70045
  this.runCheck().catch((err) => {
69887
- log31.error(`Error during session monitoring: ${err}`);
70046
+ log32.error(`Error during session monitoring: ${err}`);
69888
70047
  });
69889
70048
  }, this.intervalMs);
69890
70049
  }
@@ -69894,7 +70053,7 @@ class SessionMonitor {
69894
70053
  this.timer = null;
69895
70054
  }
69896
70055
  this.isRunning = false;
69897
- log31.debug("Session monitor stopped");
70056
+ log32.debug("Session monitor stopped");
69898
70057
  }
69899
70058
  async runCheck() {
69900
70059
  await cleanupIdleSessions(this.sessionTimeoutMs, this.sessionWarningMs, this.getContext());
@@ -69906,8 +70065,8 @@ class SessionMonitor {
69906
70065
  // src/operations/plugin/handler.ts
69907
70066
  init_spawn();
69908
70067
  init_logger();
69909
- var log32 = createLogger("plugin");
69910
- var sessionLog7 = createSessionLog(log32);
70068
+ var log33 = createLogger("plugin");
70069
+ var sessionLog7 = createSessionLog(log33);
69911
70070
  async function runPluginCommand(args, cwd, timeout2 = 60000) {
69912
70071
  return new Promise((resolve6) => {
69913
70072
  const claudePath = process.env.CLAUDE_PATH || "claude";
@@ -69928,7 +70087,7 @@ async function runPluginCommand(args, cwd, timeout2 = 60000) {
69928
70087
  });
69929
70088
  proc.on("error", (err) => {
69930
70089
  resolve6({ stdout, stderr, exitCode: 1 });
69931
- log32.error(`Plugin command error: ${err.message}`);
70090
+ log33.error(`Plugin command error: ${err.message}`);
69932
70091
  });
69933
70092
  });
69934
70093
  }
@@ -70130,7 +70289,7 @@ class SessionRegistry {
70130
70289
  // src/session/reaction-router.ts
70131
70290
  init_emoji();
70132
70291
  init_logger();
70133
- var log33 = createLogger("manager");
70292
+ var log34 = createLogger("manager");
70134
70293
  async function handleReaction(deps, platformId, postId, emojiName, username, action) {
70135
70294
  const normalizedEmoji = normalizeEmojiName(emojiName);
70136
70295
  if (action === "added" && isResumeEmoji(normalizedEmoji)) {
@@ -70144,7 +70303,7 @@ async function handleReaction(deps, platformId, postId, emojiName, username, act
70144
70303
  if (session.platformId !== platformId)
70145
70304
  return;
70146
70305
  if (!session.sessionAllowedUsers.has(username) && !session.platform.isUserAllowed(username)) {
70147
- log33.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
70306
+ log34.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
70148
70307
  event: "reaction.rejected",
70149
70308
  platformId,
70150
70309
  sessionId: session.sessionId,
@@ -70180,7 +70339,7 @@ async function tryResumeFromReaction(deps, platformId, postId, username) {
70180
70339
  return false;
70181
70340
  }
70182
70341
  const shortId = persistedSession.threadId.substring(0, 8);
70183
- log33.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
70342
+ log34.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
70184
70343
  await resumeSession(persistedSession, deps.getContext());
70185
70344
  return true;
70186
70345
  }
@@ -70210,7 +70369,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
70210
70369
  }
70211
70370
  if (session.lastError?.postId === postId && isBugReportEmoji(emojiName)) {
70212
70371
  if (session.startedBy === username || session.platform.isUserAllowed(username) || session.sessionAllowedUsers.has(username)) {
70213
- log33.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
70372
+ log34.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
70214
70373
  await reportBug(session, undefined, username, deps.getContext(), session.lastError);
70215
70374
  return;
70216
70375
  }
@@ -70225,7 +70384,10 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
70225
70384
 
70226
70385
  // src/session/manager.ts
70227
70386
  init_logger();
70228
- var log34 = createLogger("manager");
70387
+ var log35 = createLogger("manager");
70388
+ var USAGE_PROBE_TIMEOUT_MS = 1e4;
70389
+ var USAGE_REFRESH_DEADLINE_MS = 5000;
70390
+ var USAGE_CACHE_TTL_MS = 15000;
70229
70391
 
70230
70392
  class SessionManager extends EventEmitter4 {
70231
70393
  platforms = new Map;
@@ -70252,6 +70414,8 @@ class SessionManager extends EventEmitter4 {
70252
70414
  platformOverhead = new Map;
70253
70415
  autoUpdateManager = null;
70254
70416
  accountPool;
70417
+ usageRefreshInFlight = null;
70418
+ usageRefreshedAt = 0;
70255
70419
  constructor(workingDir, permissionModeOrSkipFlag = "default", chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts, respondOnlyWhenMentioned = false) {
70256
70420
  super();
70257
70421
  this.workingDir = workingDir;
@@ -70306,7 +70470,7 @@ class SessionManager extends EventEmitter4 {
70306
70470
  markNeedsBump(platformId);
70307
70471
  this.updateStickyMessage();
70308
70472
  });
70309
- log34.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
70473
+ log35.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
70310
70474
  }
70311
70475
  removePlatform(platformId) {
70312
70476
  this.platforms.delete(platformId);
@@ -70324,7 +70488,7 @@ class SessionManager extends EventEmitter4 {
70324
70488
  if (users) {
70325
70489
  users.add(sessionId);
70326
70490
  }
70327
- log34.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
70491
+ log35.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
70328
70492
  }
70329
70493
  unregisterWorktreeUser(worktreePath, sessionId) {
70330
70494
  const users = this.worktreeUsers.get(worktreePath);
@@ -70394,7 +70558,8 @@ class SessionManager extends EventEmitter4 {
70394
70558
  emitSessionAdd: (s) => this.emitSessionAdd(s),
70395
70559
  emitSessionUpdate: (sid, u) => this.emitSessionUpdate(sid, u),
70396
70560
  emitSessionRemove: (sid) => this.emitSessionRemove(sid),
70397
- acquireClaudeAccount: (preferredId, threadId) => this.accountPool.acquire(preferredId, threadId),
70561
+ acquireClaudeAccount: (preferredId, threadId, opts) => this.accountPool.acquire(preferredId, threadId, opts),
70562
+ refreshClaudeAccountUsage: () => this.refreshAccountUsage(),
70398
70563
  getClaudeAccount: (id) => this.accountPool.get(id),
70399
70564
  releaseClaudeAccount: (id) => this.accountPool.release(id),
70400
70565
  markClaudeAccountCooling: (id, untilMs) => this.accountPool.markCooling(id, untilMs),
@@ -70625,11 +70790,11 @@ class SessionManager extends EventEmitter4 {
70625
70790
  }
70626
70791
  }
70627
70792
  if (sessionsToKill.length === 0) {
70628
- log34.info(`No active sessions to pause for platform ${platformId}`);
70793
+ log35.info(`No active sessions to pause for platform ${platformId}`);
70629
70794
  await this.updateStickyMessage();
70630
70795
  return;
70631
70796
  }
70632
- log34.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
70797
+ log35.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
70633
70798
  for (const session of sessionsToKill) {
70634
70799
  try {
70635
70800
  const fmt = session.platform.getFormatter();
@@ -70645,9 +70810,9 @@ class SessionManager extends EventEmitter4 {
70645
70810
  session.claude.kill();
70646
70811
  this.registry.unregister(session.sessionId);
70647
70812
  this.emitSessionRemove(session.sessionId);
70648
- log34.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
70813
+ log35.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
70649
70814
  } catch (err) {
70650
- log34.warn(`Failed to pause session ${session.threadId}: ${err}`);
70815
+ log35.warn(`Failed to pause session ${session.threadId}: ${err}`);
70651
70816
  }
70652
70817
  }
70653
70818
  for (const session of sessionsToKill) {
@@ -70668,21 +70833,47 @@ class SessionManager extends EventEmitter4 {
70668
70833
  sessionsToResume.push(state);
70669
70834
  }
70670
70835
  if (sessionsToResume.length === 0) {
70671
- log34.info(`No paused sessions to resume for platform ${platformId}`);
70836
+ log35.info(`No paused sessions to resume for platform ${platformId}`);
70672
70837
  await this.updateStickyMessage();
70673
70838
  return;
70674
70839
  }
70675
- log34.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
70840
+ log35.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
70676
70841
  for (const state of sessionsToResume) {
70677
70842
  try {
70678
70843
  await resumeSession(state, this.getContext());
70679
- log34.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
70844
+ log35.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
70680
70845
  } catch (err) {
70681
- log34.warn(`Failed to resume session ${state.threadId}: ${err}`);
70846
+ log35.warn(`Failed to resume session ${state.threadId}: ${err}`);
70682
70847
  }
70683
70848
  }
70684
70849
  await this.updateStickyMessage();
70685
70850
  }
70851
+ async refreshAccountUsage() {
70852
+ const accounts = this.accountPool.all;
70853
+ if (accounts.length < 2)
70854
+ return;
70855
+ if (Date.now() - this.usageRefreshedAt < USAGE_CACHE_TTL_MS)
70856
+ return;
70857
+ if (!this.usageRefreshInFlight) {
70858
+ this.usageRefreshInFlight = this.probeAllAccounts(accounts).finally(() => {
70859
+ this.usageRefreshedAt = Date.now();
70860
+ this.usageRefreshInFlight = null;
70861
+ });
70862
+ }
70863
+ await Promise.race([
70864
+ this.usageRefreshInFlight,
70865
+ new Promise((resolve6) => setTimeout(resolve6, USAGE_REFRESH_DEADLINE_MS))
70866
+ ]);
70867
+ }
70868
+ async probeAllAccounts(accounts) {
70869
+ await Promise.all(accounts.map(async (acc) => {
70870
+ try {
70871
+ this.accountPool.setUsage(acc.id, await probeAccountUsage(acc, { timeoutMs: USAGE_PROBE_TIMEOUT_MS }));
70872
+ } catch {
70873
+ this.accountPool.setUsage(acc.id, null);
70874
+ }
70875
+ }));
70876
+ }
70686
70877
  async initialize() {
70687
70878
  initialize(this.sessionStore);
70688
70879
  this.sessionMonitor?.start();
@@ -70690,14 +70881,14 @@ class SessionManager extends EventEmitter4 {
70690
70881
  const sessionTimeoutMs = this.limits.sessionTimeoutMinutes * 60 * 1000;
70691
70882
  const staleIds = this.sessionStore.cleanStale(sessionTimeoutMs * 2);
70692
70883
  if (staleIds.length > 0) {
70693
- log34.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
70884
+ log35.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
70694
70885
  }
70695
70886
  const removedCount = this.sessionStore.cleanHistory();
70696
70887
  if (removedCount > 0) {
70697
- log34.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
70888
+ log35.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
70698
70889
  }
70699
70890
  const persisted = this.sessionStore.load();
70700
- log34.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
70891
+ log35.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
70701
70892
  const excludePostIdsByPlatform = new Map;
70702
70893
  for (const session of persisted.values()) {
70703
70894
  const platformId = session.platformId;
@@ -70717,10 +70908,10 @@ class SessionManager extends EventEmitter4 {
70717
70908
  const excludePostIds = excludePostIdsByPlatform.get(platform.platformId);
70718
70909
  platform.getBotUser().then((botUser) => {
70719
70910
  cleanupOldStickyMessages(platform, botUser.id, true, excludePostIds).catch((err) => {
70720
- log34.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
70911
+ log35.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
70721
70912
  });
70722
70913
  }).catch((err) => {
70723
- log34.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
70914
+ log35.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
70724
70915
  });
70725
70916
  }
70726
70917
  if (persisted.size > 0) {
@@ -70734,10 +70925,10 @@ class SessionManager extends EventEmitter4 {
70734
70925
  }
70735
70926
  }
70736
70927
  if (pausedToSkip.length > 0) {
70737
- log34.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
70928
+ log35.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
70738
70929
  }
70739
70930
  if (activeToResume.length > 0) {
70740
- log34.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
70931
+ log35.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
70741
70932
  for (const state of activeToResume) {
70742
70933
  await resumeSession(state, this.getContext());
70743
70934
  }
@@ -71178,7 +71369,7 @@ Mention me to start a session in this worktree.`, threadId);
71178
71369
  const message = messageBuilder(formatter);
71179
71370
  await post(session, "info", message);
71180
71371
  } catch (err) {
71181
- log34.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
71372
+ log35.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
71182
71373
  }
71183
71374
  }
71184
71375
  }
@@ -71197,7 +71388,7 @@ Mention me to start a session in this worktree.`, threadId);
71197
71388
  session.messageManager?.setPendingUpdatePrompt({ postId: post2.id });
71198
71389
  this.registerPost(post2.id, session.threadId);
71199
71390
  } catch (err) {
71200
- log34.warn(`Failed to post ask message to ${threadId}: ${err}`);
71391
+ log35.warn(`Failed to post ask message to ${threadId}: ${err}`);
71201
71392
  }
71202
71393
  }
71203
71394
  }
@@ -78804,29 +78995,29 @@ function SessionLog({ logs, maxLines = 20 }) {
78804
78995
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
78805
78996
  flexDirection: "column",
78806
78997
  flexShrink: 0,
78807
- children: displayLogs.map((log35) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
78998
+ children: displayLogs.map((log36) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
78808
78999
  flexShrink: 0,
78809
79000
  children: [
78810
79001
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
78811
- color: getColorForLevel(log35.level),
79002
+ color: getColorForLevel(log36.level),
78812
79003
  dimColor: true,
78813
79004
  wrap: "truncate",
78814
79005
  children: [
78815
79006
  "[",
78816
- padComponent(log35.component),
79007
+ padComponent(log36.component),
78817
79008
  "]"
78818
79009
  ]
78819
79010
  }, undefined, true, undefined, this),
78820
79011
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
78821
- color: getColorForLevel(log35.level),
79012
+ color: getColorForLevel(log36.level),
78822
79013
  wrap: "truncate",
78823
79014
  children: [
78824
79015
  " ",
78825
- log35.message
79016
+ log36.message
78826
79017
  ]
78827
79018
  }, undefined, true, undefined, this)
78828
79019
  ]
78829
- }, log35.id, true, undefined, this))
79020
+ }, log36.id, true, undefined, this))
78830
79021
  }, undefined, false, undefined, this);
78831
79022
  }
78832
79023
  // src/ui/components/Footer.tsx
@@ -79350,7 +79541,7 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
79350
79541
  const scrollRef = import_react59.default.useRef(null);
79351
79542
  const { stdout } = use_stdout_default();
79352
79543
  const isDebug = process.env.DEBUG === "1";
79353
- const displayLogs = logs.filter((log35) => isDebug || log35.level !== "debug");
79544
+ const displayLogs = logs.filter((log36) => isDebug || log36.level !== "debug");
79354
79545
  const visibleLogs = displayLogs.slice(-Math.max(maxLines * 3, 100));
79355
79546
  import_react59.default.useEffect(() => {
79356
79547
  const handleResize = () => scrollRef.current?.remeasure();
@@ -79390,25 +79581,25 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
79390
79581
  overflow: "hidden",
79391
79582
  children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ScrollView, {
79392
79583
  ref: scrollRef,
79393
- children: visibleLogs.map((log35) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
79584
+ children: visibleLogs.map((log36) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
79394
79585
  children: [
79395
79586
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
79396
79587
  dimColor: true,
79397
79588
  children: [
79398
79589
  "[",
79399
- padComponent2(log35.component),
79590
+ padComponent2(log36.component),
79400
79591
  "]"
79401
79592
  ]
79402
79593
  }, undefined, true, undefined, this),
79403
79594
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
79404
- color: getLevelColor(log35.level),
79595
+ color: getLevelColor(log36.level),
79405
79596
  children: [
79406
79597
  " ",
79407
- log35.message
79598
+ log36.message
79408
79599
  ]
79409
79600
  }, undefined, true, undefined, this)
79410
79601
  ]
79411
- }, log35.id, true, undefined, this))
79602
+ }, log36.id, true, undefined, this))
79412
79603
  }, undefined, false, undefined, this)
79413
79604
  }, undefined, false, undefined, this);
79414
79605
  }
@@ -79925,10 +80116,10 @@ function useAppState(initialConfig) {
79925
80116
  });
79926
80117
  }, []);
79927
80118
  const getLogsForSession = import_react60.useCallback((sessionId) => {
79928
- return state.logs.filter((log35) => log35.sessionId === sessionId);
80119
+ return state.logs.filter((log36) => log36.sessionId === sessionId);
79929
80120
  }, [state.logs]);
79930
80121
  const getGlobalLogs = import_react60.useCallback(() => {
79931
- return state.logs.filter((log35) => !log35.sessionId);
80122
+ return state.logs.filter((log36) => !log36.sessionId);
79932
80123
  }, [state.logs]);
79933
80124
  const togglePlatformEnabled = import_react60.useCallback((platformId) => {
79934
80125
  let newEnabled = false;
@@ -80717,6 +80908,10 @@ async function startUI(options) {
80717
80908
  init_logger();
80718
80909
 
80719
80910
  // src/message-handler.ts
80911
+ function withMessagePermalink(client, post2, content) {
80912
+ return `[message permalink: ${client.getPostPermalink(post2)}]
80913
+ ${content}`;
80914
+ }
80720
80915
  async function handleMessage(client, session, post2, user, options) {
80721
80916
  const { platformId, logger, onKill } = options;
80722
80917
  const username = user?.username || "unknown";
@@ -80813,8 +81008,10 @@ async function handleMessage(client, session, post2, user, options) {
80813
81008
  return;
80814
81009
  }
80815
81010
  const files2 = post2.metadata?.files;
80816
- if (content || files2?.length)
80817
- await session.sendFollowUp(threadRoot, content, files2, username, user?.displayName);
81011
+ if (content || files2?.length) {
81012
+ const contentForAgent = content && post2.id !== threadRoot ? withMessagePermalink(client, post2, content) : content;
81013
+ await session.sendFollowUp(threadRoot, contentForAgent, files2, username, user?.displayName);
81014
+ }
80818
81015
  return;
80819
81016
  }
80820
81017
  const hasPausedSession = session.registry.getPersistedByThreadId(threadRoot) !== undefined;
@@ -80941,7 +81138,7 @@ import { EventEmitter as EventEmitter9 } from "events";
80941
81138
  // src/auto-update/checker.ts
80942
81139
  init_logger();
80943
81140
  import { EventEmitter as EventEmitter7 } from "events";
80944
- var log35 = createLogger("checker");
81141
+ var log36 = createLogger("checker");
80945
81142
  var PACKAGE_NAME = "claude-threads";
80946
81143
  function compareVersions(a, b) {
80947
81144
  const partsA = a.replace(/^v/, "").split(".").map(Number);
@@ -80964,13 +81161,13 @@ async function fetchLatestVersion() {
80964
81161
  }
80965
81162
  });
80966
81163
  if (!response.ok) {
80967
- log35.warn(`Failed to fetch latest version: HTTP ${response.status}`);
81164
+ log36.warn(`Failed to fetch latest version: HTTP ${response.status}`);
80968
81165
  return null;
80969
81166
  }
80970
81167
  const data = await response.json();
80971
81168
  return data.version ?? null;
80972
81169
  } catch (err) {
80973
- log35.warn(`Failed to fetch latest version: ${err}`);
81170
+ log36.warn(`Failed to fetch latest version: ${err}`);
80974
81171
  return null;
80975
81172
  }
80976
81173
  }
@@ -80987,38 +81184,38 @@ class UpdateChecker extends EventEmitter7 {
80987
81184
  }
80988
81185
  start() {
80989
81186
  if (!this.config.enabled) {
80990
- log35.debug("Auto-update disabled, not starting checker");
81187
+ log36.debug("Auto-update disabled, not starting checker");
80991
81188
  return;
80992
81189
  }
80993
81190
  setTimeout(() => {
80994
81191
  this.check().catch((err) => {
80995
- log35.warn(`Initial update check failed: ${err}`);
81192
+ log36.warn(`Initial update check failed: ${err}`);
80996
81193
  });
80997
81194
  }, 5000);
80998
81195
  const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
80999
81196
  this.checkInterval = setInterval(() => {
81000
81197
  this.check().catch((err) => {
81001
- log35.warn(`Periodic update check failed: ${err}`);
81198
+ log36.warn(`Periodic update check failed: ${err}`);
81002
81199
  });
81003
81200
  }, intervalMs);
81004
- log35.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
81201
+ log36.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
81005
81202
  }
81006
81203
  stop() {
81007
81204
  if (this.checkInterval) {
81008
81205
  clearInterval(this.checkInterval);
81009
81206
  this.checkInterval = null;
81010
81207
  }
81011
- log35.debug("Update checker stopped");
81208
+ log36.debug("Update checker stopped");
81012
81209
  }
81013
81210
  async check() {
81014
81211
  if (this.isChecking) {
81015
- log35.debug("Check already in progress, skipping");
81212
+ log36.debug("Check already in progress, skipping");
81016
81213
  return this.lastUpdateInfo;
81017
81214
  }
81018
81215
  this.isChecking = true;
81019
81216
  this.emit("check:start");
81020
81217
  try {
81021
- log35.debug("Checking for updates...");
81218
+ log36.debug("Checking for updates...");
81022
81219
  const latestVersion2 = await fetchLatestVersion();
81023
81220
  if (!latestVersion2) {
81024
81221
  this.emit("check:complete", false);
@@ -81035,18 +81232,18 @@ class UpdateChecker extends EventEmitter7 {
81035
81232
  detectedAt: new Date
81036
81233
  };
81037
81234
  if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
81038
- log35.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
81235
+ log36.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
81039
81236
  this.lastUpdateInfo = updateInfo;
81040
81237
  this.emit("update", updateInfo);
81041
81238
  }
81042
81239
  this.emit("check:complete", true);
81043
81240
  return updateInfo;
81044
81241
  }
81045
- log35.debug(`Up to date (v${currentVersion})`);
81242
+ log36.debug(`Up to date (v${currentVersion})`);
81046
81243
  this.emit("check:complete", false);
81047
81244
  return null;
81048
81245
  } catch (err) {
81049
- log35.warn(`Update check failed: ${err}`);
81246
+ log36.warn(`Update check failed: ${err}`);
81050
81247
  this.emit("check:error", err);
81051
81248
  return null;
81052
81249
  } finally {
@@ -81117,7 +81314,7 @@ function isInScheduledWindow(window2) {
81117
81314
  }
81118
81315
 
81119
81316
  // src/auto-update/scheduler.ts
81120
- var log36 = createLogger("scheduler");
81317
+ var log37 = createLogger("scheduler");
81121
81318
 
81122
81319
  class UpdateScheduler extends EventEmitter8 {
81123
81320
  config;
@@ -81141,7 +81338,7 @@ class UpdateScheduler extends EventEmitter8 {
81141
81338
  scheduleUpdate(updateInfo) {
81142
81339
  this.pendingUpdate = updateInfo;
81143
81340
  if (this.config.autoRestartMode === "immediate") {
81144
- log36.info("Immediate mode: triggering update now");
81341
+ log37.info("Immediate mode: triggering update now");
81145
81342
  this.emit("ready", updateInfo);
81146
81343
  return;
81147
81344
  }
@@ -81154,19 +81351,19 @@ class UpdateScheduler extends EventEmitter8 {
81154
81351
  this.scheduledRestartAt = null;
81155
81352
  this.askApprovals.clear();
81156
81353
  this.askStartTime = null;
81157
- log36.debug("Update schedule cancelled");
81354
+ log37.debug("Update schedule cancelled");
81158
81355
  }
81159
81356
  deferUpdate(minutes) {
81160
81357
  const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
81161
81358
  this.scheduledRestartAt = null;
81162
81359
  this.idleStartTime = null;
81163
81360
  this.emit("deferred", deferUntil);
81164
- log36.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
81361
+ log37.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
81165
81362
  return deferUntil;
81166
81363
  }
81167
81364
  recordAskResponse(threadId, approved) {
81168
81365
  this.askApprovals.set(threadId, approved);
81169
- log36.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
81366
+ log37.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
81170
81367
  this.checkAskCondition();
81171
81368
  }
81172
81369
  getScheduledRestartAt() {
@@ -81187,7 +81384,7 @@ class UpdateScheduler extends EventEmitter8 {
81187
81384
  return;
81188
81385
  this.checkCondition();
81189
81386
  this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
81190
- log36.debug(`Started checking for ${this.config.autoRestartMode} condition`);
81387
+ log37.debug(`Started checking for ${this.config.autoRestartMode} condition`);
81191
81388
  }
81192
81389
  stopChecking() {
81193
81390
  if (this.checkTimer) {
@@ -81218,17 +81415,17 @@ class UpdateScheduler extends EventEmitter8 {
81218
81415
  if (activity.activeSessionCount === 0) {
81219
81416
  if (!this.idleStartTime) {
81220
81417
  this.idleStartTime = new Date;
81221
- log36.debug("No active sessions, starting idle timer");
81418
+ log37.debug("No active sessions, starting idle timer");
81222
81419
  }
81223
81420
  const idleMs = Date.now() - this.idleStartTime.getTime();
81224
81421
  const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
81225
81422
  if (idleMs >= requiredMs) {
81226
- log36.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
81423
+ log37.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
81227
81424
  this.triggerCountdown();
81228
81425
  }
81229
81426
  } else {
81230
81427
  if (this.idleStartTime) {
81231
- log36.debug("Sessions became active, resetting idle timer");
81428
+ log37.debug("Sessions became active, resetting idle timer");
81232
81429
  this.idleStartTime = null;
81233
81430
  }
81234
81431
  }
@@ -81239,7 +81436,7 @@ class UpdateScheduler extends EventEmitter8 {
81239
81436
  const quietMs = Date.now() - activity.lastActivityAt.getTime();
81240
81437
  const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
81241
81438
  if (quietMs >= requiredMs && !activity.anySessionBusy) {
81242
- log36.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
81439
+ log37.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
81243
81440
  this.triggerCountdown();
81244
81441
  }
81245
81442
  } else if (activity.activeSessionCount === 0) {
@@ -81249,7 +81446,7 @@ class UpdateScheduler extends EventEmitter8 {
81249
81446
  const idleMs = Date.now() - this.idleStartTime.getTime();
81250
81447
  const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
81251
81448
  if (idleMs >= requiredMs) {
81252
- log36.info("No sessions and quiet timeout reached, triggering update");
81449
+ log37.info("No sessions and quiet timeout reached, triggering update");
81253
81450
  this.triggerCountdown();
81254
81451
  }
81255
81452
  }
@@ -81260,13 +81457,13 @@ class UpdateScheduler extends EventEmitter8 {
81260
81457
  }
81261
81458
  const activity = this.getSessionActivity();
81262
81459
  if (activity.activeSessionCount === 0) {
81263
- log36.info("Within scheduled window and no active sessions, triggering update");
81460
+ log37.info("Within scheduled window and no active sessions, triggering update");
81264
81461
  this.triggerCountdown();
81265
81462
  } else if (activity.lastActivityAt) {
81266
81463
  const quietMs = Date.now() - activity.lastActivityAt.getTime();
81267
81464
  const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
81268
81465
  if (quietMs >= requiredMs && !activity.anySessionBusy) {
81269
- log36.info("Within scheduled window and sessions quiet, triggering update");
81466
+ log37.info("Within scheduled window and sessions quiet, triggering update");
81270
81467
  this.triggerCountdown();
81271
81468
  }
81272
81469
  }
@@ -81274,14 +81471,14 @@ class UpdateScheduler extends EventEmitter8 {
81274
81471
  checkAskCondition() {
81275
81472
  const threadIds = this.getActiveThreadIds();
81276
81473
  if (threadIds.length === 0) {
81277
- log36.info("No active threads, proceeding with update");
81474
+ log37.info("No active threads, proceeding with update");
81278
81475
  this.triggerCountdown();
81279
81476
  return;
81280
81477
  }
81281
81478
  if (!this.askStartTime && this.pendingUpdate) {
81282
81479
  this.askStartTime = new Date;
81283
81480
  this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
81284
- log36.warn(`Failed to post ask message: ${err}`);
81481
+ log37.warn(`Failed to post ask message: ${err}`);
81285
81482
  });
81286
81483
  return;
81287
81484
  }
@@ -81294,12 +81491,12 @@ class UpdateScheduler extends EventEmitter8 {
81294
81491
  denials++;
81295
81492
  }
81296
81493
  if (approvals > threadIds.length / 2) {
81297
- log36.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
81494
+ log37.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
81298
81495
  this.triggerCountdown();
81299
81496
  return;
81300
81497
  }
81301
81498
  if (denials > threadIds.length / 2) {
81302
- log36.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
81499
+ log37.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
81303
81500
  this.deferUpdate(60);
81304
81501
  return;
81305
81502
  }
@@ -81307,7 +81504,7 @@ class UpdateScheduler extends EventEmitter8 {
81307
81504
  const elapsedMs = Date.now() - this.askStartTime.getTime();
81308
81505
  const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
81309
81506
  if (elapsedMs >= timeoutMs) {
81310
- log36.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
81507
+ log37.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
81311
81508
  this.triggerCountdown();
81312
81509
  }
81313
81510
  }
@@ -81327,7 +81524,7 @@ class UpdateScheduler extends EventEmitter8 {
81327
81524
  this.emit("ready", this.pendingUpdate);
81328
81525
  }
81329
81526
  }, 1000);
81330
- log36.info("Update countdown started (60 seconds)");
81527
+ log37.info("Update countdown started (60 seconds)");
81331
81528
  }
81332
81529
  stopCountdown() {
81333
81530
  if (this.countdownTimer) {
@@ -81343,24 +81540,24 @@ import { spawn as spawn4, spawnSync } from "child_process";
81343
81540
  import { existsSync as existsSync14, readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
81344
81541
  import { dirname as dirname8, resolve as resolve6 } from "path";
81345
81542
  import { homedir as homedir6 } from "os";
81346
- var log37 = createLogger("installer");
81543
+ var log38 = createLogger("installer");
81347
81544
  function detectPackageManager() {
81348
81545
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
81349
81546
  const originalInstaller = detectOriginalInstaller();
81350
81547
  if (originalInstaller) {
81351
- log37.debug(`Detected original installer: ${originalInstaller}`);
81548
+ log38.debug(`Detected original installer: ${originalInstaller}`);
81352
81549
  if (originalInstaller === "bun") {
81353
81550
  const bunCheck2 = spawnSync("bun", ["--version"], { stdio: "ignore" });
81354
81551
  if (bunCheck2.status === 0) {
81355
81552
  return { cmd: "bun", isBun: true };
81356
81553
  }
81357
- log37.warn("Originally installed with bun, but bun not found. Falling back to npm.");
81554
+ log38.warn("Originally installed with bun, but bun not found. Falling back to npm.");
81358
81555
  } else {
81359
81556
  const npmCheck2 = spawnSync(npmCmd, ["--version"], { stdio: "ignore" });
81360
81557
  if (npmCheck2.status === 0) {
81361
81558
  return { cmd: npmCmd, isBun: false };
81362
81559
  }
81363
- log37.warn("Originally installed with npm, but npm not found. Falling back to bun.");
81560
+ log38.warn("Originally installed with npm, but npm not found. Falling back to bun.");
81364
81561
  }
81365
81562
  }
81366
81563
  const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore" });
@@ -81411,7 +81608,7 @@ function loadUpdateState() {
81411
81608
  return JSON.parse(content);
81412
81609
  }
81413
81610
  } catch (err) {
81414
- log37.warn(`Failed to load update state: ${err}`);
81611
+ log38.warn(`Failed to load update state: ${err}`);
81415
81612
  }
81416
81613
  return {};
81417
81614
  }
@@ -81422,9 +81619,9 @@ function saveUpdateState(state) {
81422
81619
  mkdirSync5(dir, { recursive: true });
81423
81620
  }
81424
81621
  writeFileSync7(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
81425
- log37.debug("Update state saved");
81622
+ log38.debug("Update state saved");
81426
81623
  } catch (err) {
81427
- log37.warn(`Failed to save update state: ${err}`);
81624
+ log38.warn(`Failed to save update state: ${err}`);
81428
81625
  }
81429
81626
  }
81430
81627
  function clearUpdateState() {
@@ -81433,7 +81630,7 @@ function clearUpdateState() {
81433
81630
  writeFileSync7(STATE_PATH, "{}", "utf-8");
81434
81631
  }
81435
81632
  } catch (err) {
81436
- log37.warn(`Failed to clear update state: ${err}`);
81633
+ log38.warn(`Failed to clear update state: ${err}`);
81437
81634
  }
81438
81635
  }
81439
81636
  function checkJustUpdated() {
@@ -81465,11 +81662,11 @@ function clearRuntimeSettings() {
81465
81662
  }
81466
81663
  }
81467
81664
  async function installVersion(version) {
81468
- log37.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
81665
+ log38.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
81469
81666
  const pm = detectPackageManager();
81470
81667
  if (!pm) {
81471
81668
  const error = "Neither bun nor npm found in PATH. Cannot install update.";
81472
- log37.error(`❌ ${error}`);
81669
+ log38.error(`❌ ${error}`);
81473
81670
  return { success: false, error };
81474
81671
  }
81475
81672
  saveUpdateState({
@@ -81481,7 +81678,7 @@ async function installVersion(version) {
81481
81678
  return new Promise((resolve7) => {
81482
81679
  const { cmd, isBun: isBun3 } = pm;
81483
81680
  const args = ["install", "-g", `${PACKAGE_NAME2}@${version}`];
81484
- log37.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
81681
+ log38.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
81485
81682
  const child = spawn4(cmd, args, {
81486
81683
  stdio: ["ignore", "pipe", "pipe"],
81487
81684
  env: {
@@ -81499,7 +81696,7 @@ async function installVersion(version) {
81499
81696
  });
81500
81697
  child.on("close", (code) => {
81501
81698
  if (code === 0) {
81502
- log37.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
81699
+ log38.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
81503
81700
  saveUpdateState({
81504
81701
  previousVersion: VERSION,
81505
81702
  targetVersion: version,
@@ -81509,20 +81706,20 @@ async function installVersion(version) {
81509
81706
  resolve7({ success: true });
81510
81707
  } else {
81511
81708
  const errorMsg = stderr || stdout || `Exit code: ${code}`;
81512
- log37.error(`❌ Installation failed: ${errorMsg}`);
81709
+ log38.error(`❌ Installation failed: ${errorMsg}`);
81513
81710
  clearUpdateState();
81514
81711
  resolve7({ success: false, error: errorMsg });
81515
81712
  }
81516
81713
  });
81517
81714
  child.on("error", (err) => {
81518
- log37.error(`❌ Failed to spawn npm: ${err}`);
81715
+ log38.error(`❌ Failed to spawn npm: ${err}`);
81519
81716
  clearUpdateState();
81520
81717
  resolve7({ success: false, error: err.message });
81521
81718
  });
81522
81719
  setTimeout(() => {
81523
81720
  if (child.exitCode === null) {
81524
81721
  child.kill();
81525
- log37.error("❌ Installation timed out");
81722
+ log38.error("❌ Installation timed out");
81526
81723
  clearUpdateState();
81527
81724
  resolve7({ success: false, error: "Installation timed out" });
81528
81725
  }
@@ -81568,7 +81765,7 @@ init_logger();
81568
81765
  import { spawn as spawn5 } from "child_process";
81569
81766
  import { existsSync as existsSync15, statSync as statSync4 } from "fs";
81570
81767
  import { delimiter, join as join11 } from "path";
81571
- var log38 = createLogger("respawn");
81768
+ var log39 = createLogger("respawn");
81572
81769
  function decideRespawn(env5 = process.env, isTTY = !!process.stdout.isTTY) {
81573
81770
  if (env5.CLAUDE_THREADS_BIN) {
81574
81771
  return { kind: "exit-for-supervisor", supervisor: "claude-threads-daemon" };
@@ -81624,7 +81821,7 @@ function isFileExecutable(path10) {
81624
81821
  }
81625
81822
  function spawnReplacement(argv = process.argv.slice(2), binPath = resolveClaudeThreadsBin()) {
81626
81823
  if (!binPath) {
81627
- log38.error("Could not resolve claude-threads on PATH; self-respawn aborted");
81824
+ log39.error("Could not resolve claude-threads on PATH; self-respawn aborted");
81628
81825
  return false;
81629
81826
  }
81630
81827
  if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
@@ -81645,23 +81842,23 @@ function spawnReplacement(argv = process.argv.slice(2), binPath = resolveClaudeT
81645
81842
  shell: useShell
81646
81843
  });
81647
81844
  } catch (err) {
81648
- log38.error(`spawn() threw: ${err instanceof Error ? err.message : String(err)}`);
81845
+ log39.error(`spawn() threw: ${err instanceof Error ? err.message : String(err)}`);
81649
81846
  return false;
81650
81847
  }
81651
81848
  child.once("error", (err) => {
81652
- log38.error(`Replacement process error: ${err.message}`);
81849
+ log39.error(`Replacement process error: ${err.message}`);
81653
81850
  });
81654
81851
  if (child.pid === undefined) {
81655
- log38.error("Spawn returned no pid (binary likely not executable)");
81852
+ log39.error("Spawn returned no pid (binary likely not executable)");
81656
81853
  return false;
81657
81854
  }
81658
81855
  child.unref();
81659
- log38.info(`Spawned replacement pid=${child.pid} from ${binPath}`);
81856
+ log39.info(`Spawned replacement pid=${child.pid} from ${binPath}`);
81660
81857
  return true;
81661
81858
  }
81662
81859
 
81663
81860
  // src/auto-update/manager.ts
81664
- var log39 = createLogger("updater");
81861
+ var log40 = createLogger("updater");
81665
81862
 
81666
81863
  class AutoUpdateManager extends EventEmitter9 {
81667
81864
  config;
@@ -81684,23 +81881,23 @@ class AutoUpdateManager extends EventEmitter9 {
81684
81881
  }
81685
81882
  start() {
81686
81883
  if (!this.config.enabled) {
81687
- log39.info("Auto-update is disabled");
81884
+ log40.info("Auto-update is disabled");
81688
81885
  return;
81689
81886
  }
81690
81887
  const updateResult = this.installer.checkJustUpdated();
81691
81888
  if (updateResult) {
81692
- log39.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
81889
+ log40.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
81693
81890
  this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
81694
- log39.warn(`Failed to broadcast update notification: ${err}`);
81891
+ log40.warn(`Failed to broadcast update notification: ${err}`);
81695
81892
  });
81696
81893
  }
81697
81894
  this.checker.start();
81698
- log39.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
81895
+ log40.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
81699
81896
  }
81700
81897
  stop() {
81701
81898
  this.checker.stop();
81702
81899
  this.scheduler.stop();
81703
- log39.debug("Auto-update manager stopped");
81900
+ log40.debug("Auto-update manager stopped");
81704
81901
  }
81705
81902
  getState() {
81706
81903
  return { ...this.state };
@@ -81714,10 +81911,10 @@ class AutoUpdateManager extends EventEmitter9 {
81714
81911
  async forceUpdate() {
81715
81912
  const updateInfo = this.state.updateInfo || await this.checker.check();
81716
81913
  if (!updateInfo) {
81717
- log39.info("No update available");
81914
+ log40.info("No update available");
81718
81915
  return;
81719
81916
  }
81720
- log39.info("Forcing immediate update");
81917
+ log40.info("Forcing immediate update");
81721
81918
  await this.performUpdate(updateInfo);
81722
81919
  }
81723
81920
  deferUpdate(minutes = 60) {
@@ -81783,11 +81980,11 @@ class AutoUpdateManager extends EventEmitter9 {
81783
81980
  await this.callbacks.prepareForRestart();
81784
81981
  } catch (err) {
81785
81982
  const reason = err instanceof Error ? err.message : String(err);
81786
- log39.error(`prepareForRestart failed: ${reason}`);
81983
+ log40.error(`prepareForRestart failed: ${reason}`);
81787
81984
  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(() => {});
81788
81985
  process.exit(1);
81789
81986
  }
81790
- log39.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
81987
+ log40.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
81791
81988
  process.stdout.write("\x1B[2J\x1B[H");
81792
81989
  process.stdout.write("\x1B[?25h");
81793
81990
  if (decision.kind === "self-respawn") {
@@ -81796,14 +81993,14 @@ class AutoUpdateManager extends EventEmitter9 {
81796
81993
  if (ok) {
81797
81994
  process.exit(0);
81798
81995
  }
81799
- log39.error("Self-respawn launch failed after binary resolution succeeded");
81996
+ log40.error("Self-respawn launch failed after binary resolution succeeded");
81800
81997
  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(() => {});
81801
81998
  } else {
81802
- log39.error("claude-threads not found on PATH; manual restart required");
81999
+ log40.error("claude-threads not found on PATH; manual restart required");
81803
82000
  }
81804
82001
  process.exit(0);
81805
82002
  }
81806
- log39.debug(`Restart handled by supervisor: ${decision.supervisor}`);
82003
+ log40.debug(`Restart handled by supervisor: ${decision.supervisor}`);
81807
82004
  process.exit(RESTART_EXIT_CODE);
81808
82005
  } else {
81809
82006
  const errorMsg = result.error ?? "Unknown error";