claude-threads 0.22.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/index.js +224 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.24.0] - 2026-01-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Enhanced session status bar with model and context info** - The session header now displays real-time usage information similar to Claude Code's status line:
|
|
14
|
+
- Model name (`🤖 Opus 4.5`, `🤖 Sonnet 4`, etc.)
|
|
15
|
+
- Context usage with visual progress bar (`🟢▓▓░░░░░░░░ 23%`)
|
|
16
|
+
- Session cost (`💰 $0.07`)
|
|
17
|
+
- Color-coded context indicator:
|
|
18
|
+
- 🟢 Green: < 50% (plenty of context)
|
|
19
|
+
- 🟡 Yellow: 50-75% (moderate usage)
|
|
20
|
+
- 🟠 Orange: 75-90% (getting full)
|
|
21
|
+
- 🔴 Red: 90%+ (almost full)
|
|
22
|
+
- **Periodic status bar updates** - Status bar now refreshes every 30 seconds automatically to keep uptime and usage stats current
|
|
23
|
+
- **Usage stats tracking** - Session now tracks token usage, cost, and model information extracted from Claude CLI result events
|
|
24
|
+
|
|
25
|
+
### Improved
|
|
26
|
+
- **Existing worktree handling** - When a worktree already exists for a branch, the bot now offers to join it with a reaction prompt (👍 to join, ❌ to skip) instead of just showing a warning message that required manually typing `!worktree switch`
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- **Task list 🔽 emoji not preserved when bumped** - Fixed issues where the collapse/expand toggle emoji would disappear or get stuck on the wrong post:
|
|
30
|
+
- When a task list is bumped to the bottom, the new post now gets the 🔽 emoji via `createInteractivePost`
|
|
31
|
+
- When a task post is repurposed for other content, the emoji is removed from the old post before reuse
|
|
32
|
+
- Added `removeReaction` method to platform client interface for proper emoji cleanup
|
|
33
|
+
- **WorktreeMode type inconsistency** - Aligned the WorktreeMode type definition across the codebase to include 'off' mode
|
|
34
|
+
|
|
35
|
+
## [0.23.0] - 2026-01-02
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- **Sticky message status bar** - Added a compact status line to the channel sticky message showing system-level info:
|
|
39
|
+
- Bot version (`v0.22.0`)
|
|
40
|
+
- Active sessions count (`3/5 sessions`)
|
|
41
|
+
- Permission mode (`🔐 Interactive` or `⚡ Auto`)
|
|
42
|
+
- Worktree mode (`🌿 Worktree: always/never`) - only shown if not default 'prompt'
|
|
43
|
+
- Chrome status (`🌐 Chrome`) - only when enabled
|
|
44
|
+
- Debug mode (`🐛 Debug`) - only when enabled
|
|
45
|
+
- Battery level (`🔋 85%` or `🔌 AC`) - macOS and Linux
|
|
46
|
+
- Bot uptime (`⏱️ 2h15m`) - how long the bot has been running
|
|
47
|
+
- Working directory (`📂 ~/projects`)
|
|
48
|
+
- Hostname (`💻 hostname`) - machine name for identification
|
|
49
|
+
|
|
10
50
|
## [0.22.1] - 2026-01-01
|
|
11
51
|
|
|
12
52
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -13388,6 +13388,9 @@ class MattermostClient extends EventEmitter {
|
|
|
13388
13388
|
emoji_name: emojiName
|
|
13389
13389
|
});
|
|
13390
13390
|
}
|
|
13391
|
+
async removeReaction(postId, emojiName) {
|
|
13392
|
+
await this.api("DELETE", `/users/${this.botUserId}/posts/${postId}/reactions/${emojiName}`);
|
|
13393
|
+
}
|
|
13391
13394
|
async createInteractivePost(message, reactions, threadId) {
|
|
13392
13395
|
const post = await this.createPost(message, threadId);
|
|
13393
13396
|
for (const emoji of reactions) {
|
|
@@ -13988,10 +13991,13 @@ function stopTyping(session) {
|
|
|
13988
13991
|
async function bumpTasksToBottomWithContent(session, newContent, registerPost) {
|
|
13989
13992
|
const oldTasksPostId = session.tasksPostId;
|
|
13990
13993
|
const oldTasksContent = session.lastTasksContent;
|
|
13994
|
+
try {
|
|
13995
|
+
await session.platform.removeReaction(oldTasksPostId, TASK_TOGGLE_EMOJIS[0]);
|
|
13996
|
+
} catch {}
|
|
13991
13997
|
await session.platform.updatePost(oldTasksPostId, newContent);
|
|
13992
13998
|
registerPost(oldTasksPostId, session.threadId);
|
|
13993
13999
|
if (oldTasksContent) {
|
|
13994
|
-
const newTasksPost = await session.platform.
|
|
14000
|
+
const newTasksPost = await session.platform.createInteractivePost(oldTasksContent, [TASK_TOGGLE_EMOJIS[0]], session.threadId);
|
|
13995
14001
|
session.tasksPostId = newTasksPost.id;
|
|
13996
14002
|
registerPost(newTasksPost.id, session.threadId);
|
|
13997
14003
|
} else {
|
|
@@ -14008,7 +14014,7 @@ async function bumpTasksToBottom(session, registerPost) {
|
|
|
14008
14014
|
}
|
|
14009
14015
|
try {
|
|
14010
14016
|
await session.platform.deletePost(session.tasksPostId);
|
|
14011
|
-
const newPost = await session.platform.
|
|
14017
|
+
const newPost = await session.platform.createInteractivePost(session.lastTasksContent, [TASK_TOGGLE_EMOJIS[0]], session.threadId);
|
|
14012
14018
|
session.tasksPostId = newPost.id;
|
|
14013
14019
|
if (registerPost) {
|
|
14014
14020
|
registerPost(newPost.id, session.threadId);
|
|
@@ -15056,6 +15062,7 @@ function formatEvent(session, e, ctx) {
|
|
|
15056
15062
|
ctx.flush(session);
|
|
15057
15063
|
session.currentPostId = null;
|
|
15058
15064
|
session.pendingContent = "";
|
|
15065
|
+
updateUsageStats(session, e, ctx);
|
|
15059
15066
|
return null;
|
|
15060
15067
|
}
|
|
15061
15068
|
case "system":
|
|
@@ -15266,6 +15273,74 @@ async function postCurrentQuestion(session, ctx) {
|
|
|
15266
15273
|
session.pendingQuestionSet.currentPostId = post.id;
|
|
15267
15274
|
ctx.registerPost(post.id, session.threadId);
|
|
15268
15275
|
}
|
|
15276
|
+
function getModelDisplayName(modelId) {
|
|
15277
|
+
if (modelId.includes("opus-4-5") || modelId.includes("opus-4.5"))
|
|
15278
|
+
return "Opus 4.5";
|
|
15279
|
+
if (modelId.includes("opus-4"))
|
|
15280
|
+
return "Opus 4";
|
|
15281
|
+
if (modelId.includes("opus"))
|
|
15282
|
+
return "Opus";
|
|
15283
|
+
if (modelId.includes("sonnet-4"))
|
|
15284
|
+
return "Sonnet 4";
|
|
15285
|
+
if (modelId.includes("sonnet-3-5") || modelId.includes("sonnet-3.5"))
|
|
15286
|
+
return "Sonnet 3.5";
|
|
15287
|
+
if (modelId.includes("sonnet"))
|
|
15288
|
+
return "Sonnet";
|
|
15289
|
+
if (modelId.includes("haiku-4-5") || modelId.includes("haiku-4.5"))
|
|
15290
|
+
return "Haiku 4.5";
|
|
15291
|
+
if (modelId.includes("haiku"))
|
|
15292
|
+
return "Haiku";
|
|
15293
|
+
const match = modelId.match(/claude-(\w+)/);
|
|
15294
|
+
return match ? match[1].charAt(0).toUpperCase() + match[1].slice(1) : modelId;
|
|
15295
|
+
}
|
|
15296
|
+
function updateUsageStats(session, event, ctx) {
|
|
15297
|
+
const result = event;
|
|
15298
|
+
if (!result.modelUsage)
|
|
15299
|
+
return;
|
|
15300
|
+
let primaryModel = "";
|
|
15301
|
+
let highestCost = 0;
|
|
15302
|
+
let contextWindowSize = 200000;
|
|
15303
|
+
const modelUsage = {};
|
|
15304
|
+
let totalTokensUsed = 0;
|
|
15305
|
+
for (const [modelId, usage] of Object.entries(result.modelUsage)) {
|
|
15306
|
+
modelUsage[modelId] = {
|
|
15307
|
+
inputTokens: usage.inputTokens,
|
|
15308
|
+
outputTokens: usage.outputTokens,
|
|
15309
|
+
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
15310
|
+
cacheCreationInputTokens: usage.cacheCreationInputTokens,
|
|
15311
|
+
contextWindow: usage.contextWindow,
|
|
15312
|
+
costUSD: usage.costUSD
|
|
15313
|
+
};
|
|
15314
|
+
totalTokensUsed += usage.inputTokens + usage.outputTokens + usage.cacheReadInputTokens + usage.cacheCreationInputTokens;
|
|
15315
|
+
if (usage.costUSD > highestCost) {
|
|
15316
|
+
highestCost = usage.costUSD;
|
|
15317
|
+
primaryModel = modelId;
|
|
15318
|
+
contextWindowSize = usage.contextWindow;
|
|
15319
|
+
}
|
|
15320
|
+
}
|
|
15321
|
+
const usageStats = {
|
|
15322
|
+
primaryModel,
|
|
15323
|
+
modelDisplayName: getModelDisplayName(primaryModel),
|
|
15324
|
+
contextWindowSize,
|
|
15325
|
+
totalTokensUsed,
|
|
15326
|
+
totalCostUSD: result.total_cost_usd || 0,
|
|
15327
|
+
modelUsage,
|
|
15328
|
+
lastUpdated: new Date
|
|
15329
|
+
};
|
|
15330
|
+
session.usageStats = usageStats;
|
|
15331
|
+
if (ctx.debug) {
|
|
15332
|
+
console.log(`[DEBUG] Updated usage stats: ${usageStats.modelDisplayName}, ` + `${usageStats.totalTokensUsed}/${usageStats.contextWindowSize} tokens, ` + `$${usageStats.totalCostUSD.toFixed(4)}`);
|
|
15333
|
+
}
|
|
15334
|
+
if (!session.statusBarTimer) {
|
|
15335
|
+
const STATUS_BAR_UPDATE_INTERVAL = 30000;
|
|
15336
|
+
session.statusBarTimer = setInterval(() => {
|
|
15337
|
+
if (session.claude.isRunning()) {
|
|
15338
|
+
ctx.updateSessionHeader(session).catch(() => {});
|
|
15339
|
+
}
|
|
15340
|
+
}, STATUS_BAR_UPDATE_INTERVAL);
|
|
15341
|
+
}
|
|
15342
|
+
ctx.updateSessionHeader(session).catch(() => {});
|
|
15343
|
+
}
|
|
15269
15344
|
|
|
15270
15345
|
// src/session/reactions.ts
|
|
15271
15346
|
async function handleQuestionReaction(session, postId, emojiName, username, ctx) {
|
|
@@ -15404,6 +15479,34 @@ async function handleTaskToggleReaction(session, ctx) {
|
|
|
15404
15479
|
}
|
|
15405
15480
|
return true;
|
|
15406
15481
|
}
|
|
15482
|
+
async function handleExistingWorktreeReaction(session, postId, emojiName, username, ctx) {
|
|
15483
|
+
const pending = session.pendingExistingWorktreePrompt;
|
|
15484
|
+
if (!pending || pending.postId !== postId) {
|
|
15485
|
+
return false;
|
|
15486
|
+
}
|
|
15487
|
+
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
15488
|
+
return false;
|
|
15489
|
+
}
|
|
15490
|
+
const isApprove = isApprovalEmoji(emojiName);
|
|
15491
|
+
const isDeny = isDenialEmoji(emojiName);
|
|
15492
|
+
if (!isApprove && !isDeny) {
|
|
15493
|
+
return false;
|
|
15494
|
+
}
|
|
15495
|
+
const shortPath = pending.worktreePath.replace(process.env.HOME || "", "~");
|
|
15496
|
+
if (isApprove) {
|
|
15497
|
+
await session.platform.updatePost(pending.postId, `\u2705 Joining worktree for branch \`${pending.branch}\` at \`${shortPath}\``);
|
|
15498
|
+
session.pendingExistingWorktreePrompt = undefined;
|
|
15499
|
+
ctx.persistSession(session);
|
|
15500
|
+
await ctx.switchToWorktree(session.threadId, pending.worktreePath, pending.username);
|
|
15501
|
+
console.log(` \uD83C\uDF3F @${username} joined existing worktree ${pending.branch} at ${shortPath}`);
|
|
15502
|
+
} else {
|
|
15503
|
+
await session.platform.updatePost(pending.postId, `\u2705 Continuing in current directory (skipped by @${username})`);
|
|
15504
|
+
session.pendingExistingWorktreePrompt = undefined;
|
|
15505
|
+
ctx.persistSession(session);
|
|
15506
|
+
console.log(` \u274C @${username} skipped joining existing worktree ${pending.branch}`);
|
|
15507
|
+
}
|
|
15508
|
+
return true;
|
|
15509
|
+
}
|
|
15407
15510
|
|
|
15408
15511
|
// src/claude/cli.ts
|
|
15409
15512
|
import { spawn } from "child_process";
|
|
@@ -19333,6 +19436,24 @@ class KeepAliveManager {
|
|
|
19333
19436
|
var keepAlive = new KeepAliveManager;
|
|
19334
19437
|
|
|
19335
19438
|
// src/session/commands.ts
|
|
19439
|
+
function formatContextBar(percent) {
|
|
19440
|
+
const totalBlocks = 10;
|
|
19441
|
+
const filledBlocks = Math.round(percent / 100 * totalBlocks);
|
|
19442
|
+
const emptyBlocks = totalBlocks - filledBlocks;
|
|
19443
|
+
let indicator;
|
|
19444
|
+
if (percent < 50) {
|
|
19445
|
+
indicator = "\uD83D\uDFE2";
|
|
19446
|
+
} else if (percent < 75) {
|
|
19447
|
+
indicator = "\uD83D\uDFE1";
|
|
19448
|
+
} else if (percent < 90) {
|
|
19449
|
+
indicator = "\uD83D\uDFE0";
|
|
19450
|
+
} else {
|
|
19451
|
+
indicator = "\uD83D\uDD34";
|
|
19452
|
+
}
|
|
19453
|
+
const filled = "\u2593".repeat(filledBlocks);
|
|
19454
|
+
const empty = "\u2591".repeat(emptyBlocks);
|
|
19455
|
+
return `${indicator}${filled}${empty}`;
|
|
19456
|
+
}
|
|
19336
19457
|
async function cancelSession(session, username, ctx) {
|
|
19337
19458
|
const shortId = session.threadId.substring(0, 8);
|
|
19338
19459
|
console.log(` \uD83D\uDED1 Session (${shortId}\u2026) cancelled by @${username}`);
|
|
@@ -19516,6 +19637,14 @@ async function updateSessionHeader(session, ctx) {
|
|
|
19516
19637
|
const permMode = isInteractive ? "\uD83D\uDD10 Interactive" : "\u26A1 Auto";
|
|
19517
19638
|
const otherParticipants = [...session.sessionAllowedUsers].filter((u) => u !== session.startedBy).map((u) => `@${u}`).join(", ");
|
|
19518
19639
|
const statusItems = [];
|
|
19640
|
+
if (session.usageStats) {
|
|
19641
|
+
const stats = session.usageStats;
|
|
19642
|
+
statusItems.push(`\`\uD83E\uDD16 ${stats.modelDisplayName}\``);
|
|
19643
|
+
const contextPercent = Math.round(stats.totalTokensUsed / stats.contextWindowSize * 100);
|
|
19644
|
+
const contextBar = formatContextBar(contextPercent);
|
|
19645
|
+
statusItems.push(`\`${contextBar} ${contextPercent}%\``);
|
|
19646
|
+
statusItems.push(`\`\uD83D\uDCB0 $${stats.totalCostUSD.toFixed(2)}\``);
|
|
19647
|
+
}
|
|
19519
19648
|
statusItems.push(`\`${session.sessionNumber}/${ctx.maxSessions}\``);
|
|
19520
19649
|
statusItems.push(`\`${permMode}\``);
|
|
19521
19650
|
if (ctx.chromeEnabled) {
|
|
@@ -19705,7 +19834,8 @@ async function startSession(options, username, displayName, replyToPostId, platf
|
|
|
19705
19834
|
inProgressTaskStart: null,
|
|
19706
19835
|
activeToolStarts: new Map,
|
|
19707
19836
|
firstPrompt: options.prompt,
|
|
19708
|
-
messageCount: 0
|
|
19837
|
+
messageCount: 0,
|
|
19838
|
+
statusBarTimer: null
|
|
19709
19839
|
};
|
|
19710
19840
|
ctx.sessions.set(sessionId, session);
|
|
19711
19841
|
ctx.registerPost(post.id, actualThreadId);
|
|
@@ -19834,7 +19964,8 @@ Please start a new session.`, state.threadId);
|
|
|
19834
19964
|
needsContextPromptOnNextMessage: state.needsContextPromptOnNextMessage,
|
|
19835
19965
|
sessionTitle: state.sessionTitle,
|
|
19836
19966
|
sessionDescription: state.sessionDescription,
|
|
19837
|
-
messageCount: state.messageCount ?? 0
|
|
19967
|
+
messageCount: state.messageCount ?? 0,
|
|
19968
|
+
statusBarTimer: null
|
|
19838
19969
|
};
|
|
19839
19970
|
ctx.sessions.set(sessionId, session);
|
|
19840
19971
|
if (state.sessionStartPostId) {
|
|
@@ -19927,6 +20058,10 @@ async function handleExit(sessionId, code, ctx) {
|
|
|
19927
20058
|
clearTimeout(session.updateTimer);
|
|
19928
20059
|
session.updateTimer = null;
|
|
19929
20060
|
}
|
|
20061
|
+
if (session.statusBarTimer) {
|
|
20062
|
+
clearInterval(session.statusBarTimer);
|
|
20063
|
+
session.statusBarTimer = null;
|
|
20064
|
+
}
|
|
19930
20065
|
ctx.sessions.delete(session.sessionId);
|
|
19931
20066
|
keepAlive.sessionEnded();
|
|
19932
20067
|
return;
|
|
@@ -19938,6 +20073,10 @@ async function handleExit(sessionId, code, ctx) {
|
|
|
19938
20073
|
clearTimeout(session.updateTimer);
|
|
19939
20074
|
session.updateTimer = null;
|
|
19940
20075
|
}
|
|
20076
|
+
if (session.statusBarTimer) {
|
|
20077
|
+
clearInterval(session.statusBarTimer);
|
|
20078
|
+
session.statusBarTimer = null;
|
|
20079
|
+
}
|
|
19941
20080
|
ctx.persistSession(session);
|
|
19942
20081
|
ctx.sessions.delete(session.sessionId);
|
|
19943
20082
|
for (const [postId, tid] of ctx.postIndex.entries()) {
|
|
@@ -19960,6 +20099,10 @@ async function handleExit(sessionId, code, ctx) {
|
|
|
19960
20099
|
clearTimeout(session.updateTimer);
|
|
19961
20100
|
session.updateTimer = null;
|
|
19962
20101
|
}
|
|
20102
|
+
if (session.statusBarTimer) {
|
|
20103
|
+
clearInterval(session.statusBarTimer);
|
|
20104
|
+
session.statusBarTimer = null;
|
|
20105
|
+
}
|
|
19963
20106
|
ctx.sessions.delete(session.sessionId);
|
|
19964
20107
|
keepAlive.sessionEnded();
|
|
19965
20108
|
try {
|
|
@@ -19974,6 +20117,10 @@ async function handleExit(sessionId, code, ctx) {
|
|
|
19974
20117
|
clearTimeout(session.updateTimer);
|
|
19975
20118
|
session.updateTimer = null;
|
|
19976
20119
|
}
|
|
20120
|
+
if (session.statusBarTimer) {
|
|
20121
|
+
clearInterval(session.statusBarTimer);
|
|
20122
|
+
session.statusBarTimer = null;
|
|
20123
|
+
}
|
|
19977
20124
|
await ctx.flush(session);
|
|
19978
20125
|
if (code !== 0 && code !== null) {
|
|
19979
20126
|
await session.platform.createPost(`**[Exited: ${code}]**`, session.threadId);
|
|
@@ -20302,7 +20449,17 @@ async function createAndSwitchToWorktree(session, branch, username, options) {
|
|
|
20302
20449
|
const repoRoot = await getRepositoryRoot(session.workingDir);
|
|
20303
20450
|
const existing = await findWorktreeByBranch(repoRoot, branch);
|
|
20304
20451
|
if (existing && !existing.isMain) {
|
|
20305
|
-
|
|
20452
|
+
const shortPath = existing.path.replace(process.env.HOME || "", "~");
|
|
20453
|
+
const post = await session.platform.createInteractivePost(`\uD83C\uDF3F **Worktree for branch \`${branch}\` already exists** at \`${shortPath}\`.
|
|
20454
|
+
` + `React with \uD83D\uDC4D to join this worktree, or \u274C to continue in the current directory.`, ["+1", "x"], session.threadId);
|
|
20455
|
+
session.pendingExistingWorktreePrompt = {
|
|
20456
|
+
postId: post.id,
|
|
20457
|
+
branch,
|
|
20458
|
+
worktreePath: existing.path,
|
|
20459
|
+
username
|
|
20460
|
+
};
|
|
20461
|
+
options.registerPost(post.id, session.threadId);
|
|
20462
|
+
options.persistSession(session);
|
|
20306
20463
|
return;
|
|
20307
20464
|
}
|
|
20308
20465
|
const shortId = session.threadId.substring(0, 8);
|
|
@@ -20571,6 +20728,8 @@ function clearContextPromptTimeout(pending) {
|
|
|
20571
20728
|
}
|
|
20572
20729
|
|
|
20573
20730
|
// src/session/sticky-message.ts
|
|
20731
|
+
import { hostname } from "os";
|
|
20732
|
+
var botStartedAt = new Date;
|
|
20574
20733
|
var stickyPostIds = new Map;
|
|
20575
20734
|
var needsBump = new Map;
|
|
20576
20735
|
var updateLocks = new Map;
|
|
@@ -20613,6 +20772,35 @@ function getSessionTopic(session) {
|
|
|
20613
20772
|
}
|
|
20614
20773
|
return formatTopicFromPrompt(session.firstPrompt);
|
|
20615
20774
|
}
|
|
20775
|
+
async function buildStatusBar(sessionCount, config) {
|
|
20776
|
+
const items = [];
|
|
20777
|
+
items.push(`\`v${VERSION}\``);
|
|
20778
|
+
items.push(`\`${sessionCount}/${config.maxSessions} sessions\``);
|
|
20779
|
+
const permMode = config.skipPermissions ? "\u26A1 Auto" : "\uD83D\uDD10 Interactive";
|
|
20780
|
+
items.push(`\`${permMode}\``);
|
|
20781
|
+
if (config.worktreeMode === "require") {
|
|
20782
|
+
items.push("`\uD83C\uDF3F Worktree: require`");
|
|
20783
|
+
} else if (config.worktreeMode === "off") {
|
|
20784
|
+
items.push("`\uD83C\uDF3F Worktree: off`");
|
|
20785
|
+
}
|
|
20786
|
+
if (config.chromeEnabled) {
|
|
20787
|
+
items.push("`\uD83C\uDF10 Chrome`");
|
|
20788
|
+
}
|
|
20789
|
+
if (config.debug) {
|
|
20790
|
+
items.push("`\uD83D\uDC1B Debug`");
|
|
20791
|
+
}
|
|
20792
|
+
const battery = await formatBatteryStatus();
|
|
20793
|
+
if (battery) {
|
|
20794
|
+
items.push(`\`${battery}\``);
|
|
20795
|
+
}
|
|
20796
|
+
const uptime = formatUptime(botStartedAt);
|
|
20797
|
+
items.push(`\`\u23F1\uFE0F ${uptime}\``);
|
|
20798
|
+
const shortDir = config.workingDir.replace(process.env.HOME || "", "~");
|
|
20799
|
+
items.push(`\`\uD83D\uDCC2 ${shortDir}\``);
|
|
20800
|
+
const host = hostname();
|
|
20801
|
+
items.push(`\`\uD83D\uDCBB ${host}\``);
|
|
20802
|
+
return items.join(" \xB7 ");
|
|
20803
|
+
}
|
|
20616
20804
|
function formatTopicFromPrompt(prompt) {
|
|
20617
20805
|
if (!prompt)
|
|
20618
20806
|
return "_No topic_";
|
|
@@ -20626,13 +20814,16 @@ function formatTopicFromPrompt(prompt) {
|
|
|
20626
20814
|
}
|
|
20627
20815
|
return cleaned || "_No topic_";
|
|
20628
20816
|
}
|
|
20629
|
-
function buildStickyMessage(sessions, platformId) {
|
|
20817
|
+
async function buildStickyMessage(sessions, platformId, config) {
|
|
20630
20818
|
const platformSessions = [...sessions.values()].filter((s) => s.platformId === platformId);
|
|
20819
|
+
const statusBar = await buildStatusBar(platformSessions.length, config);
|
|
20631
20820
|
if (platformSessions.length === 0) {
|
|
20632
20821
|
return [
|
|
20633
20822
|
"---",
|
|
20634
20823
|
"**Active Claude Threads**",
|
|
20635
20824
|
"",
|
|
20825
|
+
statusBar,
|
|
20826
|
+
"",
|
|
20636
20827
|
"_No active sessions_",
|
|
20637
20828
|
"",
|
|
20638
20829
|
"_Mention me to start a session_ \xB7 `npm i -g claude-threads`"
|
|
@@ -20644,6 +20835,8 @@ function buildStickyMessage(sessions, platformId) {
|
|
|
20644
20835
|
const lines = [
|
|
20645
20836
|
"---",
|
|
20646
20837
|
`**Active Claude Threads** (${count})`,
|
|
20838
|
+
"",
|
|
20839
|
+
statusBar,
|
|
20647
20840
|
""
|
|
20648
20841
|
];
|
|
20649
20842
|
for (const session of platformSessions) {
|
|
@@ -20664,7 +20857,7 @@ function buildStickyMessage(sessions, platformId) {
|
|
|
20664
20857
|
`);
|
|
20665
20858
|
}
|
|
20666
20859
|
var DEBUG = process.env.DEBUG === "1";
|
|
20667
|
-
async function updateStickyMessage(platform, sessions) {
|
|
20860
|
+
async function updateStickyMessage(platform, sessions, config) {
|
|
20668
20861
|
const platformId = platform.platformId;
|
|
20669
20862
|
const pendingUpdate = updateLocks.get(platformId);
|
|
20670
20863
|
if (pendingUpdate) {
|
|
@@ -20676,14 +20869,14 @@ async function updateStickyMessage(platform, sessions) {
|
|
|
20676
20869
|
});
|
|
20677
20870
|
updateLocks.set(platformId, lock);
|
|
20678
20871
|
try {
|
|
20679
|
-
await updateStickyMessageImpl(platform, sessions);
|
|
20872
|
+
await updateStickyMessageImpl(platform, sessions, config);
|
|
20680
20873
|
} finally {
|
|
20681
20874
|
if (releaseLock)
|
|
20682
20875
|
releaseLock();
|
|
20683
20876
|
updateLocks.delete(platformId);
|
|
20684
20877
|
}
|
|
20685
20878
|
}
|
|
20686
|
-
async function updateStickyMessageImpl(platform, sessions) {
|
|
20879
|
+
async function updateStickyMessageImpl(platform, sessions, config) {
|
|
20687
20880
|
if (DEBUG) {
|
|
20688
20881
|
const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
|
|
20689
20882
|
console.log(`[sticky] updateStickyMessage called for ${platform.platformId}, ${platformSessions.length} sessions`);
|
|
@@ -20691,7 +20884,7 @@ async function updateStickyMessageImpl(platform, sessions) {
|
|
|
20691
20884
|
console.log(`[sticky] - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
|
|
20692
20885
|
}
|
|
20693
20886
|
}
|
|
20694
|
-
const content = buildStickyMessage(sessions, platform.platformId);
|
|
20887
|
+
const content = await buildStickyMessage(sessions, platform.platformId, config);
|
|
20695
20888
|
const existingPostId = stickyPostIds.get(platform.platformId);
|
|
20696
20889
|
const shouldBump = needsBump.get(platform.platformId) ?? false;
|
|
20697
20890
|
if (DEBUG) {
|
|
@@ -20762,8 +20955,8 @@ async function updateStickyMessageImpl(platform, sessions) {
|
|
|
20762
20955
|
console.error(` \u26A0\uFE0F Failed to update sticky message for ${platform.platformId}:`, err);
|
|
20763
20956
|
}
|
|
20764
20957
|
}
|
|
20765
|
-
async function updateAllStickyMessages(platforms, sessions) {
|
|
20766
|
-
const updates = [...platforms.values()].map((platform) => updateStickyMessage(platform, sessions));
|
|
20958
|
+
async function updateAllStickyMessages(platforms, sessions, config) {
|
|
20959
|
+
const updates = [...platforms.values()].map((platform) => updateStickyMessage(platform, sessions, config));
|
|
20767
20960
|
await Promise.all(updates);
|
|
20768
20961
|
}
|
|
20769
20962
|
function markNeedsBump(platformId) {
|
|
@@ -20985,6 +21178,15 @@ class SessionManager {
|
|
|
20985
21178
|
await handleWorktreeSkip(session, username, (s) => this.persistSession(s), (s, q) => this.offerContextPrompt(s, q));
|
|
20986
21179
|
return;
|
|
20987
21180
|
}
|
|
21181
|
+
if (session.pendingExistingWorktreePrompt?.postId === postId) {
|
|
21182
|
+
const handled = await handleExistingWorktreeReaction(session, postId, emojiName, username, {
|
|
21183
|
+
...this.getReactionContext(),
|
|
21184
|
+
switchToWorktree: (tid, branchOrPath, user) => this.switchToWorktree(tid, branchOrPath, user),
|
|
21185
|
+
persistSession: (s) => this.persistSession(s)
|
|
21186
|
+
});
|
|
21187
|
+
if (handled)
|
|
21188
|
+
return;
|
|
21189
|
+
}
|
|
20988
21190
|
if (session.pendingContextPrompt?.postId === postId) {
|
|
20989
21191
|
await this.handleContextPromptReaction(session, emojiName, username);
|
|
20990
21192
|
return;
|
|
@@ -21189,7 +21391,14 @@ class SessionManager {
|
|
|
21189
21391
|
await updateSessionHeader(session, this.getCommandContext());
|
|
21190
21392
|
}
|
|
21191
21393
|
async updateStickyMessage() {
|
|
21192
|
-
await updateAllStickyMessages(this.platforms, this.sessions
|
|
21394
|
+
await updateAllStickyMessages(this.platforms, this.sessions, {
|
|
21395
|
+
maxSessions: MAX_SESSIONS,
|
|
21396
|
+
chromeEnabled: this.chromeEnabled,
|
|
21397
|
+
skipPermissions: this.skipPermissions,
|
|
21398
|
+
worktreeMode: this.worktreeMode,
|
|
21399
|
+
workingDir: this.workingDir,
|
|
21400
|
+
debug: this.debug
|
|
21401
|
+
});
|
|
21193
21402
|
}
|
|
21194
21403
|
async initialize() {
|
|
21195
21404
|
initialize(this.sessionStore);
|
|
@@ -21345,7 +21554,8 @@ class SessionManager {
|
|
|
21345
21554
|
startTyping: (s) => this.startTyping(s),
|
|
21346
21555
|
stopTyping: (s) => this.stopTyping(s),
|
|
21347
21556
|
offerContextPrompt: (s, q) => this.offerContextPrompt(s, q),
|
|
21348
|
-
appendSystemPrompt: CHAT_PLATFORM_PROMPT
|
|
21557
|
+
appendSystemPrompt: CHAT_PLATFORM_PROMPT,
|
|
21558
|
+
registerPost: (postId, tid) => this.registerPost(postId, tid)
|
|
21349
21559
|
});
|
|
21350
21560
|
}
|
|
21351
21561
|
async switchToWorktree(threadId, branchOrPath, username) {
|