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