claude-threads 1.0.2 → 1.0.4
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 +17 -0
- package/dist/index.js +643 -623
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14758,7 +14758,7 @@ var require_react_reconciler_development = __commonJS((exports, module) => {
|
|
|
14758
14758
|
return hook.checkDCE ? true : false;
|
|
14759
14759
|
}
|
|
14760
14760
|
function setIsStrictModeForDevtools(newIsStrictMode) {
|
|
14761
|
-
typeof
|
|
14761
|
+
typeof log29 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
|
|
14762
14762
|
if (injectedHook && typeof injectedHook.setStrictMode === "function")
|
|
14763
14763
|
try {
|
|
14764
14764
|
injectedHook.setStrictMode(rendererID, newIsStrictMode);
|
|
@@ -22842,7 +22842,7 @@ Check the render method of %s.`, getComponentNameFromFiber(current) || "Unknown"
|
|
|
22842
22842
|
var fiberStack = [];
|
|
22843
22843
|
var index$jscomp$0 = -1, emptyContextObject = {};
|
|
22844
22844
|
Object.freeze(emptyContextObject);
|
|
22845
|
-
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,
|
|
22845
|
+
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, log29 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
|
|
22846
22846
|
if (typeof performance === "object" && typeof performance.now === "function") {
|
|
22847
22847
|
var localPerformance = performance;
|
|
22848
22848
|
var getCurrentTime = function() {
|
|
@@ -27449,12 +27449,12 @@ var require_backend = __commonJS((exports, module) => {
|
|
|
27449
27449
|
}
|
|
27450
27450
|
return (r === "string" ? String : Number)(t);
|
|
27451
27451
|
}
|
|
27452
|
-
var
|
|
27453
|
-
function
|
|
27454
|
-
_classCallCheck(this,
|
|
27452
|
+
var EventEmitter5 = /* @__PURE__ */ function() {
|
|
27453
|
+
function EventEmitter6() {
|
|
27454
|
+
_classCallCheck(this, EventEmitter6);
|
|
27455
27455
|
_defineProperty(this, "listenersMap", new Map);
|
|
27456
27456
|
}
|
|
27457
|
-
return _createClass(
|
|
27457
|
+
return _createClass(EventEmitter6, [{
|
|
27458
27458
|
key: "addListener",
|
|
27459
27459
|
value: function addListener(event, listener) {
|
|
27460
27460
|
var listeners = this.listenersMap.get(event);
|
|
@@ -30882,7 +30882,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
30882
30882
|
} while (this._messageQueue.length);
|
|
30883
30883
|
}
|
|
30884
30884
|
}]);
|
|
30885
|
-
}(
|
|
30885
|
+
}(EventEmitter5);
|
|
30886
30886
|
const src_bridge = Bridge;
|
|
30887
30887
|
function storage_localStorageGetItem(key) {
|
|
30888
30888
|
try {
|
|
@@ -31736,7 +31736,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
31736
31736
|
this._bridge.send("unsupportedRendererVersion");
|
|
31737
31737
|
}
|
|
31738
31738
|
}]);
|
|
31739
|
-
}(
|
|
31739
|
+
}(EventEmitter5);
|
|
31740
31740
|
function DevToolsConsolePatching_typeof(o) {
|
|
31741
31741
|
"@babel/helpers - typeof";
|
|
31742
31742
|
return DevToolsConsolePatching_typeof = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(o2) {
|
|
@@ -46392,7 +46392,7 @@ async function setupSlackPlatform(id, existing) {
|
|
|
46392
46392
|
}
|
|
46393
46393
|
}
|
|
46394
46394
|
|
|
46395
|
-
// src/platform/
|
|
46395
|
+
// src/platform/base-client.ts
|
|
46396
46396
|
import { EventEmitter } from "events";
|
|
46397
46397
|
|
|
46398
46398
|
// src/utils/logger.ts
|
|
@@ -46468,6 +46468,114 @@ ${err.stack || err.message}` : msg;
|
|
|
46468
46468
|
var mcpLogger = createLogger("MCP", true);
|
|
46469
46469
|
var wsLogger = createLogger("ws", false);
|
|
46470
46470
|
|
|
46471
|
+
// src/platform/base-client.ts
|
|
46472
|
+
var log = createLogger("base-client");
|
|
46473
|
+
|
|
46474
|
+
class BasePlatformClient extends EventEmitter {
|
|
46475
|
+
allowedUsers = [];
|
|
46476
|
+
botName = "";
|
|
46477
|
+
isIntentionalDisconnect = false;
|
|
46478
|
+
isReconnecting = false;
|
|
46479
|
+
heartbeatInterval = null;
|
|
46480
|
+
lastMessageAt = Date.now();
|
|
46481
|
+
HEARTBEAT_INTERVAL_MS = 30000;
|
|
46482
|
+
HEARTBEAT_TIMEOUT_MS = 60000;
|
|
46483
|
+
reconnectAttempts = 0;
|
|
46484
|
+
maxReconnectAttempts = 10;
|
|
46485
|
+
reconnectDelay = 1000;
|
|
46486
|
+
isUserAllowed(username) {
|
|
46487
|
+
if (this.allowedUsers.length === 0) {
|
|
46488
|
+
return true;
|
|
46489
|
+
}
|
|
46490
|
+
return this.allowedUsers.includes(username);
|
|
46491
|
+
}
|
|
46492
|
+
getBotName() {
|
|
46493
|
+
return this.botName;
|
|
46494
|
+
}
|
|
46495
|
+
async createInteractivePost(message, reactions, threadId) {
|
|
46496
|
+
const post = await this.createPost(message, threadId);
|
|
46497
|
+
for (const emoji of reactions) {
|
|
46498
|
+
try {
|
|
46499
|
+
await this.addReaction(post.id, emoji);
|
|
46500
|
+
} catch (err) {
|
|
46501
|
+
log.warn(`Failed to add reaction ${emoji}: ${err}`);
|
|
46502
|
+
}
|
|
46503
|
+
}
|
|
46504
|
+
return post;
|
|
46505
|
+
}
|
|
46506
|
+
disconnect() {
|
|
46507
|
+
wsLogger.info("Disconnecting (intentional)");
|
|
46508
|
+
this.isIntentionalDisconnect = true;
|
|
46509
|
+
this.stopHeartbeat();
|
|
46510
|
+
this.forceCloseConnection();
|
|
46511
|
+
}
|
|
46512
|
+
prepareForReconnect() {
|
|
46513
|
+
wsLogger.debug("Preparing for reconnect (resetting intentional disconnect flag)");
|
|
46514
|
+
this.isIntentionalDisconnect = false;
|
|
46515
|
+
this.reconnectAttempts = 0;
|
|
46516
|
+
}
|
|
46517
|
+
startHeartbeat() {
|
|
46518
|
+
this.stopHeartbeat();
|
|
46519
|
+
this.lastMessageAt = Date.now();
|
|
46520
|
+
this.heartbeatInterval = setInterval(() => {
|
|
46521
|
+
const silentFor = Date.now() - this.lastMessageAt;
|
|
46522
|
+
if (silentFor > this.HEARTBEAT_TIMEOUT_MS) {
|
|
46523
|
+
log.warn(`Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
|
|
46524
|
+
this.stopHeartbeat();
|
|
46525
|
+
this.forceCloseConnection();
|
|
46526
|
+
return;
|
|
46527
|
+
}
|
|
46528
|
+
wsLogger.debug(`Heartbeat check (last activity ${Math.round(silentFor / 1000)}s ago)`);
|
|
46529
|
+
}, this.HEARTBEAT_INTERVAL_MS);
|
|
46530
|
+
}
|
|
46531
|
+
stopHeartbeat() {
|
|
46532
|
+
if (this.heartbeatInterval) {
|
|
46533
|
+
clearInterval(this.heartbeatInterval);
|
|
46534
|
+
this.heartbeatInterval = null;
|
|
46535
|
+
}
|
|
46536
|
+
}
|
|
46537
|
+
scheduleReconnect() {
|
|
46538
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
46539
|
+
log.error("Max reconnection attempts reached");
|
|
46540
|
+
return;
|
|
46541
|
+
}
|
|
46542
|
+
this.forceCloseConnection();
|
|
46543
|
+
this.isReconnecting = true;
|
|
46544
|
+
this.reconnectAttempts++;
|
|
46545
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
46546
|
+
log.info(`Reconnecting... (attempt ${this.reconnectAttempts})`);
|
|
46547
|
+
this.emit("reconnecting", this.reconnectAttempts);
|
|
46548
|
+
setTimeout(() => {
|
|
46549
|
+
this.connect().catch((err) => {
|
|
46550
|
+
log.error(`Reconnection failed: ${err}`);
|
|
46551
|
+
});
|
|
46552
|
+
}, delay);
|
|
46553
|
+
}
|
|
46554
|
+
onConnectionEstablished() {
|
|
46555
|
+
this.reconnectAttempts = 0;
|
|
46556
|
+
this.startHeartbeat();
|
|
46557
|
+
this.emit("connected");
|
|
46558
|
+
if (this.isReconnecting) {
|
|
46559
|
+
this.recoverMissedMessages().catch((err) => {
|
|
46560
|
+
log.warn(`Failed to recover missed messages: ${err}`);
|
|
46561
|
+
});
|
|
46562
|
+
}
|
|
46563
|
+
this.isReconnecting = false;
|
|
46564
|
+
}
|
|
46565
|
+
onConnectionClosed() {
|
|
46566
|
+
this.stopHeartbeat();
|
|
46567
|
+
this.emit("disconnected");
|
|
46568
|
+
if (!this.isIntentionalDisconnect) {
|
|
46569
|
+
wsLogger.debug("Scheduling reconnect...");
|
|
46570
|
+
this.scheduleReconnect();
|
|
46571
|
+
} else {
|
|
46572
|
+
wsLogger.debug("Intentional disconnect, not reconnecting");
|
|
46573
|
+
}
|
|
46574
|
+
}
|
|
46575
|
+
updateLastMessageTime() {
|
|
46576
|
+
this.lastMessageAt = Date.now();
|
|
46577
|
+
}
|
|
46578
|
+
}
|
|
46471
46579
|
// src/utils/format.ts
|
|
46472
46580
|
function extractThreadId(sessionId) {
|
|
46473
46581
|
const colonIndex = sessionId.indexOf(":");
|
|
@@ -46731,9 +46839,9 @@ ${code}
|
|
|
46731
46839
|
}
|
|
46732
46840
|
|
|
46733
46841
|
// src/platform/mattermost/client.ts
|
|
46734
|
-
var
|
|
46842
|
+
var log2 = createLogger("mattermost");
|
|
46735
46843
|
|
|
46736
|
-
class MattermostClient extends
|
|
46844
|
+
class MattermostClient extends BasePlatformClient {
|
|
46737
46845
|
platformId;
|
|
46738
46846
|
platformType = "mattermost";
|
|
46739
46847
|
displayName;
|
|
@@ -46741,21 +46849,10 @@ class MattermostClient extends EventEmitter {
|
|
|
46741
46849
|
url;
|
|
46742
46850
|
token;
|
|
46743
46851
|
channelId;
|
|
46744
|
-
botName;
|
|
46745
|
-
allowedUsers;
|
|
46746
|
-
reconnectAttempts = 0;
|
|
46747
|
-
maxReconnectAttempts = 10;
|
|
46748
|
-
reconnectDelay = 1000;
|
|
46749
46852
|
userCache = new Map;
|
|
46750
46853
|
botUserId = null;
|
|
46751
46854
|
formatter = new MattermostFormatter;
|
|
46752
|
-
heartbeatInterval = null;
|
|
46753
|
-
lastMessageAt = Date.now();
|
|
46754
|
-
HEARTBEAT_INTERVAL_MS = 30000;
|
|
46755
|
-
HEARTBEAT_TIMEOUT_MS = 60000;
|
|
46756
46855
|
lastProcessedPostId = null;
|
|
46757
|
-
isReconnecting = false;
|
|
46758
|
-
isIntentionalDisconnect = false;
|
|
46759
46856
|
constructor(platformConfig) {
|
|
46760
46857
|
super();
|
|
46761
46858
|
this.platformId = platformConfig.id;
|
|
@@ -46812,7 +46909,7 @@ class MattermostClient extends EventEmitter {
|
|
|
46812
46909
|
const hasFileIds = post.file_ids && post.file_ids.length > 0;
|
|
46813
46910
|
const hasFileMetadata = post.metadata?.files && post.metadata.files.length > 0;
|
|
46814
46911
|
if (hasFileIds && !hasFileMetadata) {
|
|
46815
|
-
|
|
46912
|
+
log2.debug(`Post ${formatShortId(post.id)} has ${post.file_ids.length} file(s), fetching metadata`);
|
|
46816
46913
|
try {
|
|
46817
46914
|
const files = [];
|
|
46818
46915
|
for (const fileId of post.file_ids) {
|
|
@@ -46820,7 +46917,7 @@ class MattermostClient extends EventEmitter {
|
|
|
46820
46917
|
const file = await this.api("GET", `/files/${fileId}/info`);
|
|
46821
46918
|
files.push(file);
|
|
46822
46919
|
} catch (err) {
|
|
46823
|
-
|
|
46920
|
+
log2.warn(`Failed to fetch file info for ${fileId}: ${err}`);
|
|
46824
46921
|
}
|
|
46825
46922
|
}
|
|
46826
46923
|
if (files.length > 0) {
|
|
@@ -46828,10 +46925,10 @@ class MattermostClient extends EventEmitter {
|
|
|
46828
46925
|
...post.metadata,
|
|
46829
46926
|
files
|
|
46830
46927
|
};
|
|
46831
|
-
|
|
46928
|
+
log2.debug(`Enriched post ${formatShortId(post.id)} with ${files.length} file(s)`);
|
|
46832
46929
|
}
|
|
46833
46930
|
} catch (err) {
|
|
46834
|
-
|
|
46931
|
+
log2.warn(`Failed to fetch file metadata for post ${formatShortId(post.id)}: ${err}`);
|
|
46835
46932
|
}
|
|
46836
46933
|
}
|
|
46837
46934
|
const user = await this.getUser(post.user_id);
|
|
@@ -46845,7 +46942,7 @@ class MattermostClient extends EventEmitter {
|
|
|
46845
46942
|
RETRY_DELAY_MS = 500;
|
|
46846
46943
|
async api(method, path, body, retryCount = 0, options) {
|
|
46847
46944
|
const url = `${this.url}/api/v4${path}`;
|
|
46848
|
-
|
|
46945
|
+
log2.debug(`API ${method} ${path}`);
|
|
46849
46946
|
const response = await fetch(url, {
|
|
46850
46947
|
method,
|
|
46851
46948
|
headers: {
|
|
@@ -46858,19 +46955,19 @@ class MattermostClient extends EventEmitter {
|
|
|
46858
46955
|
const text = await response.text();
|
|
46859
46956
|
if (response.status === 500 && retryCount < this.MAX_RETRIES) {
|
|
46860
46957
|
const delay = this.RETRY_DELAY_MS * Math.pow(2, retryCount);
|
|
46861
|
-
|
|
46958
|
+
log2.warn(`API ${method} ${path} failed with 500, retrying in ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
|
|
46862
46959
|
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
46863
46960
|
return this.api(method, path, body, retryCount + 1, options);
|
|
46864
46961
|
}
|
|
46865
46962
|
const isSilent = options?.silent?.includes(response.status);
|
|
46866
46963
|
if (isSilent) {
|
|
46867
|
-
|
|
46964
|
+
log2.debug(`API ${method} ${path} failed: ${response.status} (expected)`);
|
|
46868
46965
|
} else {
|
|
46869
|
-
|
|
46966
|
+
log2.warn(`API ${method} ${path} failed: ${response.status} ${text.substring(0, 100)}`);
|
|
46870
46967
|
}
|
|
46871
46968
|
throw new Error(`Mattermost API error ${response.status}: ${text}`);
|
|
46872
46969
|
}
|
|
46873
|
-
|
|
46970
|
+
log2.debug(`API ${method} ${path} \u2192 ${response.status}`);
|
|
46874
46971
|
return response.json();
|
|
46875
46972
|
}
|
|
46876
46973
|
async getBotUser() {
|
|
@@ -46881,28 +46978,28 @@ class MattermostClient extends EventEmitter {
|
|
|
46881
46978
|
async getUser(userId) {
|
|
46882
46979
|
const cached = this.userCache.get(userId);
|
|
46883
46980
|
if (cached) {
|
|
46884
|
-
|
|
46981
|
+
log2.debug(`User ${userId} found in cache: @${cached.username}`);
|
|
46885
46982
|
return this.normalizePlatformUser(cached);
|
|
46886
46983
|
}
|
|
46887
46984
|
try {
|
|
46888
46985
|
const user = await this.api("GET", `/users/${userId}`);
|
|
46889
46986
|
this.userCache.set(userId, user);
|
|
46890
|
-
|
|
46987
|
+
log2.debug(`User ${userId} fetched: @${user.username}`);
|
|
46891
46988
|
return this.normalizePlatformUser(user);
|
|
46892
46989
|
} catch (err) {
|
|
46893
|
-
|
|
46990
|
+
log2.warn(`Failed to get user ${userId}: ${err}`);
|
|
46894
46991
|
return null;
|
|
46895
46992
|
}
|
|
46896
46993
|
}
|
|
46897
46994
|
async getUserByUsername(username) {
|
|
46898
46995
|
try {
|
|
46899
|
-
|
|
46996
|
+
log2.debug(`Looking up user by username: @${username}`);
|
|
46900
46997
|
const user = await this.api("GET", `/users/username/${username}`);
|
|
46901
46998
|
this.userCache.set(user.id, user);
|
|
46902
|
-
|
|
46999
|
+
log2.debug(`User @${username} found: ${user.id}`);
|
|
46903
47000
|
return this.normalizePlatformUser(user);
|
|
46904
47001
|
} catch (err) {
|
|
46905
|
-
|
|
47002
|
+
log2.warn(`User @${username} not found: ${err}`);
|
|
46906
47003
|
return null;
|
|
46907
47004
|
}
|
|
46908
47005
|
}
|
|
@@ -46924,7 +47021,7 @@ class MattermostClient extends EventEmitter {
|
|
|
46924
47021
|
return this.normalizePlatformPost(post);
|
|
46925
47022
|
}
|
|
46926
47023
|
async addReaction(postId, emojiName) {
|
|
46927
|
-
|
|
47024
|
+
log2.debug(`Adding reaction :${emojiName}: to post ${postId.substring(0, 8)}`);
|
|
46928
47025
|
await this.api("POST", "/reactions", {
|
|
46929
47026
|
user_id: this.botUserId,
|
|
46930
47027
|
post_id: postId,
|
|
@@ -46932,22 +47029,11 @@ class MattermostClient extends EventEmitter {
|
|
|
46932
47029
|
});
|
|
46933
47030
|
}
|
|
46934
47031
|
async removeReaction(postId, emojiName) {
|
|
46935
|
-
|
|
47032
|
+
log2.debug(`Removing reaction :${emojiName}: from post ${postId.substring(0, 8)}`);
|
|
46936
47033
|
await this.api("DELETE", `/users/${this.botUserId}/posts/${postId}/reactions/${emojiName}`);
|
|
46937
47034
|
}
|
|
46938
|
-
async createInteractivePost(message, reactions, threadId) {
|
|
46939
|
-
const post = await this.createPost(message, threadId);
|
|
46940
|
-
for (const emoji of reactions) {
|
|
46941
|
-
try {
|
|
46942
|
-
await this.addReaction(post.id, emoji);
|
|
46943
|
-
} catch (err) {
|
|
46944
|
-
log.warn(`Failed to add reaction ${emoji}: ${err}`);
|
|
46945
|
-
}
|
|
46946
|
-
}
|
|
46947
|
-
return post;
|
|
46948
|
-
}
|
|
46949
47035
|
async downloadFile(fileId) {
|
|
46950
|
-
|
|
47036
|
+
log2.debug(`Downloading file ${fileId}`);
|
|
46951
47037
|
const url = `${this.url}/api/v4/files/${fileId}`;
|
|
46952
47038
|
const response = await fetch(url, {
|
|
46953
47039
|
headers: {
|
|
@@ -46955,11 +47041,11 @@ class MattermostClient extends EventEmitter {
|
|
|
46955
47041
|
}
|
|
46956
47042
|
});
|
|
46957
47043
|
if (!response.ok) {
|
|
46958
|
-
|
|
47044
|
+
log2.warn(`Failed to download file ${fileId}: ${response.status}`);
|
|
46959
47045
|
throw new Error(`Failed to download file ${fileId}: ${response.status}`);
|
|
46960
47046
|
}
|
|
46961
47047
|
const arrayBuffer = await response.arrayBuffer();
|
|
46962
|
-
|
|
47048
|
+
log2.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
|
|
46963
47049
|
return Buffer.from(arrayBuffer);
|
|
46964
47050
|
}
|
|
46965
47051
|
async getFileInfo(fileId) {
|
|
@@ -46968,24 +47054,24 @@ class MattermostClient extends EventEmitter {
|
|
|
46968
47054
|
}
|
|
46969
47055
|
async getPost(postId) {
|
|
46970
47056
|
try {
|
|
46971
|
-
|
|
47057
|
+
log2.debug(`Fetching post ${postId.substring(0, 8)}`);
|
|
46972
47058
|
const post = await this.api("GET", `/posts/${postId}`);
|
|
46973
47059
|
return this.normalizePlatformPost(post);
|
|
46974
47060
|
} catch (err) {
|
|
46975
|
-
|
|
47061
|
+
log2.debug(`Post ${postId.substring(0, 8)} not found: ${err}`);
|
|
46976
47062
|
return null;
|
|
46977
47063
|
}
|
|
46978
47064
|
}
|
|
46979
47065
|
async deletePost(postId) {
|
|
46980
|
-
|
|
47066
|
+
log2.debug(`Deleting post ${postId.substring(0, 8)}`);
|
|
46981
47067
|
await this.api("DELETE", `/posts/${postId}`);
|
|
46982
47068
|
}
|
|
46983
47069
|
async pinPost(postId) {
|
|
46984
|
-
|
|
47070
|
+
log2.debug(`Pinning post ${postId.substring(0, 8)}`);
|
|
46985
47071
|
await this.api("POST", `/posts/${postId}/pin`);
|
|
46986
47072
|
}
|
|
46987
47073
|
async unpinPost(postId) {
|
|
46988
|
-
|
|
47074
|
+
log2.debug(`Unpinning post ${postId.substring(0, 8)}`);
|
|
46989
47075
|
try {
|
|
46990
47076
|
await this.api("POST", `/posts/${postId}/unpin`, undefined, 0, { silent: [403, 404] });
|
|
46991
47077
|
} catch (err) {
|
|
@@ -47029,7 +47115,7 @@ class MattermostClient extends EventEmitter {
|
|
|
47029
47115
|
}
|
|
47030
47116
|
return messages;
|
|
47031
47117
|
} catch (err) {
|
|
47032
|
-
|
|
47118
|
+
log2.warn(`Failed to get thread history for ${threadId}: ${err}`);
|
|
47033
47119
|
return [];
|
|
47034
47120
|
}
|
|
47035
47121
|
}
|
|
@@ -47048,7 +47134,7 @@ class MattermostClient extends EventEmitter {
|
|
|
47048
47134
|
posts.sort((a, b) => (a.createAt ?? 0) - (b.createAt ?? 0));
|
|
47049
47135
|
return posts;
|
|
47050
47136
|
} catch (err) {
|
|
47051
|
-
|
|
47137
|
+
log2.warn(`Failed to get channel posts after ${afterPostId}: ${err}`);
|
|
47052
47138
|
return [];
|
|
47053
47139
|
}
|
|
47054
47140
|
}
|
|
@@ -47070,21 +47156,13 @@ class MattermostClient extends EventEmitter {
|
|
|
47070
47156
|
}
|
|
47071
47157
|
};
|
|
47072
47158
|
this.ws.onmessage = (event) => {
|
|
47073
|
-
this.
|
|
47159
|
+
this.updateLastMessageTime();
|
|
47074
47160
|
try {
|
|
47075
47161
|
const data = typeof event.data === "string" ? event.data : event.data.toString();
|
|
47076
47162
|
const wsEvent = JSON.parse(data);
|
|
47077
47163
|
this.handleEvent(wsEvent);
|
|
47078
47164
|
if (wsEvent.event === "hello") {
|
|
47079
|
-
this.
|
|
47080
|
-
this.startHeartbeat();
|
|
47081
|
-
this.emit("connected");
|
|
47082
|
-
if (this.isReconnecting && this.lastProcessedPostId) {
|
|
47083
|
-
this.recoverMissedMessages().catch((err) => {
|
|
47084
|
-
log.warn(`Failed to recover missed messages: ${err}`);
|
|
47085
|
-
});
|
|
47086
|
-
}
|
|
47087
|
-
this.isReconnecting = false;
|
|
47165
|
+
this.onConnectionEstablished();
|
|
47088
47166
|
resolve2();
|
|
47089
47167
|
}
|
|
47090
47168
|
} catch (err) {
|
|
@@ -47093,14 +47171,7 @@ class MattermostClient extends EventEmitter {
|
|
|
47093
47171
|
};
|
|
47094
47172
|
this.ws.onclose = (event) => {
|
|
47095
47173
|
wsLogger.info(`WebSocket disconnected (code: ${event.code}, reason: ${event.reason || "none"}, clean: ${event.wasClean})`);
|
|
47096
|
-
this.
|
|
47097
|
-
this.emit("disconnected");
|
|
47098
|
-
if (!this.isIntentionalDisconnect) {
|
|
47099
|
-
wsLogger.debug("Scheduling reconnect...");
|
|
47100
|
-
this.scheduleReconnect();
|
|
47101
|
-
} else {
|
|
47102
|
-
wsLogger.debug("Intentional disconnect, not reconnecting");
|
|
47103
|
-
}
|
|
47174
|
+
this.onConnectionClosed();
|
|
47104
47175
|
};
|
|
47105
47176
|
this.ws.onerror = (event) => {
|
|
47106
47177
|
wsLogger.warn(`WebSocket error: ${event}`);
|
|
@@ -47158,55 +47229,31 @@ class MattermostClient extends EventEmitter {
|
|
|
47158
47229
|
}
|
|
47159
47230
|
}
|
|
47160
47231
|
}
|
|
47161
|
-
|
|
47162
|
-
if (this.
|
|
47163
|
-
|
|
47164
|
-
|
|
47165
|
-
|
|
47166
|
-
|
|
47167
|
-
|
|
47168
|
-
|
|
47169
|
-
log.info(`Reconnecting... (attempt ${this.reconnectAttempts})`);
|
|
47170
|
-
this.emit("reconnecting", this.reconnectAttempts);
|
|
47171
|
-
setTimeout(() => {
|
|
47172
|
-
this.connect().catch((err) => {
|
|
47173
|
-
log.error(`Reconnection failed: ${err}`);
|
|
47174
|
-
});
|
|
47175
|
-
}, delay);
|
|
47176
|
-
}
|
|
47177
|
-
startHeartbeat() {
|
|
47178
|
-
this.stopHeartbeat();
|
|
47179
|
-
this.lastMessageAt = Date.now();
|
|
47180
|
-
this.heartbeatInterval = setInterval(() => {
|
|
47181
|
-
const silentFor = Date.now() - this.lastMessageAt;
|
|
47182
|
-
if (silentFor > this.HEARTBEAT_TIMEOUT_MS) {
|
|
47183
|
-
log.warn(`Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
|
|
47184
|
-
this.stopHeartbeat();
|
|
47185
|
-
if (this.ws) {
|
|
47232
|
+
forceCloseConnection() {
|
|
47233
|
+
if (this.ws) {
|
|
47234
|
+
this.ws.onopen = null;
|
|
47235
|
+
this.ws.onmessage = null;
|
|
47236
|
+
this.ws.onclose = null;
|
|
47237
|
+
this.ws.onerror = null;
|
|
47238
|
+
if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
|
|
47239
|
+
try {
|
|
47186
47240
|
this.ws.close();
|
|
47187
|
-
}
|
|
47188
|
-
return;
|
|
47241
|
+
} catch {}
|
|
47189
47242
|
}
|
|
47190
|
-
|
|
47191
|
-
}, this.HEARTBEAT_INTERVAL_MS);
|
|
47192
|
-
}
|
|
47193
|
-
stopHeartbeat() {
|
|
47194
|
-
if (this.heartbeatInterval) {
|
|
47195
|
-
clearInterval(this.heartbeatInterval);
|
|
47196
|
-
this.heartbeatInterval = null;
|
|
47243
|
+
this.ws = null;
|
|
47197
47244
|
}
|
|
47198
47245
|
}
|
|
47199
47246
|
async recoverMissedMessages() {
|
|
47200
47247
|
if (!this.lastProcessedPostId) {
|
|
47201
47248
|
return;
|
|
47202
47249
|
}
|
|
47203
|
-
|
|
47250
|
+
log2.info(`Recovering missed messages after post ${this.lastProcessedPostId}...`);
|
|
47204
47251
|
const missedPosts = await this.getChannelPostsAfter(this.lastProcessedPostId);
|
|
47205
47252
|
if (missedPosts.length === 0) {
|
|
47206
|
-
|
|
47253
|
+
log2.info("No missed messages to recover");
|
|
47207
47254
|
return;
|
|
47208
47255
|
}
|
|
47209
|
-
|
|
47256
|
+
log2.info(`Recovered ${missedPosts.length} missed message(s)`);
|
|
47210
47257
|
for (const post of missedPosts) {
|
|
47211
47258
|
this.lastProcessedPostId = post.id;
|
|
47212
47259
|
const user = await this.getUser(post.userId);
|
|
@@ -47216,12 +47263,6 @@ class MattermostClient extends EventEmitter {
|
|
|
47216
47263
|
}
|
|
47217
47264
|
}
|
|
47218
47265
|
}
|
|
47219
|
-
isUserAllowed(username) {
|
|
47220
|
-
if (this.allowedUsers.length === 0) {
|
|
47221
|
-
return true;
|
|
47222
|
-
}
|
|
47223
|
-
return this.allowedUsers.includes(username);
|
|
47224
|
-
}
|
|
47225
47266
|
isBotMentioned(message) {
|
|
47226
47267
|
const botName = escapeRegExp(this.botName);
|
|
47227
47268
|
const mentionPattern = new RegExp(`(^|\\s)@${botName}\\b`, "i");
|
|
@@ -47231,9 +47272,6 @@ class MattermostClient extends EventEmitter {
|
|
|
47231
47272
|
const botName = escapeRegExp(this.botName);
|
|
47232
47273
|
return message.replace(new RegExp(`(^|\\s)@${botName}\\b`, "gi"), " ").trim();
|
|
47233
47274
|
}
|
|
47234
|
-
getBotName() {
|
|
47235
|
-
return this.botName;
|
|
47236
|
-
}
|
|
47237
47275
|
getMcpConfig() {
|
|
47238
47276
|
return {
|
|
47239
47277
|
type: "mattermost",
|
|
@@ -47264,24 +47302,7 @@ class MattermostClient extends EventEmitter {
|
|
|
47264
47302
|
}
|
|
47265
47303
|
}));
|
|
47266
47304
|
}
|
|
47267
|
-
disconnect() {
|
|
47268
|
-
wsLogger.info("Disconnecting WebSocket (intentional)");
|
|
47269
|
-
this.isIntentionalDisconnect = true;
|
|
47270
|
-
this.stopHeartbeat();
|
|
47271
|
-
if (this.ws) {
|
|
47272
|
-
this.ws.close();
|
|
47273
|
-
this.ws = null;
|
|
47274
|
-
}
|
|
47275
|
-
}
|
|
47276
|
-
prepareForReconnect() {
|
|
47277
|
-
wsLogger.debug("Preparing for reconnect (resetting intentional disconnect flag)");
|
|
47278
|
-
this.isIntentionalDisconnect = false;
|
|
47279
|
-
this.reconnectAttempts = 0;
|
|
47280
|
-
}
|
|
47281
47305
|
}
|
|
47282
|
-
// src/platform/slack/client.ts
|
|
47283
|
-
import { EventEmitter as EventEmitter2 } from "events";
|
|
47284
|
-
|
|
47285
47306
|
// src/platform/slack/formatter.ts
|
|
47286
47307
|
class SlackFormatter {
|
|
47287
47308
|
formatBold(text) {
|
|
@@ -47352,9 +47373,9 @@ ${code}
|
|
|
47352
47373
|
}
|
|
47353
47374
|
|
|
47354
47375
|
// src/platform/slack/client.ts
|
|
47355
|
-
var
|
|
47376
|
+
var log3 = createLogger("slack");
|
|
47356
47377
|
|
|
47357
|
-
class SlackClient extends
|
|
47378
|
+
class SlackClient extends BasePlatformClient {
|
|
47358
47379
|
platformId;
|
|
47359
47380
|
platformType = "slack";
|
|
47360
47381
|
displayName;
|
|
@@ -47362,25 +47383,14 @@ class SlackClient extends EventEmitter2 {
|
|
|
47362
47383
|
botToken;
|
|
47363
47384
|
appToken;
|
|
47364
47385
|
channelId;
|
|
47365
|
-
botName;
|
|
47366
|
-
allowedUsers;
|
|
47367
47386
|
skipPermissions;
|
|
47368
47387
|
apiUrl;
|
|
47369
|
-
reconnectAttempts = 0;
|
|
47370
|
-
maxReconnectAttempts = 10;
|
|
47371
|
-
reconnectDelay = 1000;
|
|
47372
|
-
isIntentionalDisconnect = false;
|
|
47373
|
-
isReconnecting = false;
|
|
47374
47388
|
reconnectTimeout = null;
|
|
47375
47389
|
userCache = new Map;
|
|
47376
47390
|
usernameToIdCache = new Map;
|
|
47377
47391
|
botUserId = null;
|
|
47378
47392
|
botUser = null;
|
|
47379
47393
|
teamUrl = null;
|
|
47380
|
-
heartbeatInterval = null;
|
|
47381
|
-
lastMessageAt = Date.now();
|
|
47382
|
-
HEARTBEAT_INTERVAL_MS = 30000;
|
|
47383
|
-
HEARTBEAT_TIMEOUT_MS = 60000;
|
|
47384
47394
|
lastProcessedTs = null;
|
|
47385
47395
|
processedMessages = new Set;
|
|
47386
47396
|
MAX_PROCESSED_MESSAGES = 1000;
|
|
@@ -47437,13 +47447,13 @@ class SlackClient extends EventEmitter2 {
|
|
|
47437
47447
|
const now = Date.now();
|
|
47438
47448
|
if (now < this.rateLimitRetryAfter) {
|
|
47439
47449
|
const waitTime = this.rateLimitRetryAfter - now;
|
|
47440
|
-
|
|
47450
|
+
log3.debug(`Rate limited, waiting ${waitTime}ms`);
|
|
47441
47451
|
await new Promise((resolve2) => setTimeout(resolve2, waitTime));
|
|
47442
47452
|
}
|
|
47443
47453
|
this.rateLimitDelay = 0;
|
|
47444
47454
|
}
|
|
47445
47455
|
const url = `${this.apiUrl}/${endpoint}`;
|
|
47446
|
-
|
|
47456
|
+
log3.debug(`API ${method} ${endpoint}`);
|
|
47447
47457
|
const headers = {
|
|
47448
47458
|
Authorization: `Bearer ${this.botToken}`,
|
|
47449
47459
|
"Content-Type": "application/json; charset=utf-8"
|
|
@@ -47455,25 +47465,25 @@ class SlackClient extends EventEmitter2 {
|
|
|
47455
47465
|
});
|
|
47456
47466
|
if (response.status === 429) {
|
|
47457
47467
|
if (retryCount >= this.MAX_RATE_LIMIT_RETRIES) {
|
|
47458
|
-
|
|
47468
|
+
log3.error(`Rate limit max retries (${this.MAX_RATE_LIMIT_RETRIES}) exceeded for ${endpoint}`);
|
|
47459
47469
|
throw new Error(`Slack API rate limit exceeded after ${this.MAX_RATE_LIMIT_RETRIES} retries`);
|
|
47460
47470
|
}
|
|
47461
47471
|
const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
|
|
47462
47472
|
this.rateLimitDelay = retryAfter * 1000;
|
|
47463
47473
|
this.rateLimitRetryAfter = Date.now() + this.rateLimitDelay;
|
|
47464
|
-
|
|
47474
|
+
log3.warn(`Rate limited by Slack, retrying after ${retryAfter}s (attempt ${retryCount + 1}/${this.MAX_RATE_LIMIT_RETRIES})`);
|
|
47465
47475
|
await new Promise((resolve2) => setTimeout(resolve2, this.rateLimitDelay));
|
|
47466
47476
|
return this.api(method, endpoint, body, retryCount + 1);
|
|
47467
47477
|
}
|
|
47468
47478
|
if (!response.ok) {
|
|
47469
47479
|
const text = await response.text();
|
|
47470
|
-
|
|
47480
|
+
log3.warn(`API ${method} ${endpoint} failed: ${response.status} ${text.substring(0, 100)}`);
|
|
47471
47481
|
throw new Error(`Slack API error ${response.status}: ${text}`);
|
|
47472
47482
|
}
|
|
47473
47483
|
const data = await response.json();
|
|
47474
47484
|
if (!data.ok) {
|
|
47475
47485
|
if (!expectedErrors.includes(data.error || "")) {
|
|
47476
|
-
|
|
47486
|
+
log3.warn(`API ${method} ${endpoint} error: ${data.error}`);
|
|
47477
47487
|
}
|
|
47478
47488
|
throw new Error(`Slack API error: ${data.error}`);
|
|
47479
47489
|
}
|
|
@@ -47481,7 +47491,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
47481
47491
|
}
|
|
47482
47492
|
async appApi(method, endpoint, body) {
|
|
47483
47493
|
const url = `${this.apiUrl}/${endpoint}`;
|
|
47484
|
-
|
|
47494
|
+
log3.debug(`App API ${method} ${endpoint}`);
|
|
47485
47495
|
const headers = {
|
|
47486
47496
|
Authorization: `Bearer ${this.appToken}`,
|
|
47487
47497
|
"Content-Type": "application/json; charset=utf-8"
|
|
@@ -47534,22 +47544,19 @@ class SlackClient extends EventEmitter2 {
|
|
|
47534
47544
|
wsLogger.info("Socket Mode: WebSocket connected, waiting for hello...");
|
|
47535
47545
|
};
|
|
47536
47546
|
this.ws.onmessage = (event) => {
|
|
47537
|
-
this.
|
|
47547
|
+
this.updateLastMessageTime();
|
|
47538
47548
|
try {
|
|
47539
47549
|
const data = typeof event.data === "string" ? event.data : event.data.toString();
|
|
47540
47550
|
const envelope = JSON.parse(data);
|
|
47541
47551
|
this.handleSocketModeEvent(envelope);
|
|
47542
47552
|
if (envelope.type === "hello") {
|
|
47543
47553
|
clearTimeout(connectionTimeout);
|
|
47544
|
-
this.
|
|
47545
|
-
this.startHeartbeat();
|
|
47546
|
-
this.emit("connected");
|
|
47554
|
+
this.onConnectionEstablished();
|
|
47547
47555
|
if (this.isReconnecting && this.lastProcessedTs) {
|
|
47548
47556
|
this.recoverMissedMessages().catch((err) => {
|
|
47549
|
-
|
|
47557
|
+
log3.warn(`Failed to recover missed messages: ${err}`);
|
|
47550
47558
|
});
|
|
47551
47559
|
}
|
|
47552
|
-
this.isReconnecting = false;
|
|
47553
47560
|
doResolve();
|
|
47554
47561
|
}
|
|
47555
47562
|
} catch (err) {
|
|
@@ -47559,8 +47566,6 @@ class SlackClient extends EventEmitter2 {
|
|
|
47559
47566
|
this.ws.onclose = (event) => {
|
|
47560
47567
|
clearTimeout(connectionTimeout);
|
|
47561
47568
|
wsLogger.info(`Socket Mode: WebSocket disconnected (code: ${event.code}, reason: ${event.reason || "none"}, clean: ${event.wasClean})`);
|
|
47562
|
-
this.stopHeartbeat();
|
|
47563
|
-
this.emit("disconnected");
|
|
47564
47569
|
if (!settled) {
|
|
47565
47570
|
wsLogger.warn(`WebSocket closed before hello event (code: ${event.code}, reason: ${event.reason || "none"})`);
|
|
47566
47571
|
}
|
|
@@ -47568,9 +47573,10 @@ class SlackClient extends EventEmitter2 {
|
|
|
47568
47573
|
const serverShutdown = event.reason?.toLowerCase().includes("server shutting down");
|
|
47569
47574
|
const connectionReplaced = event.reason?.toLowerCase().includes("new connection replacing");
|
|
47570
47575
|
if (!this.isIntentionalDisconnect && !serverShutdown && !connectionReplaced) {
|
|
47571
|
-
|
|
47572
|
-
this.scheduleReconnect();
|
|
47576
|
+
this.onConnectionClosed();
|
|
47573
47577
|
} else {
|
|
47578
|
+
this.stopHeartbeat();
|
|
47579
|
+
this.emit("disconnected");
|
|
47574
47580
|
if (serverShutdown) {
|
|
47575
47581
|
wsLogger.debug("Server shutdown detected, not reconnecting");
|
|
47576
47582
|
} else if (connectionReplaced) {
|
|
@@ -47644,7 +47650,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
47644
47650
|
this.emit("channel_post", post, user);
|
|
47645
47651
|
}
|
|
47646
47652
|
}).catch((err) => {
|
|
47647
|
-
|
|
47653
|
+
log3.warn(`Failed to get user for message event: ${err}`);
|
|
47648
47654
|
this.emit("message", post, null);
|
|
47649
47655
|
});
|
|
47650
47656
|
}
|
|
@@ -47664,7 +47670,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
47664
47670
|
this.getUser(event.user || "").then((user) => {
|
|
47665
47671
|
this.emit("reaction", reaction, user);
|
|
47666
47672
|
}).catch((err) => {
|
|
47667
|
-
|
|
47673
|
+
log3.warn(`Failed to get user for reaction event: ${err}`);
|
|
47668
47674
|
this.emit("reaction", reaction, null);
|
|
47669
47675
|
});
|
|
47670
47676
|
}
|
|
@@ -47684,24 +47690,39 @@ class SlackClient extends EventEmitter2 {
|
|
|
47684
47690
|
this.getUser(event.user || "").then((user) => {
|
|
47685
47691
|
this.emit("reaction_removed", reaction, user);
|
|
47686
47692
|
}).catch((err) => {
|
|
47687
|
-
|
|
47693
|
+
log3.warn(`Failed to get user for reaction_removed event: ${err}`);
|
|
47688
47694
|
this.emit("reaction_removed", reaction, null);
|
|
47689
47695
|
});
|
|
47690
47696
|
}
|
|
47691
47697
|
}
|
|
47692
|
-
|
|
47693
|
-
if (this.
|
|
47694
|
-
|
|
47695
|
-
|
|
47698
|
+
forceCloseConnection() {
|
|
47699
|
+
if (this.ws) {
|
|
47700
|
+
this.ws.onopen = null;
|
|
47701
|
+
this.ws.onmessage = null;
|
|
47702
|
+
this.ws.onclose = null;
|
|
47703
|
+
this.ws.onerror = null;
|
|
47704
|
+
if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
|
|
47705
|
+
try {
|
|
47706
|
+
this.ws.close();
|
|
47707
|
+
} catch {}
|
|
47708
|
+
}
|
|
47709
|
+
this.ws = null;
|
|
47696
47710
|
}
|
|
47711
|
+
}
|
|
47712
|
+
scheduleReconnect() {
|
|
47697
47713
|
if (this.reconnectTimeout) {
|
|
47698
47714
|
clearTimeout(this.reconnectTimeout);
|
|
47699
47715
|
this.reconnectTimeout = null;
|
|
47700
47716
|
}
|
|
47717
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
47718
|
+
log3.error("Max reconnection attempts reached");
|
|
47719
|
+
return;
|
|
47720
|
+
}
|
|
47721
|
+
this.forceCloseConnection();
|
|
47701
47722
|
this.isReconnecting = true;
|
|
47702
47723
|
this.reconnectAttempts++;
|
|
47703
47724
|
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
47704
|
-
|
|
47725
|
+
wsLogger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
47705
47726
|
this.emit("reconnecting", this.reconnectAttempts);
|
|
47706
47727
|
this.reconnectTimeout = setTimeout(() => {
|
|
47707
47728
|
this.reconnectTimeout = null;
|
|
@@ -47710,45 +47731,24 @@ class SlackClient extends EventEmitter2 {
|
|
|
47710
47731
|
return;
|
|
47711
47732
|
}
|
|
47712
47733
|
this.connect().catch((err) => {
|
|
47713
|
-
|
|
47734
|
+
wsLogger.error(`Reconnection failed: ${err}`);
|
|
47735
|
+
this.scheduleReconnect();
|
|
47714
47736
|
});
|
|
47715
47737
|
}, delay);
|
|
47716
47738
|
}
|
|
47717
|
-
startHeartbeat() {
|
|
47718
|
-
this.stopHeartbeat();
|
|
47719
|
-
this.lastMessageAt = Date.now();
|
|
47720
|
-
this.heartbeatInterval = setInterval(() => {
|
|
47721
|
-
const silentFor = Date.now() - this.lastMessageAt;
|
|
47722
|
-
if (silentFor > this.HEARTBEAT_TIMEOUT_MS) {
|
|
47723
|
-
log2.warn(`Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
|
|
47724
|
-
this.stopHeartbeat();
|
|
47725
|
-
if (this.ws) {
|
|
47726
|
-
this.ws.close();
|
|
47727
|
-
}
|
|
47728
|
-
return;
|
|
47729
|
-
}
|
|
47730
|
-
wsLogger.debug(`Heartbeat check (last activity ${Math.round(silentFor / 1000)}s ago)`);
|
|
47731
|
-
}, this.HEARTBEAT_INTERVAL_MS);
|
|
47732
|
-
}
|
|
47733
|
-
stopHeartbeat() {
|
|
47734
|
-
if (this.heartbeatInterval) {
|
|
47735
|
-
clearInterval(this.heartbeatInterval);
|
|
47736
|
-
this.heartbeatInterval = null;
|
|
47737
|
-
}
|
|
47738
|
-
}
|
|
47739
47739
|
async recoverMissedMessages() {
|
|
47740
47740
|
if (!this.lastProcessedTs) {
|
|
47741
47741
|
return;
|
|
47742
47742
|
}
|
|
47743
|
-
|
|
47743
|
+
log3.info(`Recovering missed messages after ts ${this.lastProcessedTs}...`);
|
|
47744
47744
|
try {
|
|
47745
47745
|
const response = await this.api("GET", `conversations.history?channel=${this.channelId}&oldest=${this.lastProcessedTs}&inclusive=false&limit=100`);
|
|
47746
47746
|
const messages = response.messages || [];
|
|
47747
47747
|
if (messages.length === 0) {
|
|
47748
|
-
|
|
47748
|
+
log3.info("No missed messages to recover");
|
|
47749
47749
|
return;
|
|
47750
47750
|
}
|
|
47751
|
-
|
|
47751
|
+
log3.info(`Recovered ${messages.length} missed message(s)`);
|
|
47752
47752
|
const sortedMessages = messages.sort((a, b) => parseFloat(a.ts) - parseFloat(b.ts));
|
|
47753
47753
|
for (const message of sortedMessages) {
|
|
47754
47754
|
if (message.user === this.botUserId || message.bot_id) {
|
|
@@ -47763,7 +47763,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
47763
47763
|
}
|
|
47764
47764
|
}
|
|
47765
47765
|
} catch (err) {
|
|
47766
|
-
|
|
47766
|
+
log3.warn(`Failed to recover missed messages: ${err}`);
|
|
47767
47767
|
}
|
|
47768
47768
|
}
|
|
47769
47769
|
disconnect() {
|
|
@@ -47774,15 +47774,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
47774
47774
|
clearTimeout(this.reconnectTimeout);
|
|
47775
47775
|
this.reconnectTimeout = null;
|
|
47776
47776
|
}
|
|
47777
|
-
|
|
47778
|
-
this.ws.close();
|
|
47779
|
-
this.ws = null;
|
|
47780
|
-
}
|
|
47781
|
-
}
|
|
47782
|
-
prepareForReconnect() {
|
|
47783
|
-
wsLogger.debug("Preparing for reconnect (resetting intentional disconnect flag)");
|
|
47784
|
-
this.isIntentionalDisconnect = false;
|
|
47785
|
-
this.reconnectAttempts = 0;
|
|
47777
|
+
this.forceCloseConnection();
|
|
47786
47778
|
}
|
|
47787
47779
|
async fetchBotUser() {
|
|
47788
47780
|
const response = await this.api("POST", "auth.test");
|
|
@@ -47805,17 +47797,17 @@ class SlackClient extends EventEmitter2 {
|
|
|
47805
47797
|
}
|
|
47806
47798
|
const cached = this.userCache.get(userId);
|
|
47807
47799
|
if (cached) {
|
|
47808
|
-
|
|
47800
|
+
log3.debug(`User ${userId} found in cache: @${cached.name}`);
|
|
47809
47801
|
return this.normalizePlatformUser(cached);
|
|
47810
47802
|
}
|
|
47811
47803
|
try {
|
|
47812
47804
|
const response = await this.api("GET", `users.info?user=${userId}`);
|
|
47813
47805
|
this.userCache.set(userId, response.user);
|
|
47814
47806
|
this.usernameToIdCache.set(response.user.name, userId);
|
|
47815
|
-
|
|
47807
|
+
log3.debug(`User ${userId} fetched: @${response.user.name}`);
|
|
47816
47808
|
return this.normalizePlatformUser(response.user);
|
|
47817
47809
|
} catch (err) {
|
|
47818
|
-
|
|
47810
|
+
log3.warn(`Failed to get user ${userId}: ${err}`);
|
|
47819
47811
|
return null;
|
|
47820
47812
|
}
|
|
47821
47813
|
}
|
|
@@ -47825,7 +47817,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
47825
47817
|
return this.getUser(cachedId);
|
|
47826
47818
|
}
|
|
47827
47819
|
try {
|
|
47828
|
-
|
|
47820
|
+
log3.debug(`Looking up user by username: @${username}`);
|
|
47829
47821
|
let cursor;
|
|
47830
47822
|
do {
|
|
47831
47823
|
const params = cursor ? `cursor=${cursor}&limit=200` : "limit=200";
|
|
@@ -47834,28 +47826,19 @@ class SlackClient extends EventEmitter2 {
|
|
|
47834
47826
|
this.userCache.set(user.id, user);
|
|
47835
47827
|
this.usernameToIdCache.set(user.name, user.id);
|
|
47836
47828
|
if (user.name === username) {
|
|
47837
|
-
|
|
47829
|
+
log3.debug(`User @${username} found: ${user.id}`);
|
|
47838
47830
|
return this.normalizePlatformUser(user);
|
|
47839
47831
|
}
|
|
47840
47832
|
}
|
|
47841
47833
|
cursor = response.response_metadata?.next_cursor;
|
|
47842
47834
|
} while (cursor);
|
|
47843
|
-
|
|
47835
|
+
log3.warn(`User @${username} not found`);
|
|
47844
47836
|
return null;
|
|
47845
47837
|
} catch (err) {
|
|
47846
|
-
|
|
47838
|
+
log3.warn(`Failed to lookup user @${username}: ${err}`);
|
|
47847
47839
|
return null;
|
|
47848
47840
|
}
|
|
47849
47841
|
}
|
|
47850
|
-
isUserAllowed(username) {
|
|
47851
|
-
if (this.allowedUsers.length === 0) {
|
|
47852
|
-
return true;
|
|
47853
|
-
}
|
|
47854
|
-
return this.allowedUsers.includes(username);
|
|
47855
|
-
}
|
|
47856
|
-
getBotName() {
|
|
47857
|
-
return this.botName;
|
|
47858
|
-
}
|
|
47859
47842
|
getMcpConfig() {
|
|
47860
47843
|
return {
|
|
47861
47844
|
type: "slack",
|
|
@@ -47919,17 +47902,6 @@ class SlackClient extends EventEmitter2 {
|
|
|
47919
47902
|
createAt: Math.floor(parseFloat(response.ts) * 1000)
|
|
47920
47903
|
};
|
|
47921
47904
|
}
|
|
47922
|
-
async createInteractivePost(message, reactions, threadId) {
|
|
47923
|
-
const post = await this.createPost(message, threadId, { unfurl: false });
|
|
47924
|
-
for (const emoji of reactions) {
|
|
47925
|
-
try {
|
|
47926
|
-
await this.addReaction(post.id, emoji);
|
|
47927
|
-
} catch (err) {
|
|
47928
|
-
log2.warn(`Failed to add reaction ${emoji}: ${err}`);
|
|
47929
|
-
}
|
|
47930
|
-
}
|
|
47931
|
-
return post;
|
|
47932
|
-
}
|
|
47933
47905
|
async getPost(postId) {
|
|
47934
47906
|
try {
|
|
47935
47907
|
const response = await this.api("GET", `conversations.history?channel=${this.channelId}&latest=${postId}&oldest=${postId}&inclusive=true&limit=1`);
|
|
@@ -47938,19 +47910,19 @@ class SlackClient extends EventEmitter2 {
|
|
|
47938
47910
|
}
|
|
47939
47911
|
return null;
|
|
47940
47912
|
} catch (err) {
|
|
47941
|
-
|
|
47913
|
+
log3.debug(`Post ${postId.substring(0, 12)} not found: ${err}`);
|
|
47942
47914
|
return null;
|
|
47943
47915
|
}
|
|
47944
47916
|
}
|
|
47945
47917
|
async deletePost(postId) {
|
|
47946
|
-
|
|
47918
|
+
log3.debug(`Deleting post ${postId.substring(0, 12)}`);
|
|
47947
47919
|
await this.api("POST", "chat.delete", {
|
|
47948
47920
|
channel: this.channelId,
|
|
47949
47921
|
ts: postId
|
|
47950
47922
|
});
|
|
47951
47923
|
}
|
|
47952
47924
|
async pinPost(postId) {
|
|
47953
|
-
|
|
47925
|
+
log3.debug(`Pinning post ${postId.substring(0, 12)}`);
|
|
47954
47926
|
try {
|
|
47955
47927
|
await this.api("POST", "pins.add", {
|
|
47956
47928
|
channel: this.channelId,
|
|
@@ -47958,14 +47930,14 @@ class SlackClient extends EventEmitter2 {
|
|
|
47958
47930
|
}, 0, ["already_pinned"]);
|
|
47959
47931
|
} catch (err) {
|
|
47960
47932
|
if (err instanceof Error && err.message.includes("already_pinned")) {
|
|
47961
|
-
|
|
47933
|
+
log3.debug(`Post ${postId.substring(0, 12)} already pinned`);
|
|
47962
47934
|
return;
|
|
47963
47935
|
}
|
|
47964
47936
|
throw err;
|
|
47965
47937
|
}
|
|
47966
47938
|
}
|
|
47967
47939
|
async unpinPost(postId) {
|
|
47968
|
-
|
|
47940
|
+
log3.debug(`Unpinning post ${postId.substring(0, 12)}`);
|
|
47969
47941
|
try {
|
|
47970
47942
|
await this.api("POST", "pins.remove", {
|
|
47971
47943
|
channel: this.channelId,
|
|
@@ -47973,7 +47945,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
47973
47945
|
}, 0, ["no_pin"]);
|
|
47974
47946
|
} catch (err) {
|
|
47975
47947
|
if (err instanceof Error && err.message.includes("no_pin")) {
|
|
47976
|
-
|
|
47948
|
+
log3.debug(`Post ${postId.substring(0, 12)} was not pinned`);
|
|
47977
47949
|
return;
|
|
47978
47950
|
}
|
|
47979
47951
|
throw err;
|
|
@@ -47991,7 +47963,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
47991
47963
|
if (message.length <= maxLength) {
|
|
47992
47964
|
return message;
|
|
47993
47965
|
}
|
|
47994
|
-
|
|
47966
|
+
log3.warn(`Truncating message from ${message.length} to ~${maxLength} chars`);
|
|
47995
47967
|
return truncateMessageSafely(message, maxLength, "_... (truncated)_");
|
|
47996
47968
|
}
|
|
47997
47969
|
async getThreadHistory(threadId, options) {
|
|
@@ -48016,13 +47988,13 @@ class SlackClient extends EventEmitter2 {
|
|
|
48016
47988
|
messages.sort((a, b) => a.createAt - b.createAt);
|
|
48017
47989
|
return messages;
|
|
48018
47990
|
} catch (err) {
|
|
48019
|
-
|
|
47991
|
+
log3.warn(`Failed to get thread history for ${threadId}: ${err}`);
|
|
48020
47992
|
return [];
|
|
48021
47993
|
}
|
|
48022
47994
|
}
|
|
48023
47995
|
async addReaction(postId, emojiName) {
|
|
48024
47996
|
const name = getEmojiName(emojiName);
|
|
48025
|
-
|
|
47997
|
+
log3.debug(`Adding reaction :${name}: to post ${postId.substring(0, 12)}`);
|
|
48026
47998
|
await this.api("POST", "reactions.add", {
|
|
48027
47999
|
channel: this.channelId,
|
|
48028
48000
|
timestamp: postId,
|
|
@@ -48031,7 +48003,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
48031
48003
|
}
|
|
48032
48004
|
async removeReaction(postId, emojiName) {
|
|
48033
48005
|
const name = getEmojiName(emojiName);
|
|
48034
|
-
|
|
48006
|
+
log3.debug(`Removing reaction :${name}: from post ${postId.substring(0, 12)}`);
|
|
48035
48007
|
await this.api("POST", "reactions.remove", {
|
|
48036
48008
|
channel: this.channelId,
|
|
48037
48009
|
timestamp: postId,
|
|
@@ -48057,7 +48029,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
48057
48029
|
}
|
|
48058
48030
|
sendTyping(_threadId) {}
|
|
48059
48031
|
async downloadFile(fileId) {
|
|
48060
|
-
|
|
48032
|
+
log3.debug(`Downloading file ${fileId}`);
|
|
48061
48033
|
const fileInfo = await this.api("GET", `files.info?file=${fileId}`);
|
|
48062
48034
|
const downloadUrl = fileInfo.file.url_private_download || fileInfo.file.url_private;
|
|
48063
48035
|
if (!downloadUrl) {
|
|
@@ -48069,11 +48041,11 @@ class SlackClient extends EventEmitter2 {
|
|
|
48069
48041
|
}
|
|
48070
48042
|
});
|
|
48071
48043
|
if (!response.ok) {
|
|
48072
|
-
|
|
48044
|
+
log3.warn(`Failed to download file ${fileId}: ${response.status}`);
|
|
48073
48045
|
throw new Error(`Failed to download file ${fileId}: ${response.status}`);
|
|
48074
48046
|
}
|
|
48075
48047
|
const arrayBuffer = await response.arrayBuffer();
|
|
48076
|
-
|
|
48048
|
+
log3.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
|
|
48077
48049
|
return Buffer.from(arrayBuffer);
|
|
48078
48050
|
}
|
|
48079
48051
|
async getFileInfo(fileId) {
|
|
@@ -48082,10 +48054,10 @@ class SlackClient extends EventEmitter2 {
|
|
|
48082
48054
|
}
|
|
48083
48055
|
}
|
|
48084
48056
|
// src/mattermost/api.ts
|
|
48085
|
-
var
|
|
48057
|
+
var log4 = createLogger("mm-api");
|
|
48086
48058
|
async function mattermostApi(config, method, path, body) {
|
|
48087
48059
|
const url = `${config.url}/api/v4${path}`;
|
|
48088
|
-
|
|
48060
|
+
log4.debug(`API ${method} ${path}`);
|
|
48089
48061
|
const response = await fetch(url, {
|
|
48090
48062
|
method,
|
|
48091
48063
|
headers: {
|
|
@@ -48096,10 +48068,10 @@ async function mattermostApi(config, method, path, body) {
|
|
|
48096
48068
|
});
|
|
48097
48069
|
if (!response.ok) {
|
|
48098
48070
|
const text = await response.text();
|
|
48099
|
-
|
|
48071
|
+
log4.warn(`API ${method} ${path} failed: ${response.status} ${text.substring(0, 100)}`);
|
|
48100
48072
|
throw new Error(`Mattermost API error ${response.status}: ${text}`);
|
|
48101
48073
|
}
|
|
48102
|
-
|
|
48074
|
+
log4.debug(`API ${method} ${path} \u2192 ${response.status}`);
|
|
48103
48075
|
return response.json();
|
|
48104
48076
|
}
|
|
48105
48077
|
async function getMe(config) {
|
|
@@ -48109,7 +48081,7 @@ async function getUser(config, userId) {
|
|
|
48109
48081
|
try {
|
|
48110
48082
|
return await mattermostApi(config, "GET", `/users/${userId}`);
|
|
48111
48083
|
} catch (err) {
|
|
48112
|
-
|
|
48084
|
+
log4.debug(`Failed to get user ${userId}: ${err}`);
|
|
48113
48085
|
return null;
|
|
48114
48086
|
}
|
|
48115
48087
|
}
|
|
@@ -48144,7 +48116,7 @@ async function createInteractivePost(config, channelId, message, reactions, root
|
|
|
48144
48116
|
try {
|
|
48145
48117
|
await addReaction(config, post.id, botUserId, emoji);
|
|
48146
48118
|
} catch (err) {
|
|
48147
|
-
|
|
48119
|
+
log4.warn(`Failed to add reaction ${emoji}: ${err}`);
|
|
48148
48120
|
}
|
|
48149
48121
|
}
|
|
48150
48122
|
return post;
|
|
@@ -48488,13 +48460,13 @@ class SlackPermissionApi {
|
|
|
48488
48460
|
}
|
|
48489
48461
|
}
|
|
48490
48462
|
// src/session/manager.ts
|
|
48491
|
-
import { EventEmitter as
|
|
48463
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
48492
48464
|
|
|
48493
48465
|
// src/persistence/session-store.ts
|
|
48494
48466
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
48495
48467
|
import { homedir as homedir2 } from "os";
|
|
48496
48468
|
import { join as join2 } from "path";
|
|
48497
|
-
var
|
|
48469
|
+
var log5 = createLogger("persist");
|
|
48498
48470
|
var STORE_VERSION = 2;
|
|
48499
48471
|
var DEFAULT_CONFIG_DIR = join2(homedir2(), ".config", "claude-threads");
|
|
48500
48472
|
var DEFAULT_SESSIONS_FILE = join2(DEFAULT_CONFIG_DIR, "sessions.json");
|
|
@@ -48519,13 +48491,13 @@ class SessionStore {
|
|
|
48519
48491
|
load() {
|
|
48520
48492
|
const sessions = new Map;
|
|
48521
48493
|
if (!existsSync3(this.sessionsFile)) {
|
|
48522
|
-
|
|
48494
|
+
log5.debug("No sessions file found");
|
|
48523
48495
|
return sessions;
|
|
48524
48496
|
}
|
|
48525
48497
|
try {
|
|
48526
48498
|
const data = JSON.parse(readFileSync3(this.sessionsFile, "utf-8"));
|
|
48527
48499
|
if (data.version === 1) {
|
|
48528
|
-
|
|
48500
|
+
log5.info("Migrating sessions from v1 to v2 (adding platformId)");
|
|
48529
48501
|
const newSessions = {};
|
|
48530
48502
|
for (const [_oldKey, session] of Object.entries(data.sessions)) {
|
|
48531
48503
|
const v1Session = session;
|
|
@@ -48539,7 +48511,7 @@ class SessionStore {
|
|
|
48539
48511
|
data.version = 2;
|
|
48540
48512
|
this.writeAtomic(data);
|
|
48541
48513
|
} else if (data.version !== STORE_VERSION) {
|
|
48542
|
-
|
|
48514
|
+
log5.warn(`Sessions file version ${data.version} not supported, starting fresh`);
|
|
48543
48515
|
return sessions;
|
|
48544
48516
|
}
|
|
48545
48517
|
for (const session of Object.values(data.sessions)) {
|
|
@@ -48548,9 +48520,9 @@ class SessionStore {
|
|
|
48548
48520
|
const sessionId = `${session.platformId}:${session.threadId}`;
|
|
48549
48521
|
sessions.set(sessionId, session);
|
|
48550
48522
|
}
|
|
48551
|
-
|
|
48523
|
+
log5.debug(`Loaded ${sessions.size} active session(s)`);
|
|
48552
48524
|
} catch (err) {
|
|
48553
|
-
|
|
48525
|
+
log5.error(`Failed to load sessions: ${err}`);
|
|
48554
48526
|
}
|
|
48555
48527
|
return sessions;
|
|
48556
48528
|
}
|
|
@@ -48559,7 +48531,7 @@ class SessionStore {
|
|
|
48559
48531
|
data.sessions[sessionId] = session;
|
|
48560
48532
|
this.writeAtomic(data);
|
|
48561
48533
|
const shortId = sessionId.substring(0, 20);
|
|
48562
|
-
|
|
48534
|
+
log5.debug(`Saved session ${shortId}...`);
|
|
48563
48535
|
}
|
|
48564
48536
|
remove(sessionId) {
|
|
48565
48537
|
const data = this.loadRaw();
|
|
@@ -48567,7 +48539,7 @@ class SessionStore {
|
|
|
48567
48539
|
delete data.sessions[sessionId];
|
|
48568
48540
|
this.writeAtomic(data);
|
|
48569
48541
|
const shortId = sessionId.substring(0, 20);
|
|
48570
|
-
|
|
48542
|
+
log5.debug(`Removed session ${shortId}...`);
|
|
48571
48543
|
}
|
|
48572
48544
|
}
|
|
48573
48545
|
softDelete(sessionId) {
|
|
@@ -48576,7 +48548,7 @@ class SessionStore {
|
|
|
48576
48548
|
data.sessions[sessionId].cleanedAt = new Date().toISOString();
|
|
48577
48549
|
this.writeAtomic(data);
|
|
48578
48550
|
const shortId = sessionId.substring(0, 20);
|
|
48579
|
-
|
|
48551
|
+
log5.debug(`Soft-deleted session ${shortId}...`);
|
|
48580
48552
|
}
|
|
48581
48553
|
}
|
|
48582
48554
|
cleanStale(maxAgeMs) {
|
|
@@ -48594,7 +48566,7 @@ class SessionStore {
|
|
|
48594
48566
|
}
|
|
48595
48567
|
if (staleIds.length > 0) {
|
|
48596
48568
|
this.writeAtomic(data);
|
|
48597
|
-
|
|
48569
|
+
log5.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
|
|
48598
48570
|
}
|
|
48599
48571
|
return staleIds;
|
|
48600
48572
|
}
|
|
@@ -48613,7 +48585,7 @@ class SessionStore {
|
|
|
48613
48585
|
}
|
|
48614
48586
|
if (removedCount > 0) {
|
|
48615
48587
|
this.writeAtomic(data);
|
|
48616
|
-
|
|
48588
|
+
log5.debug(`Permanently removed ${removedCount} old session(s) from history`);
|
|
48617
48589
|
}
|
|
48618
48590
|
return removedCount;
|
|
48619
48591
|
}
|
|
@@ -48640,7 +48612,7 @@ class SessionStore {
|
|
|
48640
48612
|
clear() {
|
|
48641
48613
|
const data = this.loadRaw();
|
|
48642
48614
|
this.writeAtomic({ version: STORE_VERSION, sessions: {}, stickyPostIds: data.stickyPostIds });
|
|
48643
|
-
|
|
48615
|
+
log5.debug("Cleared all sessions");
|
|
48644
48616
|
}
|
|
48645
48617
|
saveStickyPostId(platformId, postId) {
|
|
48646
48618
|
const data = this.loadRaw();
|
|
@@ -48649,7 +48621,7 @@ class SessionStore {
|
|
|
48649
48621
|
}
|
|
48650
48622
|
data.stickyPostIds[platformId] = postId;
|
|
48651
48623
|
this.writeAtomic(data);
|
|
48652
|
-
|
|
48624
|
+
log5.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
|
|
48653
48625
|
}
|
|
48654
48626
|
getStickyPostIds() {
|
|
48655
48627
|
const data = this.loadRaw();
|
|
@@ -48660,7 +48632,7 @@ class SessionStore {
|
|
|
48660
48632
|
if (data.stickyPostIds && data.stickyPostIds[platformId]) {
|
|
48661
48633
|
delete data.stickyPostIds[platformId];
|
|
48662
48634
|
this.writeAtomic(data);
|
|
48663
|
-
|
|
48635
|
+
log5.debug(`Removed sticky post ID for ${platformId}`);
|
|
48664
48636
|
}
|
|
48665
48637
|
}
|
|
48666
48638
|
getPlatformEnabledState() {
|
|
@@ -48678,7 +48650,7 @@ class SessionStore {
|
|
|
48678
48650
|
}
|
|
48679
48651
|
data.platformEnabledState[platformId] = enabled;
|
|
48680
48652
|
this.writeAtomic(data);
|
|
48681
|
-
|
|
48653
|
+
log5.debug(`Set platform ${platformId} enabled state to ${enabled}`);
|
|
48682
48654
|
}
|
|
48683
48655
|
findByThread(platformId, threadId) {
|
|
48684
48656
|
const sessionId = `${platformId}:${threadId}`;
|
|
@@ -48724,7 +48696,7 @@ import { join as join5 } from "path";
|
|
|
48724
48696
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, appendFileSync, readdirSync, statSync, unlinkSync, rmdirSync, readFileSync as readFileSync4 } from "fs";
|
|
48725
48697
|
import { homedir as homedir3 } from "os";
|
|
48726
48698
|
import { join as join3, dirname as dirname3 } from "path";
|
|
48727
|
-
var
|
|
48699
|
+
var log6 = createLogger("thread-log");
|
|
48728
48700
|
var LOGS_BASE_DIR = join3(homedir3(), ".claude-threads", "logs");
|
|
48729
48701
|
|
|
48730
48702
|
class ThreadLoggerImpl {
|
|
@@ -48754,7 +48726,7 @@ class ThreadLoggerImpl {
|
|
|
48754
48726
|
this.flushTimer = setInterval(() => {
|
|
48755
48727
|
this.flushSync();
|
|
48756
48728
|
}, this.flushIntervalMs);
|
|
48757
|
-
|
|
48729
|
+
log6.debug(`Thread logger initialized: ${this.logPath}`);
|
|
48758
48730
|
}
|
|
48759
48731
|
}
|
|
48760
48732
|
isEnabled() {
|
|
@@ -48868,7 +48840,7 @@ class ThreadLoggerImpl {
|
|
|
48868
48840
|
this.flushTimer = null;
|
|
48869
48841
|
}
|
|
48870
48842
|
this.flushSync();
|
|
48871
|
-
|
|
48843
|
+
log6.debug(`Thread logger closed: ${this.logPath}`);
|
|
48872
48844
|
}
|
|
48873
48845
|
addEntry(entry) {
|
|
48874
48846
|
this.buffer.push(entry);
|
|
@@ -48886,7 +48858,7 @@ class ThreadLoggerImpl {
|
|
|
48886
48858
|
appendFileSync(this.logPath, lines, "utf8");
|
|
48887
48859
|
this.buffer = [];
|
|
48888
48860
|
} catch (err) {
|
|
48889
|
-
|
|
48861
|
+
log6.error(`Failed to flush thread log: ${err}`);
|
|
48890
48862
|
}
|
|
48891
48863
|
}
|
|
48892
48864
|
}
|
|
@@ -48937,25 +48909,25 @@ function cleanupOldLogs(retentionDays = 30) {
|
|
|
48937
48909
|
if (fileStat.mtimeMs < cutoffMs) {
|
|
48938
48910
|
unlinkSync(filePath);
|
|
48939
48911
|
deletedCount++;
|
|
48940
|
-
|
|
48912
|
+
log6.debug(`Deleted old log file: ${filePath}`);
|
|
48941
48913
|
}
|
|
48942
48914
|
} catch (err) {
|
|
48943
|
-
|
|
48915
|
+
log6.warn(`Failed to check/delete log file ${filePath}: ${err}`);
|
|
48944
48916
|
}
|
|
48945
48917
|
}
|
|
48946
48918
|
try {
|
|
48947
48919
|
const remaining = readdirSync(platformDir);
|
|
48948
48920
|
if (remaining.length === 0) {
|
|
48949
48921
|
rmdirSync(platformDir);
|
|
48950
|
-
|
|
48922
|
+
log6.debug(`Removed empty platform log directory: ${platformDir}`);
|
|
48951
48923
|
}
|
|
48952
48924
|
} catch {}
|
|
48953
48925
|
}
|
|
48954
48926
|
if (deletedCount > 0) {
|
|
48955
|
-
|
|
48927
|
+
log6.info(`Cleaned up ${deletedCount} old log file(s)`);
|
|
48956
48928
|
}
|
|
48957
48929
|
} catch (err) {
|
|
48958
|
-
|
|
48930
|
+
log6.error(`Failed to clean up old logs: ${err}`);
|
|
48959
48931
|
}
|
|
48960
48932
|
return deletedCount;
|
|
48961
48933
|
}
|
|
@@ -48964,16 +48936,16 @@ function getLogFilePath(platformId, sessionId) {
|
|
|
48964
48936
|
}
|
|
48965
48937
|
function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
48966
48938
|
const logPath = getLogFilePath(platformId, sessionId);
|
|
48967
|
-
|
|
48939
|
+
log6.debug(`Reading log entries from: ${logPath}`);
|
|
48968
48940
|
if (!existsSync4(logPath)) {
|
|
48969
|
-
|
|
48941
|
+
log6.debug(`Log file does not exist: ${logPath}`);
|
|
48970
48942
|
return [];
|
|
48971
48943
|
}
|
|
48972
48944
|
try {
|
|
48973
48945
|
const content = readFileSync4(logPath, "utf8");
|
|
48974
48946
|
const lines = content.trim().split(`
|
|
48975
48947
|
`);
|
|
48976
|
-
|
|
48948
|
+
log6.debug(`Log file has ${lines.length} lines`);
|
|
48977
48949
|
const recentLines = lines.slice(-maxLines);
|
|
48978
48950
|
const entries = [];
|
|
48979
48951
|
for (const line of recentLines) {
|
|
@@ -48983,10 +48955,10 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
|
48983
48955
|
entries.push(JSON.parse(line));
|
|
48984
48956
|
} catch {}
|
|
48985
48957
|
}
|
|
48986
|
-
|
|
48958
|
+
log6.debug(`Parsed ${entries.length} log entries`);
|
|
48987
48959
|
return entries;
|
|
48988
48960
|
} catch (err) {
|
|
48989
|
-
|
|
48961
|
+
log6.error(`Failed to read log file: ${err}`);
|
|
48990
48962
|
return [];
|
|
48991
48963
|
}
|
|
48992
48964
|
}
|
|
@@ -48997,11 +48969,11 @@ import { randomUUID } from "crypto";
|
|
|
48997
48969
|
import * as path from "path";
|
|
48998
48970
|
import * as fs from "fs/promises";
|
|
48999
48971
|
import { homedir as homedir4 } from "os";
|
|
49000
|
-
var
|
|
48972
|
+
var log7 = createLogger("git-wt");
|
|
49001
48973
|
var WORKTREES_DIR = path.join(homedir4(), ".claude-threads", "worktrees");
|
|
49002
48974
|
async function execGit(args, cwd) {
|
|
49003
48975
|
const cmd = `git ${args.join(" ")}`;
|
|
49004
|
-
|
|
48976
|
+
log7.debug(`Executing: ${cmd}`);
|
|
49005
48977
|
return new Promise((resolve2, reject) => {
|
|
49006
48978
|
const proc = spawn("git", args, { cwd });
|
|
49007
48979
|
let stdout = "";
|
|
@@ -49014,15 +48986,15 @@ async function execGit(args, cwd) {
|
|
|
49014
48986
|
});
|
|
49015
48987
|
proc.on("close", (code) => {
|
|
49016
48988
|
if (code === 0) {
|
|
49017
|
-
|
|
48989
|
+
log7.debug(`${cmd} \u2192 success`);
|
|
49018
48990
|
resolve2(stdout.trim());
|
|
49019
48991
|
} else {
|
|
49020
|
-
|
|
48992
|
+
log7.debug(`${cmd} \u2192 failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
|
|
49021
48993
|
reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
|
|
49022
48994
|
}
|
|
49023
48995
|
});
|
|
49024
48996
|
proc.on("error", (err) => {
|
|
49025
|
-
|
|
48997
|
+
log7.warn(`${cmd} \u2192 error: ${err}`);
|
|
49026
48998
|
reject(err);
|
|
49027
48999
|
});
|
|
49028
49000
|
});
|
|
@@ -49032,7 +49004,7 @@ async function isGitRepository(dir) {
|
|
|
49032
49004
|
await execGit(["rev-parse", "--git-dir"], dir);
|
|
49033
49005
|
return true;
|
|
49034
49006
|
} catch (err) {
|
|
49035
|
-
|
|
49007
|
+
log7.debug(`Not a git repository: ${dir} (${err})`);
|
|
49036
49008
|
return false;
|
|
49037
49009
|
}
|
|
49038
49010
|
}
|
|
@@ -49167,7 +49139,7 @@ async function detectWorktreeInfo(workingDir) {
|
|
|
49167
49139
|
const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
|
|
49168
49140
|
const branch = branchOutput?.trim();
|
|
49169
49141
|
if (!branch) {
|
|
49170
|
-
|
|
49142
|
+
log7.debug(`Could not detect branch for worktree at ${workingDir}`);
|
|
49171
49143
|
return null;
|
|
49172
49144
|
}
|
|
49173
49145
|
const gitDirOutput = await execGit(["rev-parse", "--git-common-dir"], workingDir);
|
|
@@ -49179,45 +49151,45 @@ async function detectWorktreeInfo(workingDir) {
|
|
|
49179
49151
|
repoRoot = repoRoot.slice(0, -4);
|
|
49180
49152
|
}
|
|
49181
49153
|
}
|
|
49182
|
-
|
|
49154
|
+
log7.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
|
|
49183
49155
|
return {
|
|
49184
49156
|
worktreePath: workingDir,
|
|
49185
49157
|
branch,
|
|
49186
49158
|
repoRoot: repoRoot || workingDir
|
|
49187
49159
|
};
|
|
49188
49160
|
} catch (err) {
|
|
49189
|
-
|
|
49161
|
+
log7.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
|
|
49190
49162
|
return null;
|
|
49191
49163
|
}
|
|
49192
49164
|
}
|
|
49193
49165
|
async function createWorktree(repoRoot, branch, targetDir) {
|
|
49194
|
-
|
|
49166
|
+
log7.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
|
|
49195
49167
|
const parentDir = path.dirname(targetDir);
|
|
49196
|
-
|
|
49168
|
+
log7.debug(`Creating parent directory: ${parentDir}`);
|
|
49197
49169
|
await fs.mkdir(parentDir, { recursive: true });
|
|
49198
49170
|
const exists = await branchExists(repoRoot, branch);
|
|
49199
49171
|
if (exists) {
|
|
49200
|
-
|
|
49172
|
+
log7.debug(`Branch '${branch}' exists, adding worktree`);
|
|
49201
49173
|
await execGit(["worktree", "add", targetDir, branch], repoRoot);
|
|
49202
49174
|
} else {
|
|
49203
|
-
|
|
49175
|
+
log7.debug(`Branch '${branch}' does not exist, creating with worktree`);
|
|
49204
49176
|
await execGit(["worktree", "add", "-b", branch, targetDir], repoRoot);
|
|
49205
49177
|
}
|
|
49206
|
-
|
|
49178
|
+
log7.info(`Worktree created successfully: ${targetDir}`);
|
|
49207
49179
|
return targetDir;
|
|
49208
49180
|
}
|
|
49209
49181
|
async function removeWorktree(repoRoot, worktreePath) {
|
|
49210
|
-
|
|
49182
|
+
log7.info(`Removing worktree: ${worktreePath}`);
|
|
49211
49183
|
try {
|
|
49212
49184
|
await execGit(["worktree", "remove", worktreePath], repoRoot);
|
|
49213
|
-
|
|
49185
|
+
log7.debug("Worktree removed cleanly");
|
|
49214
49186
|
} catch (err) {
|
|
49215
|
-
|
|
49187
|
+
log7.debug(`Clean remove failed (${err}), trying force remove`);
|
|
49216
49188
|
await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
|
|
49217
49189
|
}
|
|
49218
|
-
|
|
49190
|
+
log7.debug("Pruning stale worktree references");
|
|
49219
49191
|
await execGit(["worktree", "prune"], repoRoot);
|
|
49220
|
-
|
|
49192
|
+
log7.info("Worktree removed and pruned successfully");
|
|
49221
49193
|
}
|
|
49222
49194
|
async function findWorktreeByBranch(repoRoot, branch) {
|
|
49223
49195
|
const worktrees = await listWorktrees(repoRoot);
|
|
@@ -49258,14 +49230,14 @@ async function writeMetadataStore(store) {
|
|
|
49258
49230
|
await fs.mkdir(path.dirname(METADATA_STORE_PATH), { recursive: true });
|
|
49259
49231
|
await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), "utf-8");
|
|
49260
49232
|
} catch (err) {
|
|
49261
|
-
|
|
49233
|
+
log7.warn(`Failed to write worktree metadata store: ${err}`);
|
|
49262
49234
|
}
|
|
49263
49235
|
}
|
|
49264
49236
|
async function writeWorktreeMetadata(worktreePath, metadata) {
|
|
49265
49237
|
const store = await readMetadataStore();
|
|
49266
49238
|
store[worktreePath] = metadata;
|
|
49267
49239
|
await writeMetadataStore(store);
|
|
49268
|
-
|
|
49240
|
+
log7.debug(`Wrote worktree metadata for: ${worktreePath}`);
|
|
49269
49241
|
}
|
|
49270
49242
|
async function readWorktreeMetadata(worktreePath) {
|
|
49271
49243
|
const store = await readMetadataStore();
|
|
@@ -49288,12 +49260,12 @@ async function removeWorktreeMetadata(worktreePath) {
|
|
|
49288
49260
|
if (store[worktreePath]) {
|
|
49289
49261
|
delete store[worktreePath];
|
|
49290
49262
|
await writeMetadataStore(store);
|
|
49291
|
-
|
|
49263
|
+
log7.debug(`Removed worktree metadata for: ${worktreePath}`);
|
|
49292
49264
|
}
|
|
49293
49265
|
}
|
|
49294
49266
|
|
|
49295
49267
|
// src/cleanup/scheduler.ts
|
|
49296
|
-
var
|
|
49268
|
+
var log8 = createLogger("cleanup");
|
|
49297
49269
|
var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
|
|
49298
49270
|
var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
|
|
49299
49271
|
|
|
@@ -49316,17 +49288,17 @@ class CleanupScheduler {
|
|
|
49316
49288
|
}
|
|
49317
49289
|
start() {
|
|
49318
49290
|
if (this.isRunning) {
|
|
49319
|
-
|
|
49291
|
+
log8.debug("Cleanup scheduler already running");
|
|
49320
49292
|
return;
|
|
49321
49293
|
}
|
|
49322
49294
|
this.isRunning = true;
|
|
49323
|
-
|
|
49295
|
+
log8.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
|
|
49324
49296
|
this.runCleanup().catch((err) => {
|
|
49325
|
-
|
|
49297
|
+
log8.warn(`Initial cleanup failed: ${err}`);
|
|
49326
49298
|
});
|
|
49327
49299
|
this.timer = setInterval(() => {
|
|
49328
49300
|
this.runCleanup().catch((err) => {
|
|
49329
|
-
|
|
49301
|
+
log8.warn(`Periodic cleanup failed: ${err}`);
|
|
49330
49302
|
});
|
|
49331
49303
|
}, this.intervalMs);
|
|
49332
49304
|
}
|
|
@@ -49336,11 +49308,11 @@ class CleanupScheduler {
|
|
|
49336
49308
|
this.timer = null;
|
|
49337
49309
|
}
|
|
49338
49310
|
this.isRunning = false;
|
|
49339
|
-
|
|
49311
|
+
log8.debug("Cleanup scheduler stopped");
|
|
49340
49312
|
}
|
|
49341
49313
|
async runCleanup() {
|
|
49342
49314
|
const startTime = Date.now();
|
|
49343
|
-
|
|
49315
|
+
log8.debug("Running background cleanup...");
|
|
49344
49316
|
const stats = {
|
|
49345
49317
|
logsDeleted: 0,
|
|
49346
49318
|
worktreesCleaned: 0,
|
|
@@ -49366,9 +49338,9 @@ class CleanupScheduler {
|
|
|
49366
49338
|
const elapsed = Date.now() - startTime;
|
|
49367
49339
|
const totalCleaned = stats.logsDeleted + stats.worktreesCleaned + stats.metadataCleaned;
|
|
49368
49340
|
if (totalCleaned > 0 || stats.errors.length > 0) {
|
|
49369
|
-
|
|
49341
|
+
log8.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
|
|
49370
49342
|
} else {
|
|
49371
|
-
|
|
49343
|
+
log8.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
|
|
49372
49344
|
}
|
|
49373
49345
|
return stats;
|
|
49374
49346
|
}
|
|
@@ -49381,7 +49353,7 @@ class CleanupScheduler {
|
|
|
49381
49353
|
const deleted = cleanupOldLogs(this.logRetentionDays);
|
|
49382
49354
|
resolve2(deleted);
|
|
49383
49355
|
} catch (err) {
|
|
49384
|
-
|
|
49356
|
+
log8.warn(`Log cleanup error: ${err}`);
|
|
49385
49357
|
resolve2(0);
|
|
49386
49358
|
}
|
|
49387
49359
|
});
|
|
@@ -49390,7 +49362,7 @@ class CleanupScheduler {
|
|
|
49390
49362
|
const worktreesDir = getWorktreesDir();
|
|
49391
49363
|
const result = { cleaned: 0, metadata: 0 };
|
|
49392
49364
|
if (!existsSync5(worktreesDir)) {
|
|
49393
|
-
|
|
49365
|
+
log8.debug("No worktrees directory exists, nothing to clean");
|
|
49394
49366
|
return result;
|
|
49395
49367
|
}
|
|
49396
49368
|
const persisted = this.sessionStore.load();
|
|
@@ -49408,7 +49380,7 @@ class CleanupScheduler {
|
|
|
49408
49380
|
continue;
|
|
49409
49381
|
const worktreePath = join5(worktreesDir, entry.name);
|
|
49410
49382
|
if (activeWorktrees.has(worktreePath)) {
|
|
49411
|
-
|
|
49383
|
+
log8.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
|
|
49412
49384
|
continue;
|
|
49413
49385
|
}
|
|
49414
49386
|
const meta = await readWorktreeMetadata(worktreePath);
|
|
@@ -49418,7 +49390,7 @@ class CleanupScheduler {
|
|
|
49418
49390
|
const lastActivity = new Date(meta.lastActivityAt).getTime();
|
|
49419
49391
|
const age = now - lastActivity;
|
|
49420
49392
|
if (meta.sessionId && age < this.maxWorktreeAgeMs) {
|
|
49421
|
-
|
|
49393
|
+
log8.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
|
|
49422
49394
|
continue;
|
|
49423
49395
|
}
|
|
49424
49396
|
const merged = age >= this.maxWorktreeAgeMs ? await isBranchMerged(meta.repoRoot, meta.branch).catch(() => false) : false;
|
|
@@ -49429,7 +49401,7 @@ class CleanupScheduler {
|
|
|
49429
49401
|
shouldCleanup = true;
|
|
49430
49402
|
cleanupReason = `inactive for ${Math.round(age / 3600000)}h`;
|
|
49431
49403
|
} else {
|
|
49432
|
-
|
|
49404
|
+
log8.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
|
|
49433
49405
|
continue;
|
|
49434
49406
|
}
|
|
49435
49407
|
} else {
|
|
@@ -49438,7 +49410,7 @@ class CleanupScheduler {
|
|
|
49438
49410
|
}
|
|
49439
49411
|
if (!shouldCleanup)
|
|
49440
49412
|
continue;
|
|
49441
|
-
|
|
49413
|
+
log8.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
|
|
49442
49414
|
try {
|
|
49443
49415
|
if (meta?.repoRoot) {
|
|
49444
49416
|
await removeWorktree(meta.repoRoot, worktreePath);
|
|
@@ -49449,19 +49421,19 @@ class CleanupScheduler {
|
|
|
49449
49421
|
await removeWorktreeMetadata(worktreePath);
|
|
49450
49422
|
result.metadata++;
|
|
49451
49423
|
} catch (err) {
|
|
49452
|
-
|
|
49424
|
+
log8.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
|
|
49453
49425
|
try {
|
|
49454
49426
|
await rm(worktreePath, { recursive: true, force: true });
|
|
49455
49427
|
result.cleaned++;
|
|
49456
49428
|
await removeWorktreeMetadata(worktreePath);
|
|
49457
49429
|
result.metadata++;
|
|
49458
49430
|
} catch (rmErr) {
|
|
49459
|
-
|
|
49431
|
+
log8.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
|
|
49460
49432
|
}
|
|
49461
49433
|
}
|
|
49462
49434
|
}
|
|
49463
49435
|
} catch (err) {
|
|
49464
|
-
|
|
49436
|
+
log8.warn(`Failed to scan worktrees directory: ${err}`);
|
|
49465
49437
|
}
|
|
49466
49438
|
return result;
|
|
49467
49439
|
}
|
|
@@ -49528,13 +49500,13 @@ function getSessionStatus(session) {
|
|
|
49528
49500
|
|
|
49529
49501
|
// src/claude/cli.ts
|
|
49530
49502
|
import { spawn as spawn2 } from "child_process";
|
|
49531
|
-
import { EventEmitter as
|
|
49503
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
49532
49504
|
import { resolve as resolve2, dirname as dirname5 } from "path";
|
|
49533
49505
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
49534
49506
|
import { existsSync as existsSync6, readFileSync as readFileSync5, watchFile, unwatchFile, unlinkSync as unlinkSync2, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
|
|
49535
49507
|
import { tmpdir } from "os";
|
|
49536
49508
|
import { join as join6 } from "path";
|
|
49537
|
-
var
|
|
49509
|
+
var log9 = createLogger("claude");
|
|
49538
49510
|
function cleanupBrowserBridgeSockets() {
|
|
49539
49511
|
try {
|
|
49540
49512
|
const tempDir = tmpdir();
|
|
@@ -49546,17 +49518,17 @@ function cleanupBrowserBridgeSockets() {
|
|
|
49546
49518
|
const stats = statSync2(filePath);
|
|
49547
49519
|
if (stats.isSocket()) {
|
|
49548
49520
|
unlinkSync2(filePath);
|
|
49549
|
-
|
|
49521
|
+
log9.debug(`Removed stale browser bridge socket: ${file}`);
|
|
49550
49522
|
}
|
|
49551
49523
|
} catch {}
|
|
49552
49524
|
}
|
|
49553
49525
|
}
|
|
49554
49526
|
} catch (err) {
|
|
49555
|
-
|
|
49527
|
+
log9.debug(`Browser bridge cleanup failed: ${err}`);
|
|
49556
49528
|
}
|
|
49557
49529
|
}
|
|
49558
49530
|
|
|
49559
|
-
class ClaudeCli extends
|
|
49531
|
+
class ClaudeCli extends EventEmitter2 {
|
|
49560
49532
|
process = null;
|
|
49561
49533
|
options;
|
|
49562
49534
|
buffer = "";
|
|
@@ -50207,7 +50179,7 @@ import { existsSync as existsSync10 } from "fs";
|
|
|
50207
50179
|
|
|
50208
50180
|
// src/utils/keep-alive.ts
|
|
50209
50181
|
import { spawn as spawn3 } from "child_process";
|
|
50210
|
-
var
|
|
50182
|
+
var log10 = createLogger("keepalive");
|
|
50211
50183
|
|
|
50212
50184
|
class KeepAliveManager {
|
|
50213
50185
|
activeSessionCount = 0;
|
|
@@ -50222,7 +50194,7 @@ class KeepAliveManager {
|
|
|
50222
50194
|
if (!enabled && this.keepAliveProcess) {
|
|
50223
50195
|
this.stopKeepAlive();
|
|
50224
50196
|
}
|
|
50225
|
-
|
|
50197
|
+
log10.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
|
|
50226
50198
|
}
|
|
50227
50199
|
isEnabled() {
|
|
50228
50200
|
return this.enabled;
|
|
@@ -50232,7 +50204,7 @@ class KeepAliveManager {
|
|
|
50232
50204
|
}
|
|
50233
50205
|
sessionStarted() {
|
|
50234
50206
|
this.activeSessionCount++;
|
|
50235
|
-
|
|
50207
|
+
log10.debug(`Session started (${this.activeSessionCount} active)`);
|
|
50236
50208
|
if (this.activeSessionCount === 1) {
|
|
50237
50209
|
this.startKeepAlive();
|
|
50238
50210
|
}
|
|
@@ -50241,7 +50213,7 @@ class KeepAliveManager {
|
|
|
50241
50213
|
if (this.activeSessionCount > 0) {
|
|
50242
50214
|
this.activeSessionCount--;
|
|
50243
50215
|
}
|
|
50244
|
-
|
|
50216
|
+
log10.debug(`Session ended (${this.activeSessionCount} active)`);
|
|
50245
50217
|
if (this.activeSessionCount === 0) {
|
|
50246
50218
|
this.stopKeepAlive();
|
|
50247
50219
|
}
|
|
@@ -50255,11 +50227,11 @@ class KeepAliveManager {
|
|
|
50255
50227
|
}
|
|
50256
50228
|
startKeepAlive() {
|
|
50257
50229
|
if (!this.enabled) {
|
|
50258
|
-
|
|
50230
|
+
log10.debug("Keep-alive disabled, skipping");
|
|
50259
50231
|
return;
|
|
50260
50232
|
}
|
|
50261
50233
|
if (this.keepAliveProcess) {
|
|
50262
|
-
|
|
50234
|
+
log10.debug("Keep-alive already running");
|
|
50263
50235
|
return;
|
|
50264
50236
|
}
|
|
50265
50237
|
switch (this.platform) {
|
|
@@ -50273,12 +50245,12 @@ class KeepAliveManager {
|
|
|
50273
50245
|
this.startWindowsKeepAlive();
|
|
50274
50246
|
break;
|
|
50275
50247
|
default:
|
|
50276
|
-
|
|
50248
|
+
log10.warn(`Keep-alive not supported on ${this.platform}`);
|
|
50277
50249
|
}
|
|
50278
50250
|
}
|
|
50279
50251
|
stopKeepAlive() {
|
|
50280
50252
|
if (this.keepAliveProcess) {
|
|
50281
|
-
|
|
50253
|
+
log10.debug("Stopping keep-alive");
|
|
50282
50254
|
this.keepAliveProcess.kill();
|
|
50283
50255
|
this.keepAliveProcess = null;
|
|
50284
50256
|
}
|
|
@@ -50290,18 +50262,18 @@ class KeepAliveManager {
|
|
|
50290
50262
|
detached: false
|
|
50291
50263
|
});
|
|
50292
50264
|
this.keepAliveProcess.on("error", (err) => {
|
|
50293
|
-
|
|
50265
|
+
log10.error(`Failed to start caffeinate: ${err.message}`);
|
|
50294
50266
|
this.keepAliveProcess = null;
|
|
50295
50267
|
});
|
|
50296
50268
|
this.keepAliveProcess.on("exit", (code) => {
|
|
50297
50269
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
50298
|
-
|
|
50270
|
+
log10.debug(`caffeinate exited with code ${code}`);
|
|
50299
50271
|
}
|
|
50300
50272
|
this.keepAliveProcess = null;
|
|
50301
50273
|
});
|
|
50302
|
-
|
|
50274
|
+
log10.info("Sleep prevention active (caffeinate)");
|
|
50303
50275
|
} catch (err) {
|
|
50304
|
-
|
|
50276
|
+
log10.error(`Failed to start caffeinate: ${err}`);
|
|
50305
50277
|
}
|
|
50306
50278
|
}
|
|
50307
50279
|
startLinuxKeepAlive() {
|
|
@@ -50317,19 +50289,19 @@ class KeepAliveManager {
|
|
|
50317
50289
|
detached: false
|
|
50318
50290
|
});
|
|
50319
50291
|
this.keepAliveProcess.on("error", (err) => {
|
|
50320
|
-
|
|
50292
|
+
log10.debug(`systemd-inhibit not available: ${err.message}`);
|
|
50321
50293
|
this.keepAliveProcess = null;
|
|
50322
50294
|
this.startLinuxKeepAliveFallback();
|
|
50323
50295
|
});
|
|
50324
50296
|
this.keepAliveProcess.on("exit", (code) => {
|
|
50325
50297
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
50326
|
-
|
|
50298
|
+
log10.debug(`systemd-inhibit exited with code ${code}`);
|
|
50327
50299
|
}
|
|
50328
50300
|
this.keepAliveProcess = null;
|
|
50329
50301
|
});
|
|
50330
|
-
|
|
50302
|
+
log10.info("Sleep prevention active (systemd-inhibit)");
|
|
50331
50303
|
} catch (err) {
|
|
50332
|
-
|
|
50304
|
+
log10.debug(`Failed to start systemd-inhibit: ${err}`);
|
|
50333
50305
|
this.startLinuxKeepAliveFallback();
|
|
50334
50306
|
}
|
|
50335
50307
|
}
|
|
@@ -50343,15 +50315,15 @@ class KeepAliveManager {
|
|
|
50343
50315
|
detached: false
|
|
50344
50316
|
});
|
|
50345
50317
|
this.keepAliveProcess.on("error", (err) => {
|
|
50346
|
-
|
|
50318
|
+
log10.warn(`Linux keep-alive fallback not available: ${err.message}`);
|
|
50347
50319
|
this.keepAliveProcess = null;
|
|
50348
50320
|
});
|
|
50349
50321
|
this.keepAliveProcess.on("exit", () => {
|
|
50350
50322
|
this.keepAliveProcess = null;
|
|
50351
50323
|
});
|
|
50352
|
-
|
|
50324
|
+
log10.info("Sleep prevention active (xdg-screensaver)");
|
|
50353
50325
|
} catch (err) {
|
|
50354
|
-
|
|
50326
|
+
log10.warn(`Linux keep-alive not available: ${err}`);
|
|
50355
50327
|
}
|
|
50356
50328
|
}
|
|
50357
50329
|
startWindowsKeepAlive() {
|
|
@@ -50376,25 +50348,25 @@ class KeepAliveManager {
|
|
|
50376
50348
|
windowsHide: true
|
|
50377
50349
|
});
|
|
50378
50350
|
this.keepAliveProcess.on("error", (err) => {
|
|
50379
|
-
|
|
50351
|
+
log10.warn(`Windows keep-alive not available: ${err.message}`);
|
|
50380
50352
|
this.keepAliveProcess = null;
|
|
50381
50353
|
});
|
|
50382
50354
|
this.keepAliveProcess.on("exit", (code) => {
|
|
50383
50355
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
50384
|
-
|
|
50356
|
+
log10.debug(`PowerShell keep-alive exited with code ${code}`);
|
|
50385
50357
|
}
|
|
50386
50358
|
this.keepAliveProcess = null;
|
|
50387
50359
|
});
|
|
50388
|
-
|
|
50360
|
+
log10.info("Sleep prevention active (SetThreadExecutionState)");
|
|
50389
50361
|
} catch (err) {
|
|
50390
|
-
|
|
50362
|
+
log10.warn(`Windows keep-alive not available: ${err}`);
|
|
50391
50363
|
}
|
|
50392
50364
|
}
|
|
50393
50365
|
}
|
|
50394
50366
|
var keepAlive = new KeepAliveManager;
|
|
50395
50367
|
|
|
50396
50368
|
// src/utils/error-handler/index.ts
|
|
50397
|
-
var
|
|
50369
|
+
var log11 = createLogger("error");
|
|
50398
50370
|
|
|
50399
50371
|
class SessionError extends Error {
|
|
50400
50372
|
sessionId;
|
|
@@ -50420,19 +50392,19 @@ async function handleError(error, context, severity = "recoverable") {
|
|
|
50420
50392
|
const sessionPart = sessionId ? ` (${formatShortId(sessionId)})` : "";
|
|
50421
50393
|
const logMessage = `${context.action}${sessionPart}: ${message}`;
|
|
50422
50394
|
if (severity === "recoverable") {
|
|
50423
|
-
|
|
50395
|
+
log11.warn(logMessage);
|
|
50424
50396
|
} else {
|
|
50425
|
-
|
|
50397
|
+
log11.error(logMessage, error instanceof Error ? error : undefined);
|
|
50426
50398
|
}
|
|
50427
50399
|
if (context.details) {
|
|
50428
|
-
|
|
50400
|
+
log11.debugJson("Error details", context.details);
|
|
50429
50401
|
}
|
|
50430
50402
|
if (context.notifyUser && context.session) {
|
|
50431
50403
|
try {
|
|
50432
50404
|
const fmt = context.session.platform.getFormatter();
|
|
50433
50405
|
await context.session.platform.createPost(`\u26A0\uFE0F ${fmt.formatBold("Error")}: ${context.action} failed. Please try again.`, context.session.threadId);
|
|
50434
50406
|
} catch (notifyError) {
|
|
50435
|
-
|
|
50407
|
+
log11.warn(`Could not notify user: ${notifyError}`);
|
|
50436
50408
|
}
|
|
50437
50409
|
}
|
|
50438
50410
|
if (severity === "session-fatal" || severity === "system-fatal") {
|
|
@@ -50459,7 +50431,7 @@ async function logAndNotify(error, context) {
|
|
|
50459
50431
|
}
|
|
50460
50432
|
function logSilentError(context, error) {
|
|
50461
50433
|
const message = error instanceof Error ? error.message : String(error);
|
|
50462
|
-
|
|
50434
|
+
log11.debug(`[${context}] Silently caught: ${message}`);
|
|
50463
50435
|
}
|
|
50464
50436
|
|
|
50465
50437
|
// src/utils/session-log.ts
|
|
@@ -50474,8 +50446,8 @@ function createSessionLog(baseLog) {
|
|
|
50474
50446
|
|
|
50475
50447
|
// src/operations/post-helpers/index.ts
|
|
50476
50448
|
init_emoji();
|
|
50477
|
-
var
|
|
50478
|
-
var sessionLog = createSessionLog(
|
|
50449
|
+
var log12 = createLogger("helpers");
|
|
50450
|
+
var sessionLog = createSessionLog(log12);
|
|
50479
50451
|
var POST_TYPES = {
|
|
50480
50452
|
info: "",
|
|
50481
50453
|
success: "\u2705",
|
|
@@ -50558,7 +50530,7 @@ function updateLastMessage(session, post2) {
|
|
|
50558
50530
|
|
|
50559
50531
|
// src/claude/quick-query.ts
|
|
50560
50532
|
import { spawn as spawn4 } from "child_process";
|
|
50561
|
-
var
|
|
50533
|
+
var log13 = createLogger("query");
|
|
50562
50534
|
async function quickQuery(options) {
|
|
50563
50535
|
const {
|
|
50564
50536
|
prompt,
|
|
@@ -50574,7 +50546,7 @@ async function quickQuery(options) {
|
|
|
50574
50546
|
args.push("--system-prompt", systemPrompt);
|
|
50575
50547
|
}
|
|
50576
50548
|
args.push(prompt);
|
|
50577
|
-
|
|
50549
|
+
log13.debug(`Quick query: model=${model}, timeout=${timeout}ms, prompt="${prompt.substring(0, 50)}..."`);
|
|
50578
50550
|
return new Promise((resolve4) => {
|
|
50579
50551
|
let stdout = "";
|
|
50580
50552
|
let stderr = "";
|
|
@@ -50588,7 +50560,7 @@ async function quickQuery(options) {
|
|
|
50588
50560
|
if (!resolved) {
|
|
50589
50561
|
resolved = true;
|
|
50590
50562
|
proc.kill("SIGTERM");
|
|
50591
|
-
|
|
50563
|
+
log13.debug(`Quick query timed out after ${timeout}ms`);
|
|
50592
50564
|
resolve4({
|
|
50593
50565
|
success: false,
|
|
50594
50566
|
error: "timeout",
|
|
@@ -50606,7 +50578,7 @@ async function quickQuery(options) {
|
|
|
50606
50578
|
if (!resolved) {
|
|
50607
50579
|
resolved = true;
|
|
50608
50580
|
clearTimeout(timeoutId);
|
|
50609
|
-
|
|
50581
|
+
log13.debug(`Quick query error: ${err.message}`);
|
|
50610
50582
|
resolve4({
|
|
50611
50583
|
success: false,
|
|
50612
50584
|
error: err.message,
|
|
@@ -50620,14 +50592,14 @@ async function quickQuery(options) {
|
|
|
50620
50592
|
clearTimeout(timeoutId);
|
|
50621
50593
|
const durationMs = Date.now() - startTime;
|
|
50622
50594
|
if (code === 0 && stdout.trim()) {
|
|
50623
|
-
|
|
50595
|
+
log13.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
|
|
50624
50596
|
resolve4({
|
|
50625
50597
|
success: true,
|
|
50626
50598
|
response: stdout.trim(),
|
|
50627
50599
|
durationMs
|
|
50628
50600
|
});
|
|
50629
50601
|
} else {
|
|
50630
|
-
|
|
50602
|
+
log13.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
|
|
50631
50603
|
resolve4({
|
|
50632
50604
|
success: false,
|
|
50633
50605
|
error: stderr || `exit code ${code}`,
|
|
@@ -50641,7 +50613,7 @@ async function quickQuery(options) {
|
|
|
50641
50613
|
}
|
|
50642
50614
|
|
|
50643
50615
|
// src/operations/suggestions/title.ts
|
|
50644
|
-
var
|
|
50616
|
+
var log14 = createLogger("title");
|
|
50645
50617
|
var SUGGESTION_TIMEOUT = 15000;
|
|
50646
50618
|
var MIN_TITLE_LENGTH = 3;
|
|
50647
50619
|
var MAX_TITLE_LENGTH = 50;
|
|
@@ -50705,32 +50677,32 @@ function parseMetadata(response) {
|
|
|
50705
50677
|
const titleMatch = response.match(/TITLE:\s*(.+)/i);
|
|
50706
50678
|
const descMatch = response.match(/DESC:\s*(.+)/i);
|
|
50707
50679
|
if (!titleMatch || !descMatch) {
|
|
50708
|
-
|
|
50680
|
+
log14.debug("Failed to parse title/description from response");
|
|
50709
50681
|
return null;
|
|
50710
50682
|
}
|
|
50711
50683
|
let title = titleMatch[1].trim();
|
|
50712
50684
|
let description = descMatch[1].trim();
|
|
50713
50685
|
if (title.length < MIN_TITLE_LENGTH) {
|
|
50714
|
-
|
|
50686
|
+
log14.debug(`Title too short: ${title.length} chars`);
|
|
50715
50687
|
return null;
|
|
50716
50688
|
}
|
|
50717
50689
|
if (title.length > MAX_TITLE_LENGTH) {
|
|
50718
|
-
|
|
50690
|
+
log14.debug(`Title too long (${title.length} chars), truncating`);
|
|
50719
50691
|
title = truncateAtWord(title, MAX_TITLE_LENGTH);
|
|
50720
50692
|
}
|
|
50721
50693
|
if (description.length < MIN_DESC_LENGTH) {
|
|
50722
|
-
|
|
50694
|
+
log14.debug(`Description too short: ${description.length} chars`);
|
|
50723
50695
|
return null;
|
|
50724
50696
|
}
|
|
50725
50697
|
if (description.length > MAX_DESC_LENGTH) {
|
|
50726
|
-
|
|
50698
|
+
log14.debug(`Description too long (${description.length} chars), truncating`);
|
|
50727
50699
|
description = truncateAtWord(description, MAX_DESC_LENGTH);
|
|
50728
50700
|
}
|
|
50729
50701
|
return { title, description };
|
|
50730
50702
|
}
|
|
50731
50703
|
async function suggestSessionMetadata(context) {
|
|
50732
50704
|
const logContext = typeof context === "string" ? context.substring(0, 50) : context.originalTask.substring(0, 50);
|
|
50733
|
-
|
|
50705
|
+
log14.debug(`Suggesting title for: "${logContext}..."`);
|
|
50734
50706
|
try {
|
|
50735
50707
|
const result = await quickQuery({
|
|
50736
50708
|
prompt: buildTitlePrompt(context),
|
|
@@ -50738,22 +50710,22 @@ async function suggestSessionMetadata(context) {
|
|
|
50738
50710
|
timeout: SUGGESTION_TIMEOUT
|
|
50739
50711
|
});
|
|
50740
50712
|
if (!result.success || !result.response) {
|
|
50741
|
-
|
|
50713
|
+
log14.debug(`Title suggestion failed: ${result.error || "no response"}`);
|
|
50742
50714
|
return null;
|
|
50743
50715
|
}
|
|
50744
50716
|
const metadata = parseMetadata(result.response);
|
|
50745
50717
|
if (metadata) {
|
|
50746
|
-
|
|
50718
|
+
log14.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
|
|
50747
50719
|
}
|
|
50748
50720
|
return metadata;
|
|
50749
50721
|
} catch (err) {
|
|
50750
|
-
|
|
50722
|
+
log14.debug(`Title suggestion error: ${err}`);
|
|
50751
50723
|
return null;
|
|
50752
50724
|
}
|
|
50753
50725
|
}
|
|
50754
50726
|
|
|
50755
50727
|
// src/operations/suggestions/tag.ts
|
|
50756
|
-
var
|
|
50728
|
+
var log15 = createLogger("tags");
|
|
50757
50729
|
var SUGGESTION_TIMEOUT2 = 15000;
|
|
50758
50730
|
var MAX_TAGS = 3;
|
|
50759
50731
|
var VALID_TAGS = [
|
|
@@ -50785,7 +50757,7 @@ function parseTags(response) {
|
|
|
50785
50757
|
return [...new Set(tags)].slice(0, MAX_TAGS);
|
|
50786
50758
|
}
|
|
50787
50759
|
async function suggestSessionTags(userMessage) {
|
|
50788
|
-
|
|
50760
|
+
log15.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
|
|
50789
50761
|
try {
|
|
50790
50762
|
const result = await quickQuery({
|
|
50791
50763
|
prompt: buildTagPrompt(userMessage),
|
|
@@ -50793,14 +50765,14 @@ async function suggestSessionTags(userMessage) {
|
|
|
50793
50765
|
timeout: SUGGESTION_TIMEOUT2
|
|
50794
50766
|
});
|
|
50795
50767
|
if (!result.success || !result.response) {
|
|
50796
|
-
|
|
50768
|
+
log15.debug(`Tag suggestion failed: ${result.error || "no response"}`);
|
|
50797
50769
|
return [];
|
|
50798
50770
|
}
|
|
50799
50771
|
const tags = parseTags(result.response);
|
|
50800
|
-
|
|
50772
|
+
log15.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
|
|
50801
50773
|
return tags;
|
|
50802
50774
|
} catch (err) {
|
|
50803
|
-
|
|
50775
|
+
log15.debug(`Tag suggestion error: ${err}`);
|
|
50804
50776
|
return [];
|
|
50805
50777
|
}
|
|
50806
50778
|
}
|
|
@@ -53893,11 +53865,11 @@ class BugReportExecutor extends BaseExecutor {
|
|
|
53893
53865
|
}
|
|
53894
53866
|
// src/operations/executors/worktree-prompt.ts
|
|
53895
53867
|
init_emoji();
|
|
53896
|
-
var
|
|
53868
|
+
var log16 = createLogger("wt-prompt");
|
|
53897
53869
|
// src/operations/message-manager-events.ts
|
|
53898
|
-
import { EventEmitter as
|
|
53870
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
53899
53871
|
|
|
53900
|
-
class TypedEventEmitter extends
|
|
53872
|
+
class TypedEventEmitter extends EventEmitter3 {
|
|
53901
53873
|
emit(event, data) {
|
|
53902
53874
|
return super.emit(event, data);
|
|
53903
53875
|
}
|
|
@@ -53922,7 +53894,7 @@ function createMessageManagerEvents() {
|
|
|
53922
53894
|
}
|
|
53923
53895
|
|
|
53924
53896
|
// src/operations/message-manager.ts
|
|
53925
|
-
var
|
|
53897
|
+
var log17 = createLogger("msg-mgr");
|
|
53926
53898
|
|
|
53927
53899
|
class MessageManager {
|
|
53928
53900
|
platform;
|
|
@@ -54010,7 +53982,7 @@ class MessageManager {
|
|
|
54010
53982
|
});
|
|
54011
53983
|
}
|
|
54012
53984
|
async handleEvent(event) {
|
|
54013
|
-
const logger =
|
|
53985
|
+
const logger = log17.forSession(this.sessionId);
|
|
54014
53986
|
const transformCtx = {
|
|
54015
53987
|
sessionId: this.sessionId,
|
|
54016
53988
|
formatter: this.platform.getFormatter(),
|
|
@@ -54060,7 +54032,7 @@ class MessageManager {
|
|
|
54060
54032
|
}
|
|
54061
54033
|
}
|
|
54062
54034
|
async executeOperation(op) {
|
|
54063
|
-
const logger =
|
|
54035
|
+
const logger = log17.forSession(this.sessionId);
|
|
54064
54036
|
const ctx = this.getExecutorContext();
|
|
54065
54037
|
try {
|
|
54066
54038
|
if (isContentOp(op)) {
|
|
@@ -54128,7 +54100,7 @@ class MessageManager {
|
|
|
54128
54100
|
threadId: this.threadId,
|
|
54129
54101
|
platform: this.platform,
|
|
54130
54102
|
formatter: this.platform.getFormatter(),
|
|
54131
|
-
logger:
|
|
54103
|
+
logger: log17.forSession(this.sessionId),
|
|
54132
54104
|
postTracker: this.postTracker,
|
|
54133
54105
|
contentBreaker: this.contentBreaker,
|
|
54134
54106
|
threadLogger: this.session.threadLogger,
|
|
@@ -54339,13 +54311,13 @@ class MessageManager {
|
|
|
54339
54311
|
return this.systemExecutor.postSuccess(message, this.getExecutorContext());
|
|
54340
54312
|
}
|
|
54341
54313
|
async prepareForUserMessage() {
|
|
54342
|
-
const logger =
|
|
54314
|
+
const logger = log17.forSession(this.sessionId);
|
|
54343
54315
|
logger.debug("Preparing for new user message");
|
|
54344
54316
|
await this.closeCurrentPost();
|
|
54345
54317
|
await this.bumpTaskList();
|
|
54346
54318
|
}
|
|
54347
54319
|
async handleUserMessage(message, files, username, displayName) {
|
|
54348
|
-
const logger =
|
|
54320
|
+
const logger = log17.forSession(this.sessionId);
|
|
54349
54321
|
if (!this.session.claude.isRunning()) {
|
|
54350
54322
|
logger.debug("Claude not running, ignoring user message");
|
|
54351
54323
|
return false;
|
|
@@ -54368,7 +54340,7 @@ class MessageManager {
|
|
|
54368
54340
|
return this.session;
|
|
54369
54341
|
}
|
|
54370
54342
|
async handleReaction(postId, emoji, user, action) {
|
|
54371
|
-
const logger =
|
|
54343
|
+
const logger = log17.forSession(this.sessionId);
|
|
54372
54344
|
const ctx = this.getExecutorContext();
|
|
54373
54345
|
logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
|
|
54374
54346
|
if (await this.questionApprovalExecutor.handleReaction(postId, emoji, user, action, ctx)) {
|
|
@@ -54676,7 +54648,7 @@ function formatPullRequestLink(url, formatter) {
|
|
|
54676
54648
|
}
|
|
54677
54649
|
|
|
54678
54650
|
// src/operations/sticky-message/handler.ts
|
|
54679
|
-
var
|
|
54651
|
+
var log18 = createLogger("sticky");
|
|
54680
54652
|
var botStartedAt = new Date;
|
|
54681
54653
|
function getPendingPrompts(session) {
|
|
54682
54654
|
const prompts2 = [];
|
|
@@ -54751,21 +54723,21 @@ function initialize(store) {
|
|
|
54751
54723
|
stickyPostIds.set(platformId, postId);
|
|
54752
54724
|
}
|
|
54753
54725
|
if (persistedIds.size > 0) {
|
|
54754
|
-
|
|
54726
|
+
log18.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
|
|
54755
54727
|
}
|
|
54756
54728
|
}
|
|
54757
54729
|
function setPlatformPaused(platformId, paused) {
|
|
54758
54730
|
if (paused) {
|
|
54759
54731
|
pausedPlatforms.set(platformId, true);
|
|
54760
|
-
|
|
54732
|
+
log18.debug(`Platform ${platformId} marked as paused`);
|
|
54761
54733
|
} else {
|
|
54762
54734
|
pausedPlatforms.delete(platformId);
|
|
54763
|
-
|
|
54735
|
+
log18.debug(`Platform ${platformId} marked as active`);
|
|
54764
54736
|
}
|
|
54765
54737
|
}
|
|
54766
54738
|
function setShuttingDown(shuttingDown) {
|
|
54767
54739
|
isShuttingDown = shuttingDown;
|
|
54768
|
-
|
|
54740
|
+
log18.debug(`Bot shutdown state: ${shuttingDown}`);
|
|
54769
54741
|
}
|
|
54770
54742
|
function getTaskContent(session) {
|
|
54771
54743
|
const taskState = session.messageManager?.getTaskListState();
|
|
@@ -55044,64 +55016,89 @@ async function updateStickyMessage(platform, sessions, config) {
|
|
|
55044
55016
|
updateLocks.delete(platformId);
|
|
55045
55017
|
}
|
|
55046
55018
|
}
|
|
55019
|
+
async function validateLastMessageIds(platform, sessions) {
|
|
55020
|
+
const sessionsWithLastMessage = sessions.filter((s) => s.lastMessageId);
|
|
55021
|
+
if (sessionsWithLastMessage.length === 0) {
|
|
55022
|
+
return;
|
|
55023
|
+
}
|
|
55024
|
+
const validationPromises = sessionsWithLastMessage.map(async (session) => {
|
|
55025
|
+
const lastMessageId = session.lastMessageId;
|
|
55026
|
+
if (!lastMessageId)
|
|
55027
|
+
return;
|
|
55028
|
+
try {
|
|
55029
|
+
const post2 = await platform.getPost(lastMessageId);
|
|
55030
|
+
if (!post2) {
|
|
55031
|
+
log18.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
|
|
55032
|
+
session.lastMessageId = undefined;
|
|
55033
|
+
session.lastMessageTs = undefined;
|
|
55034
|
+
}
|
|
55035
|
+
} catch (err) {
|
|
55036
|
+
log18.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
|
|
55037
|
+
session.lastMessageId = undefined;
|
|
55038
|
+
session.lastMessageTs = undefined;
|
|
55039
|
+
}
|
|
55040
|
+
});
|
|
55041
|
+
await Promise.all(validationPromises);
|
|
55042
|
+
}
|
|
55047
55043
|
async function updateStickyMessageImpl(platform, sessions, config) {
|
|
55048
55044
|
const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
|
|
55049
|
-
|
|
55045
|
+
log18.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
|
|
55050
55046
|
for (const s of platformSessions) {
|
|
55051
|
-
|
|
55047
|
+
log18.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
|
|
55052
55048
|
}
|
|
55049
|
+
await validateLastMessageIds(platform, platformSessions);
|
|
55053
55050
|
const formatter = platform.getFormatter();
|
|
55054
55051
|
const content = await buildStickyMessage(sessions, platform.platformId, config, formatter, (threadId) => platform.getThreadLink(threadId));
|
|
55055
55052
|
const existingPostId = stickyPostIds.get(platform.platformId);
|
|
55056
55053
|
const shouldBump = needsBump.get(platform.platformId) ?? false;
|
|
55057
|
-
|
|
55054
|
+
log18.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
|
|
55058
55055
|
try {
|
|
55059
55056
|
if (existingPostId && !shouldBump) {
|
|
55060
|
-
|
|
55057
|
+
log18.debug(`Updating existing post in place...`);
|
|
55061
55058
|
try {
|
|
55062
55059
|
await platform.updatePost(existingPostId, content);
|
|
55063
55060
|
try {
|
|
55064
55061
|
await platform.pinPost(existingPostId);
|
|
55065
|
-
|
|
55062
|
+
log18.debug(`Re-pinned post`);
|
|
55066
55063
|
} catch (pinErr) {
|
|
55067
|
-
|
|
55064
|
+
log18.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
|
|
55068
55065
|
}
|
|
55069
|
-
|
|
55066
|
+
log18.debug(`Updated successfully`);
|
|
55070
55067
|
return;
|
|
55071
55068
|
} catch (err) {
|
|
55072
|
-
|
|
55069
|
+
log18.debug(`Update failed, will create new: ${err}`);
|
|
55073
55070
|
}
|
|
55074
55071
|
}
|
|
55075
55072
|
needsBump.set(platform.platformId, false);
|
|
55076
55073
|
if (existingPostId) {
|
|
55077
|
-
|
|
55074
|
+
log18.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
|
|
55078
55075
|
try {
|
|
55079
55076
|
await platform.unpinPost(existingPostId);
|
|
55080
|
-
|
|
55077
|
+
log18.debug(`Unpinned successfully`);
|
|
55081
55078
|
} catch (err) {
|
|
55082
|
-
|
|
55079
|
+
log18.debug(`Unpin failed (probably already unpinned): ${err}`);
|
|
55083
55080
|
}
|
|
55084
55081
|
try {
|
|
55085
55082
|
await platform.deletePost(existingPostId);
|
|
55086
|
-
|
|
55083
|
+
log18.debug(`Deleted successfully`);
|
|
55087
55084
|
} catch (err) {
|
|
55088
|
-
|
|
55085
|
+
log18.debug(`Delete failed (probably already deleted): ${err}`);
|
|
55089
55086
|
}
|
|
55090
55087
|
stickyPostIds.delete(platform.platformId);
|
|
55091
55088
|
}
|
|
55092
|
-
|
|
55089
|
+
log18.debug(`Creating new post...`);
|
|
55093
55090
|
const post2 = await platform.createPost(content);
|
|
55094
55091
|
stickyPostIds.set(platform.platformId, post2.id);
|
|
55095
55092
|
try {
|
|
55096
55093
|
await platform.pinPost(post2.id);
|
|
55097
|
-
|
|
55094
|
+
log18.debug(`Pinned post successfully`);
|
|
55098
55095
|
} catch (err) {
|
|
55099
|
-
|
|
55096
|
+
log18.debug(`Failed to pin post: ${err}`);
|
|
55100
55097
|
}
|
|
55101
55098
|
if (sessionStore) {
|
|
55102
55099
|
sessionStore.saveStickyPostId(platform.platformId, post2.id);
|
|
55103
55100
|
}
|
|
55104
|
-
|
|
55101
|
+
log18.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
|
|
55105
55102
|
const excludePostIds = new Set;
|
|
55106
55103
|
if (sessionStore) {
|
|
55107
55104
|
for (const session of sessionStore.load().values()) {
|
|
@@ -55117,10 +55114,10 @@ async function updateStickyMessageImpl(platform, sessions, config) {
|
|
|
55117
55114
|
}
|
|
55118
55115
|
const botUser = await platform.getBotUser();
|
|
55119
55116
|
cleanupOldStickyMessages(platform, botUser.id, false, excludePostIds).catch((err) => {
|
|
55120
|
-
|
|
55117
|
+
log18.debug(`Background cleanup failed: ${err}`);
|
|
55121
55118
|
});
|
|
55122
55119
|
} catch (err) {
|
|
55123
|
-
|
|
55120
|
+
log18.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
|
|
55124
55121
|
}
|
|
55125
55122
|
}
|
|
55126
55123
|
async function updateAllStickyMessages(platforms, sessions, config) {
|
|
@@ -55145,7 +55142,7 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
|
|
|
55145
55142
|
if (!forceRun) {
|
|
55146
55143
|
const lastRun = lastCleanupTime.get(platformId) || 0;
|
|
55147
55144
|
if (now - lastRun < CLEANUP_THROTTLE_MS) {
|
|
55148
|
-
|
|
55145
|
+
log18.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
|
|
55149
55146
|
return;
|
|
55150
55147
|
}
|
|
55151
55148
|
}
|
|
@@ -55155,31 +55152,31 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
|
|
|
55155
55152
|
const pinnedPostIds = await platform.getPinnedPosts();
|
|
55156
55153
|
const recentPinnedIds = pinnedPostIds.filter((id) => id !== currentStickyId && !excludePostIds?.has(id) && isRecentPost(id));
|
|
55157
55154
|
if (recentPinnedIds.length === 0) {
|
|
55158
|
-
|
|
55155
|
+
log18.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
|
|
55159
55156
|
return;
|
|
55160
55157
|
}
|
|
55161
|
-
|
|
55158
|
+
log18.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
|
|
55162
55159
|
for (const postId of recentPinnedIds) {
|
|
55163
55160
|
try {
|
|
55164
55161
|
const post2 = await platform.getPost(postId);
|
|
55165
55162
|
if (!post2)
|
|
55166
55163
|
continue;
|
|
55167
55164
|
if (post2.userId === botUserId) {
|
|
55168
|
-
|
|
55165
|
+
log18.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
|
|
55169
55166
|
try {
|
|
55170
55167
|
await platform.unpinPost(postId);
|
|
55171
55168
|
await platform.deletePost(postId);
|
|
55172
|
-
|
|
55169
|
+
log18.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
|
|
55173
55170
|
} catch (err) {
|
|
55174
|
-
|
|
55171
|
+
log18.debug(`Failed to cleanup ${postId}: ${err}`);
|
|
55175
55172
|
}
|
|
55176
55173
|
}
|
|
55177
55174
|
} catch (err) {
|
|
55178
|
-
|
|
55175
|
+
log18.debug(`Could not check post ${postId}: ${err}`);
|
|
55179
55176
|
}
|
|
55180
55177
|
}
|
|
55181
55178
|
} catch (err) {
|
|
55182
|
-
|
|
55179
|
+
log18.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
|
|
55183
55180
|
}
|
|
55184
55181
|
}
|
|
55185
55182
|
// src/operations/bug-report/handler.ts
|
|
@@ -59249,8 +59246,8 @@ function getUpdateInfo() {
|
|
|
59249
59246
|
|
|
59250
59247
|
// src/operations/commands/handler.ts
|
|
59251
59248
|
init_emoji();
|
|
59252
|
-
var
|
|
59253
|
-
var sessionLog2 = createSessionLog(
|
|
59249
|
+
var log19 = createLogger("commands");
|
|
59250
|
+
var sessionLog2 = createSessionLog(log19);
|
|
59254
59251
|
async function restartClaudeSession(session, cliOptions, ctx, actionName) {
|
|
59255
59252
|
ctx.ops.stopTyping(session);
|
|
59256
59253
|
transitionTo(session, "restarting");
|
|
@@ -59583,7 +59580,6 @@ async function updateSessionHeader(session, ctx) {
|
|
|
59583
59580
|
`);
|
|
59584
59581
|
const postId = session.sessionStartPostId;
|
|
59585
59582
|
await updatePost2(session, postId, msg);
|
|
59586
|
-
session.platform.pinPost(postId).catch(() => {});
|
|
59587
59583
|
}
|
|
59588
59584
|
async function showUpdateStatus(session, updateManager, ctx) {
|
|
59589
59585
|
const formatter = session.platform.getFormatter();
|
|
@@ -59721,7 +59717,7 @@ async function handleBugReportApproval(session, isApproved, username) {
|
|
|
59721
59717
|
import { exec as exec3 } from "child_process";
|
|
59722
59718
|
import { promisify as promisify3 } from "util";
|
|
59723
59719
|
var execAsync2 = promisify3(exec3);
|
|
59724
|
-
var
|
|
59720
|
+
var log20 = createLogger("branch");
|
|
59725
59721
|
var SUGGESTION_TIMEOUT3 = 15000;
|
|
59726
59722
|
var MAX_SUGGESTIONS = 3;
|
|
59727
59723
|
async function getCurrentBranch3(workingDir) {
|
|
@@ -59770,7 +59766,7 @@ function parseBranchSuggestions(response) {
|
|
|
59770
59766
|
return lines.slice(0, MAX_SUGGESTIONS);
|
|
59771
59767
|
}
|
|
59772
59768
|
async function suggestBranchNames(workingDir, userMessage) {
|
|
59773
|
-
|
|
59769
|
+
log20.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
|
|
59774
59770
|
try {
|
|
59775
59771
|
const [currentBranch, recentCommits] = await Promise.all([
|
|
59776
59772
|
getCurrentBranch3(workingDir),
|
|
@@ -59784,22 +59780,22 @@ async function suggestBranchNames(workingDir, userMessage) {
|
|
|
59784
59780
|
workingDir
|
|
59785
59781
|
});
|
|
59786
59782
|
if (!result.success || !result.response) {
|
|
59787
|
-
|
|
59783
|
+
log20.debug(`Branch suggestion failed: ${result.error || "no response"}`);
|
|
59788
59784
|
return [];
|
|
59789
59785
|
}
|
|
59790
59786
|
const suggestions = parseBranchSuggestions(result.response);
|
|
59791
|
-
|
|
59787
|
+
log20.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
|
|
59792
59788
|
return suggestions;
|
|
59793
59789
|
} catch (err) {
|
|
59794
|
-
|
|
59790
|
+
log20.debug(`Branch suggestion error: ${err}`);
|
|
59795
59791
|
return [];
|
|
59796
59792
|
}
|
|
59797
59793
|
}
|
|
59798
59794
|
|
|
59799
59795
|
// src/operations/worktree/handler.ts
|
|
59800
59796
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
59801
|
-
var
|
|
59802
|
-
var sessionLog3 = createSessionLog(
|
|
59797
|
+
var log21 = createLogger("worktree");
|
|
59798
|
+
var sessionLog3 = createSessionLog(log21);
|
|
59803
59799
|
function parseWorktreeError(error) {
|
|
59804
59800
|
const message = error instanceof Error ? error.message : String(error);
|
|
59805
59801
|
const lowerMessage = message.toLowerCase();
|
|
@@ -60327,8 +60323,8 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
|
|
|
60327
60323
|
}
|
|
60328
60324
|
}
|
|
60329
60325
|
// src/operations/events/handler.ts
|
|
60330
|
-
var
|
|
60331
|
-
var sessionLog4 = createSessionLog(
|
|
60326
|
+
var log22 = createLogger("events");
|
|
60327
|
+
var sessionLog4 = createSessionLog(log22);
|
|
60332
60328
|
function detectAndExecuteClaudeCommands(text, session, ctx) {
|
|
60333
60329
|
const parsed = parseClaudeCommand(text);
|
|
60334
60330
|
if (parsed && isClaudeAllowedCommand(parsed.command)) {
|
|
@@ -60571,8 +60567,8 @@ function createSessionContext(config, state, ops) {
|
|
|
60571
60567
|
}
|
|
60572
60568
|
// src/operations/context-prompt/handler.ts
|
|
60573
60569
|
init_emoji();
|
|
60574
|
-
var
|
|
60575
|
-
var sessionLog5 = createSessionLog(
|
|
60570
|
+
var log23 = createLogger("context");
|
|
60571
|
+
var sessionLog5 = createSessionLog(log23);
|
|
60576
60572
|
var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
|
|
60577
60573
|
var CONTEXT_OPTIONS = [3, 5, 10];
|
|
60578
60574
|
var contextPromptTimeouts = new Map;
|
|
@@ -60758,7 +60754,7 @@ async function offerContextPrompt(session, queuedPrompt, queuedFiles, ctx, exclu
|
|
|
60758
60754
|
return true;
|
|
60759
60755
|
}
|
|
60760
60756
|
// src/operations/streaming/handler.ts
|
|
60761
|
-
var
|
|
60757
|
+
var log24 = createLogger("streaming");
|
|
60762
60758
|
async function buildMessageContent(text, platform, files, debug = false) {
|
|
60763
60759
|
const imageFiles = files?.filter((f) => f.mimeType.startsWith("image/") && ["image/jpeg", "image/png", "image/gif", "image/webp"].includes(f.mimeType)) || [];
|
|
60764
60760
|
if (imageFiles.length === 0) {
|
|
@@ -60768,7 +60764,7 @@ async function buildMessageContent(text, platform, files, debug = false) {
|
|
|
60768
60764
|
for (const file of imageFiles) {
|
|
60769
60765
|
try {
|
|
60770
60766
|
if (!platform.downloadFile) {
|
|
60771
|
-
|
|
60767
|
+
log24.warn(`Platform does not support file downloads, skipping ${file.name}`);
|
|
60772
60768
|
continue;
|
|
60773
60769
|
}
|
|
60774
60770
|
const buffer = await platform.downloadFile(file.id);
|
|
@@ -60782,10 +60778,10 @@ async function buildMessageContent(text, platform, files, debug = false) {
|
|
|
60782
60778
|
}
|
|
60783
60779
|
});
|
|
60784
60780
|
if (debug) {
|
|
60785
|
-
|
|
60781
|
+
log24.debug(`Attached image: ${file.name} (${file.mimeType}, ${Math.round(buffer.length / 1024)}KB)`);
|
|
60786
60782
|
}
|
|
60787
60783
|
} catch (err) {
|
|
60788
|
-
|
|
60784
|
+
log24.error(`Failed to download image ${file.name}: ${err}`);
|
|
60789
60785
|
}
|
|
60790
60786
|
}
|
|
60791
60787
|
if (text) {
|
|
@@ -60811,8 +60807,8 @@ function stopTyping(session) {
|
|
|
60811
60807
|
}
|
|
60812
60808
|
}
|
|
60813
60809
|
// src/session/lifecycle.ts
|
|
60814
|
-
var
|
|
60815
|
-
var sessionLog6 = createSessionLog(
|
|
60810
|
+
var log25 = createLogger("lifecycle");
|
|
60811
|
+
var sessionLog6 = createSessionLog(log25);
|
|
60816
60812
|
function mutableSessions(ctx) {
|
|
60817
60813
|
return ctx.state.sessions;
|
|
60818
60814
|
}
|
|
@@ -60971,35 +60967,76 @@ function createMessageManager(session, ctx) {
|
|
|
60971
60967
|
});
|
|
60972
60968
|
return messageManager;
|
|
60973
60969
|
}
|
|
60970
|
+
var METADATA_RETRY_DELAY_MS = 2000;
|
|
60971
|
+
var METADATA_MAX_RETRIES = 2;
|
|
60972
|
+
async function attemptMetadataFetch(session, prompt, ctx, attempt = 1, options = {}) {
|
|
60973
|
+
const sessionId = session.sessionId;
|
|
60974
|
+
const suggestMetadataFn = options.suggestMetadata ?? suggestSessionMetadata;
|
|
60975
|
+
const suggestTagsFn = options.suggestTags ?? suggestSessionTags;
|
|
60976
|
+
const [metadata, tags] = await Promise.all([
|
|
60977
|
+
suggestMetadataFn(prompt),
|
|
60978
|
+
suggestTagsFn(prompt)
|
|
60979
|
+
]);
|
|
60980
|
+
const currentSession = ctx.state.sessions.get(sessionId);
|
|
60981
|
+
if (!currentSession) {
|
|
60982
|
+
sessionLog6(session).debug("Session gone before metadata suggestions completed");
|
|
60983
|
+
return { success: false, metadataSet: false, tagsSet: false };
|
|
60984
|
+
}
|
|
60985
|
+
let metadataSet = false;
|
|
60986
|
+
let tagsSet = false;
|
|
60987
|
+
let updated = false;
|
|
60988
|
+
if (metadata && !currentSession.sessionTitle) {
|
|
60989
|
+
currentSession.sessionTitle = metadata.title;
|
|
60990
|
+
currentSession.sessionDescription = metadata.description;
|
|
60991
|
+
sessionLog6(currentSession).debug(`Set title: "${metadata.title}" (attempt ${attempt})`);
|
|
60992
|
+
metadataSet = true;
|
|
60993
|
+
updated = true;
|
|
60994
|
+
} else if (currentSession.sessionTitle) {
|
|
60995
|
+
metadataSet = true;
|
|
60996
|
+
}
|
|
60997
|
+
if (tags.length > 0 && (!currentSession.sessionTags || currentSession.sessionTags.length === 0)) {
|
|
60998
|
+
currentSession.sessionTags = tags;
|
|
60999
|
+
sessionLog6(currentSession).debug(`Set tags: ${tags.join(", ")} (attempt ${attempt})`);
|
|
61000
|
+
tagsSet = true;
|
|
61001
|
+
updated = true;
|
|
61002
|
+
} else if (currentSession.sessionTags && currentSession.sessionTags.length > 0) {
|
|
61003
|
+
tagsSet = true;
|
|
61004
|
+
}
|
|
61005
|
+
if (updated) {
|
|
61006
|
+
ctx.ops.persistSession(currentSession);
|
|
61007
|
+
await ctx.ops.updateStickyMessage();
|
|
61008
|
+
await ctx.ops.updateSessionHeader(currentSession);
|
|
61009
|
+
}
|
|
61010
|
+
return { success: metadataSet && tagsSet, metadataSet, tagsSet };
|
|
61011
|
+
}
|
|
60974
61012
|
function fireMetadataSuggestions(session, prompt, ctx) {
|
|
60975
61013
|
(async () => {
|
|
60976
61014
|
try {
|
|
60977
|
-
|
|
60978
|
-
|
|
60979
|
-
|
|
60980
|
-
|
|
60981
|
-
|
|
60982
|
-
|
|
60983
|
-
|
|
60984
|
-
|
|
60985
|
-
|
|
60986
|
-
|
|
60987
|
-
|
|
60988
|
-
|
|
60989
|
-
|
|
60990
|
-
|
|
60991
|
-
sessionLog6(
|
|
60992
|
-
|
|
60993
|
-
|
|
60994
|
-
|
|
60995
|
-
|
|
60996
|
-
|
|
60997
|
-
|
|
60998
|
-
|
|
60999
|
-
|
|
61000
|
-
|
|
61001
|
-
|
|
61002
|
-
await ctx.ops.updateSessionHeader(currentSession);
|
|
61015
|
+
let result = await attemptMetadataFetch(session, prompt, ctx, 1);
|
|
61016
|
+
let attempt = 1;
|
|
61017
|
+
while (!result.success && attempt < METADATA_MAX_RETRIES + 1) {
|
|
61018
|
+
attempt++;
|
|
61019
|
+
const currentSession = ctx.state.sessions.get(session.sessionId);
|
|
61020
|
+
if (!currentSession) {
|
|
61021
|
+
sessionLog6(session).debug("Session gone, stopping metadata retries");
|
|
61022
|
+
return;
|
|
61023
|
+
}
|
|
61024
|
+
const missing = [];
|
|
61025
|
+
if (!result.metadataSet)
|
|
61026
|
+
missing.push("title/description");
|
|
61027
|
+
if (!result.tagsSet)
|
|
61028
|
+
missing.push("tags");
|
|
61029
|
+
sessionLog6(session).debug(`Retrying metadata fetch for ${missing.join(", ")} (attempt ${attempt}/${METADATA_MAX_RETRIES + 1})`);
|
|
61030
|
+
await new Promise((resolve6) => setTimeout(resolve6, METADATA_RETRY_DELAY_MS));
|
|
61031
|
+
result = await attemptMetadataFetch(session, prompt, ctx, attempt);
|
|
61032
|
+
}
|
|
61033
|
+
if (!result.success) {
|
|
61034
|
+
const missing = [];
|
|
61035
|
+
if (!result.metadataSet)
|
|
61036
|
+
missing.push("title/description");
|
|
61037
|
+
if (!result.tagsSet)
|
|
61038
|
+
missing.push("tags");
|
|
61039
|
+
sessionLog6(session).debug(`Metadata fetch incomplete after ${attempt} attempts: missing ${missing.join(", ")}`);
|
|
61003
61040
|
}
|
|
61004
61041
|
} catch (err) {
|
|
61005
61042
|
sessionLog6(session).debug(`Metadata suggestion error: ${err}`);
|
|
@@ -61153,9 +61190,6 @@ ${CHAT_PLATFORM_PROMPT}`;
|
|
|
61153
61190
|
fireMetadataSuggestions(session, options.prompt, ctx);
|
|
61154
61191
|
keepAlive.sessionStarted();
|
|
61155
61192
|
await ctx.ops.updateSessionHeader(session);
|
|
61156
|
-
if (session.sessionStartPostId) {
|
|
61157
|
-
await platform.pinPost(session.sessionStartPostId).catch(() => {});
|
|
61158
|
-
}
|
|
61159
61193
|
await ctx.ops.updateStickyMessage();
|
|
61160
61194
|
ctx.ops.startTyping(session);
|
|
61161
61195
|
claude.on("event", (e) => ctx.ops.handleEvent(sessionId, e));
|
|
@@ -61199,28 +61233,28 @@ async function resumeSession(state, ctx) {
|
|
|
61199
61233
|
!state.claudeSessionId && "claudeSessionId",
|
|
61200
61234
|
!state.workingDir && "workingDir"
|
|
61201
61235
|
].filter(Boolean).join(", ");
|
|
61202
|
-
|
|
61236
|
+
log25.warn(`Skipping session with missing required fields: ${missing}`);
|
|
61203
61237
|
return;
|
|
61204
61238
|
}
|
|
61205
61239
|
const shortId = state.threadId.substring(0, 8);
|
|
61206
61240
|
const platforms = ctx.state.platforms;
|
|
61207
61241
|
const platform = platforms.get(state.platformId);
|
|
61208
61242
|
if (!platform) {
|
|
61209
|
-
|
|
61243
|
+
log25.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
|
|
61210
61244
|
return;
|
|
61211
61245
|
}
|
|
61212
61246
|
const threadPost = await platform.getPost(state.threadId);
|
|
61213
61247
|
if (!threadPost) {
|
|
61214
|
-
|
|
61248
|
+
log25.warn(`Thread ${shortId}... deleted, skipping resume`);
|
|
61215
61249
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
61216
61250
|
return;
|
|
61217
61251
|
}
|
|
61218
61252
|
if (ctx.state.sessions.size >= ctx.config.maxSessions) {
|
|
61219
|
-
|
|
61253
|
+
log25.warn(`Max sessions reached, skipping resume for ${shortId}...`);
|
|
61220
61254
|
return;
|
|
61221
61255
|
}
|
|
61222
61256
|
if (!existsSync10(state.workingDir)) {
|
|
61223
|
-
|
|
61257
|
+
log25.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
|
|
61224
61258
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
61225
61259
|
const resumeFormatter = platform.getFormatter();
|
|
61226
61260
|
const tempSession = {
|
|
@@ -61303,7 +61337,7 @@ ${CHAT_PLATFORM_PROMPT}`;
|
|
|
61303
61337
|
worktreePath: detected.worktreePath,
|
|
61304
61338
|
branch: detected.branch
|
|
61305
61339
|
};
|
|
61306
|
-
|
|
61340
|
+
log25.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
|
|
61307
61341
|
}
|
|
61308
61342
|
}
|
|
61309
61343
|
session.messageManager = createMessageManager(session, ctx);
|
|
@@ -61355,13 +61389,10 @@ ${sessionFormatter.formatItalic("Reconnected to Claude session. You can continue
|
|
|
61355
61389
|
await post(session, "resume", restartMsg);
|
|
61356
61390
|
}
|
|
61357
61391
|
await ctx.ops.updateSessionHeader(session);
|
|
61358
|
-
if (session.sessionStartPostId) {
|
|
61359
|
-
await session.platform.pinPost(session.sessionStartPostId).catch(() => {});
|
|
61360
|
-
}
|
|
61361
61392
|
await ctx.ops.updateStickyMessage();
|
|
61362
61393
|
ctx.ops.persistSession(session);
|
|
61363
61394
|
} catch (err) {
|
|
61364
|
-
|
|
61395
|
+
log25.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
|
|
61365
61396
|
ctx.ops.emitSessionRemove(sessionId);
|
|
61366
61397
|
mutableSessions(ctx).delete(sessionId);
|
|
61367
61398
|
ctx.state.sessionStore.remove(sessionId);
|
|
@@ -61396,18 +61427,18 @@ async function resumePausedSession(threadId, message, files, ctx) {
|
|
|
61396
61427
|
const persisted = ctx.state.sessionStore.load();
|
|
61397
61428
|
const state = findPersistedByThreadId(persisted, threadId);
|
|
61398
61429
|
if (!state) {
|
|
61399
|
-
|
|
61430
|
+
log25.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
|
|
61400
61431
|
return;
|
|
61401
61432
|
}
|
|
61402
61433
|
const shortId = threadId.substring(0, 8);
|
|
61403
|
-
|
|
61434
|
+
log25.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
|
|
61404
61435
|
await resumeSession(state, ctx);
|
|
61405
61436
|
const session = ctx.ops.findSessionByThreadId(threadId);
|
|
61406
61437
|
if (session && session.claude.isRunning() && session.messageManager) {
|
|
61407
61438
|
session.messageCount++;
|
|
61408
61439
|
await session.messageManager.handleUserMessage(message, files, state.startedBy);
|
|
61409
61440
|
} else {
|
|
61410
|
-
|
|
61441
|
+
log25.warn(`Failed to resume session ${shortId}..., could not send message`);
|
|
61411
61442
|
}
|
|
61412
61443
|
}
|
|
61413
61444
|
async function handleExit(sessionId, code, ctx) {
|
|
@@ -61415,7 +61446,7 @@ async function handleExit(sessionId, code, ctx) {
|
|
|
61415
61446
|
const shortId = sessionId.substring(0, 8);
|
|
61416
61447
|
sessionLog6(session).debug(`handleExit called code=${code} isShuttingDown=${ctx.state.isShuttingDown}`);
|
|
61417
61448
|
if (!session) {
|
|
61418
|
-
|
|
61449
|
+
log25.debug(`Session ${shortId}... not found (already cleaned up)`);
|
|
61419
61450
|
return;
|
|
61420
61451
|
}
|
|
61421
61452
|
if (isSessionRestarting(session)) {
|
|
@@ -61608,7 +61639,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
|
|
|
61608
61639
|
}
|
|
61609
61640
|
|
|
61610
61641
|
// src/operations/monitor/handler.ts
|
|
61611
|
-
var
|
|
61642
|
+
var log26 = createLogger("monitor");
|
|
61612
61643
|
var DEFAULT_INTERVAL_MS = 60 * 1000;
|
|
61613
61644
|
|
|
61614
61645
|
class SessionMonitor {
|
|
@@ -61630,14 +61661,14 @@ class SessionMonitor {
|
|
|
61630
61661
|
}
|
|
61631
61662
|
start() {
|
|
61632
61663
|
if (this.isRunning) {
|
|
61633
|
-
|
|
61664
|
+
log26.debug("Session monitor already running");
|
|
61634
61665
|
return;
|
|
61635
61666
|
}
|
|
61636
61667
|
this.isRunning = true;
|
|
61637
|
-
|
|
61668
|
+
log26.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
|
|
61638
61669
|
this.timer = setInterval(() => {
|
|
61639
61670
|
this.runCheck().catch((err) => {
|
|
61640
|
-
|
|
61671
|
+
log26.error(`Error during session monitoring: ${err}`);
|
|
61641
61672
|
});
|
|
61642
61673
|
}, this.intervalMs);
|
|
61643
61674
|
}
|
|
@@ -61647,7 +61678,7 @@ class SessionMonitor {
|
|
|
61647
61678
|
this.timer = null;
|
|
61648
61679
|
}
|
|
61649
61680
|
this.isRunning = false;
|
|
61650
|
-
|
|
61681
|
+
log26.debug("Session monitor stopped");
|
|
61651
61682
|
}
|
|
61652
61683
|
async runCheck() {
|
|
61653
61684
|
await cleanupIdleSessions(this.sessionTimeoutMs, this.sessionWarningMs, this.getContext());
|
|
@@ -61770,9 +61801,9 @@ class SessionRegistry {
|
|
|
61770
61801
|
}
|
|
61771
61802
|
|
|
61772
61803
|
// src/session/manager.ts
|
|
61773
|
-
var
|
|
61804
|
+
var log27 = createLogger("manager");
|
|
61774
61805
|
|
|
61775
|
-
class SessionManager extends
|
|
61806
|
+
class SessionManager extends EventEmitter4 {
|
|
61776
61807
|
platforms = new Map;
|
|
61777
61808
|
workingDir;
|
|
61778
61809
|
skipPermissions;
|
|
@@ -61835,7 +61866,7 @@ class SessionManager extends EventEmitter5 {
|
|
|
61835
61866
|
markNeedsBump(platformId);
|
|
61836
61867
|
this.updateStickyMessage();
|
|
61837
61868
|
});
|
|
61838
|
-
|
|
61869
|
+
log27.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
|
|
61839
61870
|
}
|
|
61840
61871
|
removePlatform(platformId) {
|
|
61841
61872
|
this.platforms.delete(platformId);
|
|
@@ -61851,7 +61882,7 @@ class SessionManager extends EventEmitter5 {
|
|
|
61851
61882
|
if (users) {
|
|
61852
61883
|
users.add(sessionId);
|
|
61853
61884
|
}
|
|
61854
|
-
|
|
61885
|
+
log27.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
|
|
61855
61886
|
}
|
|
61856
61887
|
unregisterWorktreeUser(worktreePath, sessionId) {
|
|
61857
61888
|
const users = this.worktreeUsers.get(worktreePath);
|
|
@@ -61998,7 +62029,7 @@ class SessionManager extends EventEmitter5 {
|
|
|
61998
62029
|
return false;
|
|
61999
62030
|
}
|
|
62000
62031
|
const shortId = persistedSession.threadId.substring(0, 8);
|
|
62001
|
-
|
|
62032
|
+
log27.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
|
|
62002
62033
|
await resumeSession(persistedSession, this.getContext());
|
|
62003
62034
|
return true;
|
|
62004
62035
|
}
|
|
@@ -62028,7 +62059,7 @@ class SessionManager extends EventEmitter5 {
|
|
|
62028
62059
|
}
|
|
62029
62060
|
if (session.lastError?.postId === postId && isBugReportEmoji(emojiName)) {
|
|
62030
62061
|
if (session.startedBy === username || session.platform.isUserAllowed(username) || session.sessionAllowedUsers.has(username)) {
|
|
62031
|
-
|
|
62062
|
+
log27.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
|
|
62032
62063
|
await reportBug(session, undefined, username, this.getContext(), session.lastError);
|
|
62033
62064
|
return;
|
|
62034
62065
|
}
|
|
@@ -62188,11 +62219,11 @@ class SessionManager extends EventEmitter5 {
|
|
|
62188
62219
|
}
|
|
62189
62220
|
}
|
|
62190
62221
|
if (sessionsToKill.length === 0) {
|
|
62191
|
-
|
|
62222
|
+
log27.info(`No active sessions to pause for platform ${platformId}`);
|
|
62192
62223
|
await this.updateStickyMessage();
|
|
62193
62224
|
return;
|
|
62194
62225
|
}
|
|
62195
|
-
|
|
62226
|
+
log27.info(`\u23F8\uFE0F Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
|
|
62196
62227
|
for (const session of sessionsToKill) {
|
|
62197
62228
|
try {
|
|
62198
62229
|
const fmt = session.platform.getFormatter();
|
|
@@ -62208,9 +62239,9 @@ class SessionManager extends EventEmitter5 {
|
|
|
62208
62239
|
session.claude.kill();
|
|
62209
62240
|
this.registry.unregister(session.sessionId);
|
|
62210
62241
|
this.emitSessionRemove(session.sessionId);
|
|
62211
|
-
|
|
62242
|
+
log27.info(`\u23F8\uFE0F Paused session ${session.threadId.substring(0, 8)}`);
|
|
62212
62243
|
} catch (err) {
|
|
62213
|
-
|
|
62244
|
+
log27.warn(`Failed to pause session ${session.threadId}: ${err}`);
|
|
62214
62245
|
}
|
|
62215
62246
|
}
|
|
62216
62247
|
for (const session of sessionsToKill) {
|
|
@@ -62231,17 +62262,17 @@ class SessionManager extends EventEmitter5 {
|
|
|
62231
62262
|
sessionsToResume.push(state);
|
|
62232
62263
|
}
|
|
62233
62264
|
if (sessionsToResume.length === 0) {
|
|
62234
|
-
|
|
62265
|
+
log27.info(`No paused sessions to resume for platform ${platformId}`);
|
|
62235
62266
|
await this.updateStickyMessage();
|
|
62236
62267
|
return;
|
|
62237
62268
|
}
|
|
62238
|
-
|
|
62269
|
+
log27.info(`\u25B6\uFE0F Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
|
|
62239
62270
|
for (const state of sessionsToResume) {
|
|
62240
62271
|
try {
|
|
62241
62272
|
await resumeSession(state, this.getContext());
|
|
62242
|
-
|
|
62273
|
+
log27.info(`\u25B6\uFE0F Resumed session ${state.threadId.substring(0, 8)}`);
|
|
62243
62274
|
} catch (err) {
|
|
62244
|
-
|
|
62275
|
+
log27.warn(`Failed to resume session ${state.threadId}: ${err}`);
|
|
62245
62276
|
}
|
|
62246
62277
|
}
|
|
62247
62278
|
await this.updateStickyMessage();
|
|
@@ -62253,14 +62284,14 @@ class SessionManager extends EventEmitter5 {
|
|
|
62253
62284
|
const sessionTimeoutMs = this.limits.sessionTimeoutMinutes * 60 * 1000;
|
|
62254
62285
|
const staleIds = this.sessionStore.cleanStale(sessionTimeoutMs * 2);
|
|
62255
62286
|
if (staleIds.length > 0) {
|
|
62256
|
-
|
|
62287
|
+
log27.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
|
|
62257
62288
|
}
|
|
62258
62289
|
const removedCount = this.sessionStore.cleanHistory();
|
|
62259
62290
|
if (removedCount > 0) {
|
|
62260
|
-
|
|
62291
|
+
log27.info(`\uD83D\uDDD1\uFE0F Permanently removed ${removedCount} old session(s) from history`);
|
|
62261
62292
|
}
|
|
62262
62293
|
const persisted = this.sessionStore.load();
|
|
62263
|
-
|
|
62294
|
+
log27.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
|
|
62264
62295
|
const excludePostIdsByPlatform = new Map;
|
|
62265
62296
|
for (const session of persisted.values()) {
|
|
62266
62297
|
const platformId = session.platformId;
|
|
@@ -62280,10 +62311,10 @@ class SessionManager extends EventEmitter5 {
|
|
|
62280
62311
|
const excludePostIds = excludePostIdsByPlatform.get(platform.platformId);
|
|
62281
62312
|
platform.getBotUser().then((botUser) => {
|
|
62282
62313
|
cleanupOldStickyMessages(platform, botUser.id, true, excludePostIds).catch((err) => {
|
|
62283
|
-
|
|
62314
|
+
log27.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
|
|
62284
62315
|
});
|
|
62285
62316
|
}).catch((err) => {
|
|
62286
|
-
|
|
62317
|
+
log27.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
|
|
62287
62318
|
});
|
|
62288
62319
|
}
|
|
62289
62320
|
if (persisted.size > 0) {
|
|
@@ -62297,10 +62328,10 @@ class SessionManager extends EventEmitter5 {
|
|
|
62297
62328
|
}
|
|
62298
62329
|
}
|
|
62299
62330
|
if (pausedToSkip.length > 0) {
|
|
62300
|
-
|
|
62331
|
+
log27.info(`\u23F8\uFE0F ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
|
|
62301
62332
|
}
|
|
62302
62333
|
if (activeToResume.length > 0) {
|
|
62303
|
-
|
|
62334
|
+
log27.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
|
|
62304
62335
|
for (const state of activeToResume) {
|
|
62305
62336
|
await resumeSession(state, this.getContext());
|
|
62306
62337
|
}
|
|
@@ -62586,7 +62617,7 @@ class SessionManager extends EventEmitter5 {
|
|
|
62586
62617
|
const message = messageBuilder(formatter);
|
|
62587
62618
|
await post(session, "info", message);
|
|
62588
62619
|
} catch (err) {
|
|
62589
|
-
|
|
62620
|
+
log27.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
|
|
62590
62621
|
}
|
|
62591
62622
|
}
|
|
62592
62623
|
}
|
|
@@ -62605,7 +62636,7 @@ class SessionManager extends EventEmitter5 {
|
|
|
62605
62636
|
session.messageManager?.setPendingUpdatePrompt({ postId: post2.id });
|
|
62606
62637
|
this.registerPost(post2.id, session.threadId);
|
|
62607
62638
|
} catch (err) {
|
|
62608
|
-
|
|
62639
|
+
log27.warn(`Failed to post ask message to ${threadId}: ${err}`);
|
|
62609
62640
|
}
|
|
62610
62641
|
}
|
|
62611
62642
|
}
|
|
@@ -66982,7 +67013,7 @@ var instances_default = instances;
|
|
|
66982
67013
|
|
|
66983
67014
|
// node_modules/ink/build/components/App.js
|
|
66984
67015
|
var import_react12 = __toESM(require_react(), 1);
|
|
66985
|
-
import { EventEmitter as
|
|
67016
|
+
import { EventEmitter as EventEmitter6 } from "events";
|
|
66986
67017
|
import process19 from "process";
|
|
66987
67018
|
|
|
66988
67019
|
// node_modules/ink/build/components/AppContext.js
|
|
@@ -66995,11 +67026,11 @@ var AppContext_default = AppContext;
|
|
|
66995
67026
|
|
|
66996
67027
|
// node_modules/ink/build/components/StdinContext.js
|
|
66997
67028
|
var import_react3 = __toESM(require_react(), 1);
|
|
66998
|
-
import { EventEmitter as
|
|
67029
|
+
import { EventEmitter as EventEmitter5 } from "events";
|
|
66999
67030
|
import process16 from "process";
|
|
67000
67031
|
var StdinContext = import_react3.createContext({
|
|
67001
67032
|
stdin: process16.stdin,
|
|
67002
|
-
internal_eventEmitter: new
|
|
67033
|
+
internal_eventEmitter: new EventEmitter5,
|
|
67003
67034
|
setRawMode() {},
|
|
67004
67035
|
isRawModeSupported: false,
|
|
67005
67036
|
internal_exitOnCtrlC: true
|
|
@@ -67217,7 +67248,7 @@ class App extends import_react12.PureComponent {
|
|
|
67217
67248
|
error: undefined
|
|
67218
67249
|
};
|
|
67219
67250
|
rawModeEnabledCount = 0;
|
|
67220
|
-
internal_eventEmitter = new
|
|
67251
|
+
internal_eventEmitter = new EventEmitter6;
|
|
67221
67252
|
isRawModeSupported() {
|
|
67222
67253
|
return this.props.stdin.isTTY;
|
|
67223
67254
|
}
|
|
@@ -71118,9 +71149,8 @@ function Platforms({ platforms }) {
|
|
|
71118
71149
|
!platform2.enabled ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
71119
71150
|
dimColor: true,
|
|
71120
71151
|
children: "\u25CB"
|
|
71121
|
-
}, undefined, false, undefined, this) : platform2.reconnecting ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(
|
|
71122
|
-
|
|
71123
|
-
children: "\u25CC"
|
|
71152
|
+
}, undefined, false, undefined, this) : platform2.reconnecting ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Spinner2, {
|
|
71153
|
+
type: "dots"
|
|
71124
71154
|
}, undefined, false, undefined, this) : platform2.connected ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
71125
71155
|
color: "green",
|
|
71126
71156
|
children: "\u25CF"
|
|
@@ -71136,30 +71166,20 @@ function Platforms({ platforms }) {
|
|
|
71136
71166
|
platform2.botName
|
|
71137
71167
|
]
|
|
71138
71168
|
}, undefined, true, undefined, this),
|
|
71139
|
-
|
|
71140
|
-
|
|
71141
|
-
|
|
71142
|
-
|
|
71143
|
-
|
|
71144
|
-
|
|
71145
|
-
|
|
71146
|
-
|
|
71147
|
-
|
|
71148
|
-
|
|
71149
|
-
")"
|
|
71150
|
-
]
|
|
71151
|
-
}, undefined, true, undefined, this)
|
|
71152
|
-
]
|
|
71153
|
-
}, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(jsx_dev_runtime3.Fragment, {
|
|
71169
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
71170
|
+
dimColor: true,
|
|
71171
|
+
children: "on"
|
|
71172
|
+
}, undefined, false, undefined, this),
|
|
71173
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
71174
|
+
dimColor: !platform2.enabled,
|
|
71175
|
+
children: platform2.displayName
|
|
71176
|
+
}, undefined, false, undefined, this),
|
|
71177
|
+
platform2.reconnecting && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
71178
|
+
dimColor: true,
|
|
71154
71179
|
children: [
|
|
71155
|
-
|
|
71156
|
-
|
|
71157
|
-
|
|
71158
|
-
}, undefined, false, undefined, this),
|
|
71159
|
-
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
71160
|
-
dimColor: !platform2.enabled,
|
|
71161
|
-
children: platform2.displayName
|
|
71162
|
-
}, undefined, false, undefined, this)
|
|
71180
|
+
"(retry ",
|
|
71181
|
+
platform2.reconnectAttempts,
|
|
71182
|
+
")"
|
|
71163
71183
|
]
|
|
71164
71184
|
}, undefined, true, undefined, this)
|
|
71165
71185
|
]
|
|
@@ -71192,29 +71212,29 @@ function SessionLog({ logs, maxLines = 20 }) {
|
|
|
71192
71212
|
return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
71193
71213
|
flexDirection: "column",
|
|
71194
71214
|
flexShrink: 0,
|
|
71195
|
-
children: displayLogs.map((
|
|
71215
|
+
children: displayLogs.map((log28) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
71196
71216
|
flexShrink: 0,
|
|
71197
71217
|
children: [
|
|
71198
71218
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
|
|
71199
|
-
color: getColorForLevel(
|
|
71219
|
+
color: getColorForLevel(log28.level),
|
|
71200
71220
|
dimColor: true,
|
|
71201
71221
|
wrap: "truncate",
|
|
71202
71222
|
children: [
|
|
71203
71223
|
"[",
|
|
71204
|
-
padComponent(
|
|
71224
|
+
padComponent(log28.component),
|
|
71205
71225
|
"]"
|
|
71206
71226
|
]
|
|
71207
71227
|
}, undefined, true, undefined, this),
|
|
71208
71228
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
|
|
71209
|
-
color: getColorForLevel(
|
|
71229
|
+
color: getColorForLevel(log28.level),
|
|
71210
71230
|
wrap: "truncate",
|
|
71211
71231
|
children: [
|
|
71212
71232
|
" ",
|
|
71213
|
-
|
|
71233
|
+
log28.message
|
|
71214
71234
|
]
|
|
71215
71235
|
}, undefined, true, undefined, this)
|
|
71216
71236
|
]
|
|
71217
|
-
},
|
|
71237
|
+
}, log28.id, true, undefined, this))
|
|
71218
71238
|
}, undefined, false, undefined, this);
|
|
71219
71239
|
}
|
|
71220
71240
|
// src/ui/components/Footer.tsx
|
|
@@ -71712,7 +71732,7 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
|
|
|
71712
71732
|
const scrollRef = import_react59.default.useRef(null);
|
|
71713
71733
|
const { stdout } = use_stdout_default();
|
|
71714
71734
|
const isDebug = process.env.DEBUG === "1";
|
|
71715
|
-
const displayLogs = logs.filter((
|
|
71735
|
+
const displayLogs = logs.filter((log28) => isDebug || log28.level !== "debug");
|
|
71716
71736
|
const visibleLogs = displayLogs.slice(-Math.max(maxLines * 3, 100));
|
|
71717
71737
|
import_react59.default.useEffect(() => {
|
|
71718
71738
|
const handleResize = () => scrollRef.current?.remeasure();
|
|
@@ -71752,25 +71772,25 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
|
|
|
71752
71772
|
overflow: "hidden",
|
|
71753
71773
|
children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ScrollView, {
|
|
71754
71774
|
ref: scrollRef,
|
|
71755
|
-
children: visibleLogs.map((
|
|
71775
|
+
children: visibleLogs.map((log28) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
|
|
71756
71776
|
children: [
|
|
71757
71777
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
71758
71778
|
dimColor: true,
|
|
71759
71779
|
children: [
|
|
71760
71780
|
"[",
|
|
71761
|
-
padComponent2(
|
|
71781
|
+
padComponent2(log28.component),
|
|
71762
71782
|
"]"
|
|
71763
71783
|
]
|
|
71764
71784
|
}, undefined, true, undefined, this),
|
|
71765
71785
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
71766
|
-
color: getLevelColor(
|
|
71786
|
+
color: getLevelColor(log28.level),
|
|
71767
71787
|
children: [
|
|
71768
71788
|
" ",
|
|
71769
|
-
|
|
71789
|
+
log28.message
|
|
71770
71790
|
]
|
|
71771
71791
|
}, undefined, true, undefined, this)
|
|
71772
71792
|
]
|
|
71773
|
-
},
|
|
71793
|
+
}, log28.id, true, undefined, this))
|
|
71774
71794
|
}, undefined, false, undefined, this)
|
|
71775
71795
|
}, undefined, false, undefined, this);
|
|
71776
71796
|
}
|
|
@@ -72287,10 +72307,10 @@ function useAppState(initialConfig) {
|
|
|
72287
72307
|
});
|
|
72288
72308
|
}, []);
|
|
72289
72309
|
const getLogsForSession = import_react60.useCallback((sessionId) => {
|
|
72290
|
-
return state.logs.filter((
|
|
72310
|
+
return state.logs.filter((log28) => log28.sessionId === sessionId);
|
|
72291
72311
|
}, [state.logs]);
|
|
72292
72312
|
const getGlobalLogs = import_react60.useCallback(() => {
|
|
72293
|
-
return state.logs.filter((
|
|
72313
|
+
return state.logs.filter((log28) => !log28.sessionId);
|
|
72294
72314
|
}, [state.logs]);
|
|
72295
72315
|
const togglePlatformEnabled = import_react60.useCallback((platformId) => {
|
|
72296
72316
|
let newEnabled = false;
|
|
@@ -73286,11 +73306,11 @@ Release notes not available. See ${formatter.formatLink("GitHub releases", "http
|
|
|
73286
73306
|
}
|
|
73287
73307
|
|
|
73288
73308
|
// src/auto-update/manager.ts
|
|
73289
|
-
import { EventEmitter as
|
|
73309
|
+
import { EventEmitter as EventEmitter9 } from "events";
|
|
73290
73310
|
|
|
73291
73311
|
// src/auto-update/checker.ts
|
|
73292
|
-
import { EventEmitter as
|
|
73293
|
-
var
|
|
73312
|
+
import { EventEmitter as EventEmitter7 } from "events";
|
|
73313
|
+
var log28 = createLogger("checker");
|
|
73294
73314
|
var PACKAGE_NAME = "claude-threads";
|
|
73295
73315
|
function compareVersions(a, b) {
|
|
73296
73316
|
const partsA = a.replace(/^v/, "").split(".").map(Number);
|
|
@@ -73313,18 +73333,18 @@ async function fetchLatestVersion() {
|
|
|
73313
73333
|
}
|
|
73314
73334
|
});
|
|
73315
73335
|
if (!response.ok) {
|
|
73316
|
-
|
|
73336
|
+
log28.warn(`Failed to fetch latest version: HTTP ${response.status}`);
|
|
73317
73337
|
return null;
|
|
73318
73338
|
}
|
|
73319
73339
|
const data = await response.json();
|
|
73320
73340
|
return data.version ?? null;
|
|
73321
73341
|
} catch (err) {
|
|
73322
|
-
|
|
73342
|
+
log28.warn(`Failed to fetch latest version: ${err}`);
|
|
73323
73343
|
return null;
|
|
73324
73344
|
}
|
|
73325
73345
|
}
|
|
73326
73346
|
|
|
73327
|
-
class UpdateChecker extends
|
|
73347
|
+
class UpdateChecker extends EventEmitter7 {
|
|
73328
73348
|
config;
|
|
73329
73349
|
checkInterval = null;
|
|
73330
73350
|
lastCheck = null;
|
|
@@ -73336,38 +73356,38 @@ class UpdateChecker extends EventEmitter8 {
|
|
|
73336
73356
|
}
|
|
73337
73357
|
start() {
|
|
73338
73358
|
if (!this.config.enabled) {
|
|
73339
|
-
|
|
73359
|
+
log28.debug("Auto-update disabled, not starting checker");
|
|
73340
73360
|
return;
|
|
73341
73361
|
}
|
|
73342
73362
|
setTimeout(() => {
|
|
73343
73363
|
this.check().catch((err) => {
|
|
73344
|
-
|
|
73364
|
+
log28.warn(`Initial update check failed: ${err}`);
|
|
73345
73365
|
});
|
|
73346
73366
|
}, 5000);
|
|
73347
73367
|
const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
|
|
73348
73368
|
this.checkInterval = setInterval(() => {
|
|
73349
73369
|
this.check().catch((err) => {
|
|
73350
|
-
|
|
73370
|
+
log28.warn(`Periodic update check failed: ${err}`);
|
|
73351
73371
|
});
|
|
73352
73372
|
}, intervalMs);
|
|
73353
|
-
|
|
73373
|
+
log28.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
|
|
73354
73374
|
}
|
|
73355
73375
|
stop() {
|
|
73356
73376
|
if (this.checkInterval) {
|
|
73357
73377
|
clearInterval(this.checkInterval);
|
|
73358
73378
|
this.checkInterval = null;
|
|
73359
73379
|
}
|
|
73360
|
-
|
|
73380
|
+
log28.debug("Update checker stopped");
|
|
73361
73381
|
}
|
|
73362
73382
|
async check() {
|
|
73363
73383
|
if (this.isChecking) {
|
|
73364
|
-
|
|
73384
|
+
log28.debug("Check already in progress, skipping");
|
|
73365
73385
|
return this.lastUpdateInfo;
|
|
73366
73386
|
}
|
|
73367
73387
|
this.isChecking = true;
|
|
73368
73388
|
this.emit("check:start");
|
|
73369
73389
|
try {
|
|
73370
|
-
|
|
73390
|
+
log28.debug("Checking for updates...");
|
|
73371
73391
|
const latestVersion2 = await fetchLatestVersion();
|
|
73372
73392
|
if (!latestVersion2) {
|
|
73373
73393
|
this.emit("check:complete", false);
|
|
@@ -73384,18 +73404,18 @@ class UpdateChecker extends EventEmitter8 {
|
|
|
73384
73404
|
detectedAt: new Date
|
|
73385
73405
|
};
|
|
73386
73406
|
if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
|
|
73387
|
-
|
|
73407
|
+
log28.info(`\uD83C\uDD95 Update available: v${currentVersion} \u2192 v${latestVersion2}`);
|
|
73388
73408
|
this.lastUpdateInfo = updateInfo;
|
|
73389
73409
|
this.emit("update", updateInfo);
|
|
73390
73410
|
}
|
|
73391
73411
|
this.emit("check:complete", true);
|
|
73392
73412
|
return updateInfo;
|
|
73393
73413
|
}
|
|
73394
|
-
|
|
73414
|
+
log28.debug(`Up to date (v${currentVersion})`);
|
|
73395
73415
|
this.emit("check:complete", false);
|
|
73396
73416
|
return null;
|
|
73397
73417
|
} catch (err) {
|
|
73398
|
-
|
|
73418
|
+
log28.warn(`Update check failed: ${err}`);
|
|
73399
73419
|
this.emit("check:error", err);
|
|
73400
73420
|
return null;
|
|
73401
73421
|
} finally {
|
|
@@ -73419,7 +73439,7 @@ class UpdateChecker extends EventEmitter8 {
|
|
|
73419
73439
|
}
|
|
73420
73440
|
|
|
73421
73441
|
// src/auto-update/scheduler.ts
|
|
73422
|
-
import { EventEmitter as
|
|
73442
|
+
import { EventEmitter as EventEmitter8 } from "events";
|
|
73423
73443
|
|
|
73424
73444
|
// src/auto-update/types.ts
|
|
73425
73445
|
var RESTART_EXIT_CODE = 42;
|
|
@@ -73465,9 +73485,9 @@ function isInScheduledWindow(window2) {
|
|
|
73465
73485
|
}
|
|
73466
73486
|
|
|
73467
73487
|
// src/auto-update/scheduler.ts
|
|
73468
|
-
var
|
|
73488
|
+
var log29 = createLogger("scheduler");
|
|
73469
73489
|
|
|
73470
|
-
class UpdateScheduler extends
|
|
73490
|
+
class UpdateScheduler extends EventEmitter8 {
|
|
73471
73491
|
config;
|
|
73472
73492
|
getSessionActivity;
|
|
73473
73493
|
getActiveThreadIds;
|
|
@@ -73489,7 +73509,7 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73489
73509
|
scheduleUpdate(updateInfo) {
|
|
73490
73510
|
this.pendingUpdate = updateInfo;
|
|
73491
73511
|
if (this.config.autoRestartMode === "immediate") {
|
|
73492
|
-
|
|
73512
|
+
log29.info("Immediate mode: triggering update now");
|
|
73493
73513
|
this.emit("ready", updateInfo);
|
|
73494
73514
|
return;
|
|
73495
73515
|
}
|
|
@@ -73502,19 +73522,19 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73502
73522
|
this.scheduledRestartAt = null;
|
|
73503
73523
|
this.askApprovals.clear();
|
|
73504
73524
|
this.askStartTime = null;
|
|
73505
|
-
|
|
73525
|
+
log29.debug("Update schedule cancelled");
|
|
73506
73526
|
}
|
|
73507
73527
|
deferUpdate(minutes) {
|
|
73508
73528
|
const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
|
|
73509
73529
|
this.scheduledRestartAt = null;
|
|
73510
73530
|
this.idleStartTime = null;
|
|
73511
73531
|
this.emit("deferred", deferUntil);
|
|
73512
|
-
|
|
73532
|
+
log29.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
|
|
73513
73533
|
return deferUntil;
|
|
73514
73534
|
}
|
|
73515
73535
|
recordAskResponse(threadId, approved) {
|
|
73516
73536
|
this.askApprovals.set(threadId, approved);
|
|
73517
|
-
|
|
73537
|
+
log29.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
|
|
73518
73538
|
this.checkAskCondition();
|
|
73519
73539
|
}
|
|
73520
73540
|
getScheduledRestartAt() {
|
|
@@ -73535,7 +73555,7 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73535
73555
|
return;
|
|
73536
73556
|
this.checkCondition();
|
|
73537
73557
|
this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
|
|
73538
|
-
|
|
73558
|
+
log29.debug(`Started checking for ${this.config.autoRestartMode} condition`);
|
|
73539
73559
|
}
|
|
73540
73560
|
stopChecking() {
|
|
73541
73561
|
if (this.checkTimer) {
|
|
@@ -73566,17 +73586,17 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73566
73586
|
if (activity.activeSessionCount === 0) {
|
|
73567
73587
|
if (!this.idleStartTime) {
|
|
73568
73588
|
this.idleStartTime = new Date;
|
|
73569
|
-
|
|
73589
|
+
log29.debug("No active sessions, starting idle timer");
|
|
73570
73590
|
}
|
|
73571
73591
|
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
73572
73592
|
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
73573
73593
|
if (idleMs >= requiredMs) {
|
|
73574
|
-
|
|
73594
|
+
log29.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
|
|
73575
73595
|
this.triggerCountdown();
|
|
73576
73596
|
}
|
|
73577
73597
|
} else {
|
|
73578
73598
|
if (this.idleStartTime) {
|
|
73579
|
-
|
|
73599
|
+
log29.debug("Sessions became active, resetting idle timer");
|
|
73580
73600
|
this.idleStartTime = null;
|
|
73581
73601
|
}
|
|
73582
73602
|
}
|
|
@@ -73587,7 +73607,7 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73587
73607
|
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
73588
73608
|
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
73589
73609
|
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
73590
|
-
|
|
73610
|
+
log29.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
|
|
73591
73611
|
this.triggerCountdown();
|
|
73592
73612
|
}
|
|
73593
73613
|
} else if (activity.activeSessionCount === 0) {
|
|
@@ -73597,7 +73617,7 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73597
73617
|
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
73598
73618
|
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
73599
73619
|
if (idleMs >= requiredMs) {
|
|
73600
|
-
|
|
73620
|
+
log29.info("No sessions and quiet timeout reached, triggering update");
|
|
73601
73621
|
this.triggerCountdown();
|
|
73602
73622
|
}
|
|
73603
73623
|
}
|
|
@@ -73608,13 +73628,13 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73608
73628
|
}
|
|
73609
73629
|
const activity = this.getSessionActivity();
|
|
73610
73630
|
if (activity.activeSessionCount === 0) {
|
|
73611
|
-
|
|
73631
|
+
log29.info("Within scheduled window and no active sessions, triggering update");
|
|
73612
73632
|
this.triggerCountdown();
|
|
73613
73633
|
} else if (activity.lastActivityAt) {
|
|
73614
73634
|
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
73615
73635
|
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
73616
73636
|
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
73617
|
-
|
|
73637
|
+
log29.info("Within scheduled window and sessions quiet, triggering update");
|
|
73618
73638
|
this.triggerCountdown();
|
|
73619
73639
|
}
|
|
73620
73640
|
}
|
|
@@ -73622,14 +73642,14 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73622
73642
|
checkAskCondition() {
|
|
73623
73643
|
const threadIds = this.getActiveThreadIds();
|
|
73624
73644
|
if (threadIds.length === 0) {
|
|
73625
|
-
|
|
73645
|
+
log29.info("No active threads, proceeding with update");
|
|
73626
73646
|
this.triggerCountdown();
|
|
73627
73647
|
return;
|
|
73628
73648
|
}
|
|
73629
73649
|
if (!this.askStartTime && this.pendingUpdate) {
|
|
73630
73650
|
this.askStartTime = new Date;
|
|
73631
73651
|
this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
|
|
73632
|
-
|
|
73652
|
+
log29.warn(`Failed to post ask message: ${err}`);
|
|
73633
73653
|
});
|
|
73634
73654
|
return;
|
|
73635
73655
|
}
|
|
@@ -73642,12 +73662,12 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73642
73662
|
denials++;
|
|
73643
73663
|
}
|
|
73644
73664
|
if (approvals > threadIds.length / 2) {
|
|
73645
|
-
|
|
73665
|
+
log29.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
|
|
73646
73666
|
this.triggerCountdown();
|
|
73647
73667
|
return;
|
|
73648
73668
|
}
|
|
73649
73669
|
if (denials > threadIds.length / 2) {
|
|
73650
|
-
|
|
73670
|
+
log29.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
|
|
73651
73671
|
this.deferUpdate(60);
|
|
73652
73672
|
return;
|
|
73653
73673
|
}
|
|
@@ -73655,7 +73675,7 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73655
73675
|
const elapsedMs = Date.now() - this.askStartTime.getTime();
|
|
73656
73676
|
const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
|
|
73657
73677
|
if (elapsedMs >= timeoutMs) {
|
|
73658
|
-
|
|
73678
|
+
log29.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
|
|
73659
73679
|
this.triggerCountdown();
|
|
73660
73680
|
}
|
|
73661
73681
|
}
|
|
@@ -73675,7 +73695,7 @@ class UpdateScheduler extends EventEmitter9 {
|
|
|
73675
73695
|
this.emit("ready", this.pendingUpdate);
|
|
73676
73696
|
}
|
|
73677
73697
|
}, 1000);
|
|
73678
|
-
|
|
73698
|
+
log29.info("Update countdown started (60 seconds)");
|
|
73679
73699
|
}
|
|
73680
73700
|
stopCountdown() {
|
|
73681
73701
|
if (this.countdownTimer) {
|
|
@@ -73690,7 +73710,7 @@ import { spawn as spawn6 } from "child_process";
|
|
|
73690
73710
|
import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
73691
73711
|
import { dirname as dirname8, resolve as resolve6 } from "path";
|
|
73692
73712
|
import { homedir as homedir5 } from "os";
|
|
73693
|
-
var
|
|
73713
|
+
var log30 = createLogger("installer");
|
|
73694
73714
|
var STATE_PATH = resolve6(homedir5(), ".config", "claude-threads", UPDATE_STATE_FILENAME);
|
|
73695
73715
|
var PACKAGE_NAME2 = "claude-threads";
|
|
73696
73716
|
function loadUpdateState() {
|
|
@@ -73700,7 +73720,7 @@ function loadUpdateState() {
|
|
|
73700
73720
|
return JSON.parse(content);
|
|
73701
73721
|
}
|
|
73702
73722
|
} catch (err) {
|
|
73703
|
-
|
|
73723
|
+
log30.warn(`Failed to load update state: ${err}`);
|
|
73704
73724
|
}
|
|
73705
73725
|
return {};
|
|
73706
73726
|
}
|
|
@@ -73711,9 +73731,9 @@ function saveUpdateState(state) {
|
|
|
73711
73731
|
mkdirSync4(dir, { recursive: true });
|
|
73712
73732
|
}
|
|
73713
73733
|
writeFileSync5(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
|
|
73714
|
-
|
|
73734
|
+
log30.debug("Update state saved");
|
|
73715
73735
|
} catch (err) {
|
|
73716
|
-
|
|
73736
|
+
log30.warn(`Failed to save update state: ${err}`);
|
|
73717
73737
|
}
|
|
73718
73738
|
}
|
|
73719
73739
|
function clearUpdateState() {
|
|
@@ -73722,7 +73742,7 @@ function clearUpdateState() {
|
|
|
73722
73742
|
writeFileSync5(STATE_PATH, "{}", "utf-8");
|
|
73723
73743
|
}
|
|
73724
73744
|
} catch (err) {
|
|
73725
|
-
|
|
73745
|
+
log30.warn(`Failed to clear update state: ${err}`);
|
|
73726
73746
|
}
|
|
73727
73747
|
}
|
|
73728
73748
|
function checkJustUpdated() {
|
|
@@ -73754,7 +73774,7 @@ function clearRuntimeSettings() {
|
|
|
73754
73774
|
}
|
|
73755
73775
|
}
|
|
73756
73776
|
async function installVersion(version) {
|
|
73757
|
-
|
|
73777
|
+
log30.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
|
|
73758
73778
|
saveUpdateState({
|
|
73759
73779
|
previousVersion: VERSION,
|
|
73760
73780
|
targetVersion: version,
|
|
@@ -73765,7 +73785,7 @@ async function installVersion(version) {
|
|
|
73765
73785
|
const useBun = process.platform !== "win32";
|
|
73766
73786
|
const cmd = useBun ? "bun" : "npm.cmd";
|
|
73767
73787
|
const args = useBun ? ["install", "-g", `${PACKAGE_NAME2}@${version}`] : ["install", "-g", `${PACKAGE_NAME2}@${version}`];
|
|
73768
|
-
|
|
73788
|
+
log30.debug(`Using ${useBun ? "bun" : "npm"} for installation`);
|
|
73769
73789
|
const child = spawn6(cmd, args, {
|
|
73770
73790
|
stdio: ["ignore", "pipe", "pipe"],
|
|
73771
73791
|
env: {
|
|
@@ -73783,7 +73803,7 @@ async function installVersion(version) {
|
|
|
73783
73803
|
});
|
|
73784
73804
|
child.on("close", (code) => {
|
|
73785
73805
|
if (code === 0) {
|
|
73786
|
-
|
|
73806
|
+
log30.info(`\u2705 Successfully installed ${PACKAGE_NAME2}@${version}`);
|
|
73787
73807
|
saveUpdateState({
|
|
73788
73808
|
previousVersion: VERSION,
|
|
73789
73809
|
targetVersion: version,
|
|
@@ -73793,20 +73813,20 @@ async function installVersion(version) {
|
|
|
73793
73813
|
resolve7({ success: true });
|
|
73794
73814
|
} else {
|
|
73795
73815
|
const errorMsg = stderr || stdout || `Exit code: ${code}`;
|
|
73796
|
-
|
|
73816
|
+
log30.error(`\u274C Installation failed: ${errorMsg}`);
|
|
73797
73817
|
clearUpdateState();
|
|
73798
73818
|
resolve7({ success: false, error: errorMsg });
|
|
73799
73819
|
}
|
|
73800
73820
|
});
|
|
73801
73821
|
child.on("error", (err) => {
|
|
73802
|
-
|
|
73822
|
+
log30.error(`\u274C Failed to spawn npm: ${err}`);
|
|
73803
73823
|
clearUpdateState();
|
|
73804
73824
|
resolve7({ success: false, error: err.message });
|
|
73805
73825
|
});
|
|
73806
73826
|
setTimeout(() => {
|
|
73807
73827
|
if (child.exitCode === null) {
|
|
73808
73828
|
child.kill();
|
|
73809
|
-
|
|
73829
|
+
log30.error("\u274C Installation timed out");
|
|
73810
73830
|
clearUpdateState();
|
|
73811
73831
|
resolve7({ success: false, error: "Installation timed out" });
|
|
73812
73832
|
}
|
|
@@ -73847,9 +73867,9 @@ class UpdateInstaller {
|
|
|
73847
73867
|
}
|
|
73848
73868
|
|
|
73849
73869
|
// src/auto-update/manager.ts
|
|
73850
|
-
var
|
|
73870
|
+
var log31 = createLogger("updater");
|
|
73851
73871
|
|
|
73852
|
-
class AutoUpdateManager extends
|
|
73872
|
+
class AutoUpdateManager extends EventEmitter9 {
|
|
73853
73873
|
config;
|
|
73854
73874
|
callbacks;
|
|
73855
73875
|
checker;
|
|
@@ -73870,23 +73890,23 @@ class AutoUpdateManager extends EventEmitter10 {
|
|
|
73870
73890
|
}
|
|
73871
73891
|
start() {
|
|
73872
73892
|
if (!this.config.enabled) {
|
|
73873
|
-
|
|
73893
|
+
log31.info("Auto-update is disabled");
|
|
73874
73894
|
return;
|
|
73875
73895
|
}
|
|
73876
73896
|
const updateResult = this.installer.checkJustUpdated();
|
|
73877
73897
|
if (updateResult) {
|
|
73878
|
-
|
|
73898
|
+
log31.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
|
|
73879
73899
|
this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
|
|
73880
|
-
|
|
73900
|
+
log31.warn(`Failed to broadcast update notification: ${err}`);
|
|
73881
73901
|
});
|
|
73882
73902
|
}
|
|
73883
73903
|
this.checker.start();
|
|
73884
|
-
|
|
73904
|
+
log31.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
|
|
73885
73905
|
}
|
|
73886
73906
|
stop() {
|
|
73887
73907
|
this.checker.stop();
|
|
73888
73908
|
this.scheduler.stop();
|
|
73889
|
-
|
|
73909
|
+
log31.debug("Auto-update manager stopped");
|
|
73890
73910
|
}
|
|
73891
73911
|
getState() {
|
|
73892
73912
|
return { ...this.state };
|
|
@@ -73900,10 +73920,10 @@ class AutoUpdateManager extends EventEmitter10 {
|
|
|
73900
73920
|
async forceUpdate() {
|
|
73901
73921
|
const updateInfo = this.state.updateInfo || await this.checker.check();
|
|
73902
73922
|
if (!updateInfo) {
|
|
73903
|
-
|
|
73923
|
+
log31.info("No update available");
|
|
73904
73924
|
return;
|
|
73905
73925
|
}
|
|
73906
|
-
|
|
73926
|
+
log31.info("Forcing immediate update");
|
|
73907
73927
|
await this.performUpdate(updateInfo);
|
|
73908
73928
|
}
|
|
73909
73929
|
deferUpdate(minutes = 60) {
|
|
@@ -73958,7 +73978,7 @@ class AutoUpdateManager extends EventEmitter10 {
|
|
|
73958
73978
|
await this.callbacks.broadcastUpdate((fmt) => `\u2705 ${fmt.formatBold("Update installed")} - restarting now. ${fmt.formatItalic("Sessions will resume automatically.")}`).catch(() => {});
|
|
73959
73979
|
await new Promise((resolve7) => setTimeout(resolve7, 1000));
|
|
73960
73980
|
await this.callbacks.prepareForRestart();
|
|
73961
|
-
|
|
73981
|
+
log31.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
|
|
73962
73982
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
73963
73983
|
process.stdout.write("\x1B[?25h");
|
|
73964
73984
|
process.exit(RESTART_EXIT_CODE);
|