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