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