claude-threads 1.17.3 → 1.18.1

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