claude-threads 0.45.0 → 0.47.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 +27 -0
- package/dist/index.js +179 -69
- package/dist/mcp/permission-server.js +4 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.47.0] - 2026-01-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Session context in system prompt** - Claude now receives metadata about the session including version, current working directory, git status, and platform info (#119)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Task list duplication fixed** - Resolved race condition causing duplicate task lists by extending promise lock scope (#122)
|
|
17
|
+
- **Code blocks now render correctly** - Added trailing newline to code blocks for proper markdown rendering (#123)
|
|
18
|
+
- **Worktree paths shortened in UI** - Paths now show as `[branch]/path` instead of full worktree paths for better readability (#121)
|
|
19
|
+
- **Worktree metadata centralized** - Moved `.claude-threads-meta.json` to central config directory to avoid polluting project directories (#120)
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **Bump @modelcontextprotocol/sdk** - Updated from 1.25.1 to 1.25.2 (#118)
|
|
23
|
+
|
|
24
|
+
## [0.46.0] - 2026-01-07
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **Emoji reactions for `!update` command** - React with 👍 to update immediately or 👎 to defer for 1 hour, easier than typing commands on mobile
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- **Auto-update uses bun instead of npm** - Fixed updates to use `bun install -g` matching the actual install location
|
|
31
|
+
- **ESLint warnings resolved** - Fixed 8 non-null assertion warnings with proper null checks
|
|
32
|
+
- **Dead code removed** - Removed unused Discord formatter/types and other dead code via Knip
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **Knip added to CI and pre-commit** - Dead code detection now runs automatically
|
|
36
|
+
|
|
10
37
|
## [0.45.0] - 2026-01-07
|
|
11
38
|
|
|
12
39
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -42492,7 +42492,8 @@ class MattermostFormatter {
|
|
|
42492
42492
|
const lang = language || "";
|
|
42493
42493
|
return `\`\`\`${lang}
|
|
42494
42494
|
${code}
|
|
42495
|
-
|
|
42495
|
+
\`\`\`
|
|
42496
|
+
`;
|
|
42496
42497
|
}
|
|
42497
42498
|
formatUserMention(username) {
|
|
42498
42499
|
return `@${username}`;
|
|
@@ -43147,7 +43148,8 @@ class SlackFormatter {
|
|
|
43147
43148
|
formatCodeBlock(code, _language) {
|
|
43148
43149
|
return `\`\`\`
|
|
43149
43150
|
${code}
|
|
43150
|
-
|
|
43151
|
+
\`\`\`
|
|
43152
|
+
`;
|
|
43151
43153
|
}
|
|
43152
43154
|
formatUserMention(username, userId) {
|
|
43153
43155
|
if (userId) {
|
|
@@ -44840,27 +44842,32 @@ function isValidBranchName(name) {
|
|
|
44840
44842
|
return false;
|
|
44841
44843
|
return true;
|
|
44842
44844
|
}
|
|
44843
|
-
var
|
|
44844
|
-
function
|
|
44845
|
-
|
|
44845
|
+
var METADATA_STORE_PATH = path.join(homedir3(), ".claude-threads", "worktree-metadata.json");
|
|
44846
|
+
async function readMetadataStore() {
|
|
44847
|
+
try {
|
|
44848
|
+
const content = await fs.readFile(METADATA_STORE_PATH, "utf-8");
|
|
44849
|
+
return JSON.parse(content);
|
|
44850
|
+
} catch {
|
|
44851
|
+
return {};
|
|
44852
|
+
}
|
|
44846
44853
|
}
|
|
44847
|
-
async function
|
|
44848
|
-
const metaPath = getMetadataPath(worktreePath);
|
|
44854
|
+
async function writeMetadataStore(store) {
|
|
44849
44855
|
try {
|
|
44850
|
-
await fs.
|
|
44851
|
-
|
|
44856
|
+
await fs.mkdir(path.dirname(METADATA_STORE_PATH), { recursive: true });
|
|
44857
|
+
await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), "utf-8");
|
|
44852
44858
|
} catch (err) {
|
|
44853
|
-
log5.warn(`Failed to write worktree metadata: ${err}`);
|
|
44859
|
+
log5.warn(`Failed to write worktree metadata store: ${err}`);
|
|
44854
44860
|
}
|
|
44855
44861
|
}
|
|
44862
|
+
async function writeWorktreeMetadata(worktreePath, metadata) {
|
|
44863
|
+
const store = await readMetadataStore();
|
|
44864
|
+
store[worktreePath] = metadata;
|
|
44865
|
+
await writeMetadataStore(store);
|
|
44866
|
+
log5.debug(`Wrote worktree metadata for: ${worktreePath}`);
|
|
44867
|
+
}
|
|
44856
44868
|
async function readWorktreeMetadata(worktreePath) {
|
|
44857
|
-
const
|
|
44858
|
-
|
|
44859
|
-
const content = await fs.readFile(metaPath, "utf-8");
|
|
44860
|
-
return JSON.parse(content);
|
|
44861
|
-
} catch {
|
|
44862
|
-
return null;
|
|
44863
|
-
}
|
|
44869
|
+
const store = await readMetadataStore();
|
|
44870
|
+
return store[worktreePath] || null;
|
|
44864
44871
|
}
|
|
44865
44872
|
|
|
44866
44873
|
// src/utils/format.ts
|
|
@@ -45183,29 +45190,43 @@ function stopTyping(session) {
|
|
|
45183
45190
|
}
|
|
45184
45191
|
}
|
|
45185
45192
|
async function bumpTasksToBottomWithContent(session, newContent, registerPost) {
|
|
45186
|
-
|
|
45187
|
-
|
|
45188
|
-
sessionLog(session).debug(`Bumping tasks to bottom, repurposing post ${oldTasksPostId.substring(0, 8)}`);
|
|
45189
|
-
try {
|
|
45190
|
-
await session.platform.removeReaction(oldTasksPostId, TASK_TOGGLE_EMOJIS[0]);
|
|
45191
|
-
} catch (err) {
|
|
45192
|
-
sessionLog(session).debug(`Could not remove toggle emoji: ${err}`);
|
|
45193
|
+
if (session.taskListCreationPromise) {
|
|
45194
|
+
await session.taskListCreationPromise;
|
|
45193
45195
|
}
|
|
45194
|
-
|
|
45195
|
-
|
|
45196
|
-
|
|
45197
|
-
|
|
45198
|
-
|
|
45199
|
-
const
|
|
45200
|
-
|
|
45201
|
-
sessionLog(session).debug(`
|
|
45202
|
-
|
|
45203
|
-
|
|
45204
|
-
|
|
45205
|
-
|
|
45206
|
-
|
|
45196
|
+
let resolveCreation;
|
|
45197
|
+
session.taskListCreationPromise = new Promise((resolve2) => {
|
|
45198
|
+
resolveCreation = resolve2;
|
|
45199
|
+
});
|
|
45200
|
+
try {
|
|
45201
|
+
const oldTasksPostId = session.tasksPostId;
|
|
45202
|
+
const oldTasksContent = session.lastTasksContent;
|
|
45203
|
+
sessionLog(session).debug(`Bumping tasks to bottom, repurposing post ${oldTasksPostId.substring(0, 8)}`);
|
|
45204
|
+
try {
|
|
45205
|
+
await session.platform.removeReaction(oldTasksPostId, TASK_TOGGLE_EMOJIS[0]);
|
|
45206
|
+
} catch (err) {
|
|
45207
|
+
sessionLog(session).debug(`Could not remove toggle emoji: ${err}`);
|
|
45208
|
+
}
|
|
45209
|
+
await session.platform.unpinPost(oldTasksPostId).catch(() => {});
|
|
45210
|
+
await withErrorHandling(() => session.platform.updatePost(oldTasksPostId, newContent), { action: "Repurpose task post", session });
|
|
45211
|
+
registerPost(oldTasksPostId, session.threadId);
|
|
45212
|
+
if (oldTasksContent) {
|
|
45213
|
+
const displayContent = getTaskDisplayContent(session);
|
|
45214
|
+
const newTasksPost = await session.platform.createInteractivePost(displayContent, [TASK_TOGGLE_EMOJIS[0]], session.threadId);
|
|
45215
|
+
session.tasksPostId = newTasksPost.id;
|
|
45216
|
+
sessionLog(session).debug(`Created new task post ${newTasksPost.id.substring(0, 8)}`);
|
|
45217
|
+
registerPost(newTasksPost.id, session.threadId);
|
|
45218
|
+
updateLastMessage(session, newTasksPost);
|
|
45219
|
+
await session.platform.pinPost(newTasksPost.id).catch(() => {});
|
|
45220
|
+
} else {
|
|
45221
|
+
session.tasksPostId = null;
|
|
45222
|
+
}
|
|
45223
|
+
return oldTasksPostId;
|
|
45224
|
+
} finally {
|
|
45225
|
+
if (resolveCreation) {
|
|
45226
|
+
resolveCreation();
|
|
45227
|
+
}
|
|
45228
|
+
session.taskListCreationPromise = undefined;
|
|
45207
45229
|
}
|
|
45208
|
-
return oldTasksPostId;
|
|
45209
45230
|
}
|
|
45210
45231
|
async function bumpTasksToBottom(session, registerPost) {
|
|
45211
45232
|
if (!session.tasksPostId || !session.lastTasksContent) {
|
|
@@ -45216,9 +45237,20 @@ async function bumpTasksToBottom(session, registerPost) {
|
|
|
45216
45237
|
sessionLog(session).debug("Tasks completed, not bumping");
|
|
45217
45238
|
return;
|
|
45218
45239
|
}
|
|
45219
|
-
|
|
45220
|
-
|
|
45240
|
+
if (session.taskListCreationPromise) {
|
|
45241
|
+
await session.taskListCreationPromise;
|
|
45242
|
+
}
|
|
45243
|
+
if (!session.tasksPostId || !session.lastTasksContent || session.tasksCompleted) {
|
|
45244
|
+
sessionLog(session).debug("Task list state changed while waiting for lock");
|
|
45245
|
+
return;
|
|
45246
|
+
}
|
|
45247
|
+
let resolveCreation;
|
|
45248
|
+
session.taskListCreationPromise = new Promise((resolve2) => {
|
|
45249
|
+
resolveCreation = resolve2;
|
|
45250
|
+
});
|
|
45221
45251
|
try {
|
|
45252
|
+
const oldPostId = session.tasksPostId;
|
|
45253
|
+
sessionLog(session).debug(`Bumping tasks: deleting old post ${oldPostId.substring(0, 8)}`);
|
|
45222
45254
|
await session.platform.unpinPost(session.tasksPostId).catch(() => {});
|
|
45223
45255
|
await session.platform.deletePost(session.tasksPostId);
|
|
45224
45256
|
const displayContent = getTaskDisplayContent(session);
|
|
@@ -45232,6 +45264,11 @@ async function bumpTasksToBottom(session, registerPost) {
|
|
|
45232
45264
|
await session.platform.pinPost(newPost.id).catch(() => {});
|
|
45233
45265
|
} catch (err) {
|
|
45234
45266
|
sessionLog(session).error(`Failed to bump tasks to bottom: ${err}`);
|
|
45267
|
+
} finally {
|
|
45268
|
+
if (resolveCreation) {
|
|
45269
|
+
resolveCreation();
|
|
45270
|
+
}
|
|
45271
|
+
session.taskListCreationPromise = undefined;
|
|
45235
45272
|
}
|
|
45236
45273
|
}
|
|
45237
45274
|
async function flush(session, registerPost) {
|
|
@@ -45243,6 +45280,7 @@ async function flush(session, registerPost) {
|
|
|
45243
45280
|
const { maxLength: MAX_POST_LENGTH, hardThreshold: HARD_CONTINUATION_THRESHOLD } = session.platform.getMessageLimits();
|
|
45244
45281
|
const shouldBreakEarly = session.currentPostId && content.length > MIN_BREAK_THRESHOLD && shouldFlushEarly(content);
|
|
45245
45282
|
if (session.currentPostId && (content.length > HARD_CONTINUATION_THRESHOLD || shouldBreakEarly)) {
|
|
45283
|
+
const currentPostId = session.currentPostId;
|
|
45246
45284
|
let breakPoint;
|
|
45247
45285
|
let codeBlockLanguage;
|
|
45248
45286
|
if (content.length > HARD_CONTINUATION_THRESHOLD) {
|
|
@@ -45275,7 +45313,7 @@ async function flush(session, registerPost) {
|
|
|
45275
45313
|
breakPoint = breakInfo.position;
|
|
45276
45314
|
} else {
|
|
45277
45315
|
try {
|
|
45278
|
-
await session.platform.updatePost(
|
|
45316
|
+
await session.platform.updatePost(currentPostId, content);
|
|
45279
45317
|
} catch {
|
|
45280
45318
|
sessionLog(session).debug("Update failed (no breakpoint), will create new post on next flush");
|
|
45281
45319
|
session.currentPostId = null;
|
|
@@ -45295,7 +45333,7 @@ async function flush(session, registerPost) {
|
|
|
45295
45333
|
|
|
45296
45334
|
` + formatter2.formatItalic("... (continued below)") : firstPart;
|
|
45297
45335
|
try {
|
|
45298
|
-
await session.platform.updatePost(
|
|
45336
|
+
await session.platform.updatePost(currentPostId, firstPartWithMarker);
|
|
45299
45337
|
} catch {
|
|
45300
45338
|
sessionLog(session).debug("Update failed during split, continuing with new post");
|
|
45301
45339
|
}
|
|
@@ -45329,8 +45367,9 @@ async function flush(session, registerPost) {
|
|
|
45329
45367
|
` + formatter2.formatItalic("... (truncated)");
|
|
45330
45368
|
}
|
|
45331
45369
|
if (session.currentPostId) {
|
|
45370
|
+
const postId = session.currentPostId;
|
|
45332
45371
|
try {
|
|
45333
|
-
await session.platform.updatePost(
|
|
45372
|
+
await session.platform.updatePost(postId, content);
|
|
45334
45373
|
} catch {
|
|
45335
45374
|
sessionLog(session).debug("Update failed, will create new post on next flush");
|
|
45336
45375
|
session.currentPostId = null;
|
|
@@ -46581,7 +46620,9 @@ async function handleTodoWrite(session, input, ctx) {
|
|
|
46581
46620
|
await session.platform.pinPost(post.id).catch(() => {});
|
|
46582
46621
|
}
|
|
46583
46622
|
} finally {
|
|
46584
|
-
resolveCreation
|
|
46623
|
+
if (resolveCreation) {
|
|
46624
|
+
resolveCreation();
|
|
46625
|
+
}
|
|
46585
46626
|
session.taskListCreationPromise = undefined;
|
|
46586
46627
|
}
|
|
46587
46628
|
}
|
|
@@ -46633,7 +46674,8 @@ async function handleCompactionComplete(session, compactMetadata, _ctx) {
|
|
|
46633
46674
|
const formatter = session.platform.getFormatter();
|
|
46634
46675
|
const completionMessage = `\u2705 ${formatter.formatBold("Context compacted")} ${formatter.formatItalic(`(${info})`)}`;
|
|
46635
46676
|
if (session.compactionPostId) {
|
|
46636
|
-
|
|
46677
|
+
const postId = session.compactionPostId;
|
|
46678
|
+
await withErrorHandling(() => session.platform.updatePost(postId, completionMessage), { action: "Update compaction complete", session });
|
|
46637
46679
|
session.compactionPostId = undefined;
|
|
46638
46680
|
} else {
|
|
46639
46681
|
const post = await withErrorHandling(() => session.platform.createPost(completionMessage, session.threadId), { action: "Post compaction complete", session });
|
|
@@ -46933,7 +46975,7 @@ async function handleExistingWorktreeReaction(session, postId, emojiName, userna
|
|
|
46933
46975
|
if (!isApprove && !isDeny) {
|
|
46934
46976
|
return false;
|
|
46935
46977
|
}
|
|
46936
|
-
const shortPath = pending.worktreePath
|
|
46978
|
+
const shortPath = shortenPath(pending.worktreePath, undefined, { path: pending.worktreePath, branch: pending.branch });
|
|
46937
46979
|
const formatter = session.platform.getFormatter();
|
|
46938
46980
|
if (isApprove) {
|
|
46939
46981
|
await session.platform.updatePost(pending.postId, `\u2705 Joining worktree for branch ${formatter.formatCode(pending.branch)} at ${formatter.formatCode(shortPath)}`);
|
|
@@ -46949,6 +46991,37 @@ async function handleExistingWorktreeReaction(session, postId, emojiName, userna
|
|
|
46949
46991
|
}
|
|
46950
46992
|
return true;
|
|
46951
46993
|
}
|
|
46994
|
+
async function handleUpdateReaction(session, postId, emojiName, username, ctx, updateHandler) {
|
|
46995
|
+
const pending = session.pendingUpdatePrompt;
|
|
46996
|
+
if (!pending || pending.postId !== postId) {
|
|
46997
|
+
return false;
|
|
46998
|
+
}
|
|
46999
|
+
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
47000
|
+
return false;
|
|
47001
|
+
}
|
|
47002
|
+
const isUpdateNow = isApprovalEmoji(emojiName);
|
|
47003
|
+
const isDefer = isDenialEmoji(emojiName);
|
|
47004
|
+
if (!isUpdateNow && !isDefer) {
|
|
47005
|
+
return false;
|
|
47006
|
+
}
|
|
47007
|
+
const formatter = session.platform.getFormatter();
|
|
47008
|
+
if (isUpdateNow) {
|
|
47009
|
+
await withErrorHandling(() => session.platform.updatePost(pending.postId, `\uD83D\uDD04 ${formatter.formatBold("Forcing update")} - restarting shortly...
|
|
47010
|
+
` + formatter.formatItalic("Sessions will resume automatically")), { action: "Update post for force update", session });
|
|
47011
|
+
session.pendingUpdatePrompt = undefined;
|
|
47012
|
+
ctx.ops.persistSession(session);
|
|
47013
|
+
sessionLog3(session).info(`\uD83D\uDD04 @${username} triggered immediate update`);
|
|
47014
|
+
await updateHandler.forceUpdate();
|
|
47015
|
+
} else {
|
|
47016
|
+
updateHandler.deferUpdate(60);
|
|
47017
|
+
await withErrorHandling(() => session.platform.updatePost(pending.postId, `\u23F8\uFE0F ${formatter.formatBold("Update deferred")} for 1 hour
|
|
47018
|
+
` + formatter.formatItalic("Use !update now to apply earlier")), { action: "Update post for defer update", session });
|
|
47019
|
+
session.pendingUpdatePrompt = undefined;
|
|
47020
|
+
ctx.ops.persistSession(session);
|
|
47021
|
+
sessionLog3(session).info(`\u23F8\uFE0F @${username} deferred update for 1 hour`);
|
|
47022
|
+
}
|
|
47023
|
+
return true;
|
|
47024
|
+
}
|
|
46952
47025
|
|
|
46953
47026
|
// src/claude/cli.ts
|
|
46954
47027
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -50700,7 +50773,7 @@ function checkForUpdates() {
|
|
|
50700
50773
|
cachedUpdateInfo = notifier.update;
|
|
50701
50774
|
notifier.notify({
|
|
50702
50775
|
message: `Update available: {currentVersion} \u2192 {latestVersion}
|
|
50703
|
-
Run:
|
|
50776
|
+
Run: bun install -g claude-threads`
|
|
50704
50777
|
});
|
|
50705
50778
|
} catch {}
|
|
50706
50779
|
}
|
|
@@ -51144,7 +51217,7 @@ function validateClaudeCli() {
|
|
|
51144
51217
|
installed: false,
|
|
51145
51218
|
version: null,
|
|
51146
51219
|
compatible: false,
|
|
51147
|
-
message: "Claude CLI not found. Install it with:
|
|
51220
|
+
message: "Claude CLI not found. Install it with: bun install -g @anthropic-ai/claude-code"
|
|
51148
51221
|
};
|
|
51149
51222
|
}
|
|
51150
51223
|
const compatible = isVersionCompatible(version);
|
|
@@ -51154,7 +51227,7 @@ function validateClaudeCli() {
|
|
|
51154
51227
|
version,
|
|
51155
51228
|
compatible: false,
|
|
51156
51229
|
message: `Claude CLI version ${version} is not compatible. Required: ${CLAUDE_CLI_VERSION_RANGE}
|
|
51157
|
-
` + `Install a compatible version:
|
|
51230
|
+
` + `Install a compatible version: bun install -g @anthropic-ai/claude-code@2.0.76`
|
|
51158
51231
|
};
|
|
51159
51232
|
}
|
|
51160
51233
|
return {
|
|
@@ -51271,7 +51344,8 @@ async function changeDirectory(session, newDir, username, ctx) {
|
|
|
51271
51344
|
sessionLog4(session).warn(`\uD83D\uDCC2 Not a directory: ${newDir}`);
|
|
51272
51345
|
return;
|
|
51273
51346
|
}
|
|
51274
|
-
const
|
|
51347
|
+
const worktreeContext = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
|
|
51348
|
+
const shortDir = shortenPath(absoluteDir, undefined, worktreeContext);
|
|
51275
51349
|
sessionLog4(session).info(`\uD83D\uDCC2 Changing directory to ${shortDir}`);
|
|
51276
51350
|
session.workingDir = absoluteDir;
|
|
51277
51351
|
const newSessionId = randomUUID2();
|
|
@@ -51405,7 +51479,8 @@ async function updateSessionHeader(session, ctx) {
|
|
|
51405
51479
|
if (!session.sessionStartPostId)
|
|
51406
51480
|
return;
|
|
51407
51481
|
const formatter = session.platform.getFormatter();
|
|
51408
|
-
const
|
|
51482
|
+
const worktreeContext = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
|
|
51483
|
+
const shortDir = shortenPath(session.workingDir, undefined, worktreeContext);
|
|
51409
51484
|
const isInteractive = !ctx.config.skipPermissions || session.forceInteractivePermissions;
|
|
51410
51485
|
const permMode = isInteractive ? "\uD83D\uDD10 Interactive" : "\u26A1 Auto";
|
|
51411
51486
|
const otherParticipants = [...session.sessionAllowedUsers].filter((u) => u !== session.startedBy).map((u) => formatter.formatUserMention(u)).join(", ");
|
|
@@ -51477,7 +51552,7 @@ async function updateSessionHeader(session, ctx) {
|
|
|
51477
51552
|
items.push(["\uD83C\uDD94", "Session ID", formatter.formatCode(session.claudeSessionId.substring(0, 8))]);
|
|
51478
51553
|
const updateInfo = getUpdateInfo();
|
|
51479
51554
|
const updateNotice = updateInfo ? `
|
|
51480
|
-
> \u26A0\uFE0F ${formatter.formatBold("Update available:")} v${updateInfo.current} \u2192 v${updateInfo.latest} - Run ${formatter.formatCode("
|
|
51555
|
+
> \u26A0\uFE0F ${formatter.formatBold("Update available:")} v${updateInfo.current} \u2192 v${updateInfo.latest} - Run ${formatter.formatCode("bun install -g claude-threads")}
|
|
51481
51556
|
` : "";
|
|
51482
51557
|
const releaseNotes = getReleaseNotes(VERSION);
|
|
51483
51558
|
const whatsNew = releaseNotes ? getWhatsNewSummary(releaseNotes) : "";
|
|
@@ -51520,15 +51595,15 @@ async function showUpdateStatus(session, updateManager) {
|
|
|
51520
51595
|
} else {
|
|
51521
51596
|
statusLine = `Mode: ${config.autoRestartMode}`;
|
|
51522
51597
|
}
|
|
51523
|
-
|
|
51598
|
+
const message = `\uD83D\uDD04 ${formatter.formatBold("Update available")}
|
|
51524
51599
|
|
|
51525
51600
|
` + `Current: v${updateInfo.currentVersion}
|
|
51526
51601
|
` + `Latest: v${updateInfo.latestVersion}
|
|
51527
51602
|
` + `${statusLine}
|
|
51528
51603
|
|
|
51529
|
-
` + `
|
|
51530
|
-
|
|
51531
|
-
|
|
51604
|
+
` + `React: \uD83D\uDC4D Update now | \uD83D\uDC4E Defer for 1 hour`;
|
|
51605
|
+
const post = await session.platform.createInteractivePost(message, [APPROVAL_EMOJIS[0], DENIAL_EMOJIS[0]], session.threadId);
|
|
51606
|
+
session.pendingUpdatePrompt = { postId: post.id };
|
|
51532
51607
|
}
|
|
51533
51608
|
async function forceUpdateNow(session, username, updateManager) {
|
|
51534
51609
|
if (!await requireSessionOwner(session, username, "force updates")) {
|
|
@@ -51607,6 +51682,10 @@ function findPersistedByThreadId(persisted, threadId) {
|
|
|
51607
51682
|
}
|
|
51608
51683
|
return;
|
|
51609
51684
|
}
|
|
51685
|
+
function buildSessionContext(platform, workingDir) {
|
|
51686
|
+
const platformName = platform.platformType.charAt(0).toUpperCase() + platform.platformType.slice(1);
|
|
51687
|
+
return `**Platform:** ${platformName} (${platform.displayName}) | **Working Directory:** ${workingDir}`;
|
|
51688
|
+
}
|
|
51610
51689
|
var CHAT_PLATFORM_PROMPT = `
|
|
51611
51690
|
You are running inside a chat platform (like Mattermost or Slack). Users interact with you through chat messages in a thread.
|
|
51612
51691
|
|
|
@@ -51704,6 +51783,10 @@ ${startFormatter.formatItalic("Starting session...")}`, replyToPostId), { action
|
|
|
51704
51783
|
const actualThreadId = replyToPostId || post.id;
|
|
51705
51784
|
const sessionId = ctx.ops.getSessionId(platformId, actualThreadId);
|
|
51706
51785
|
const claudeSessionId = randomUUID3();
|
|
51786
|
+
const sessionContext = buildSessionContext(platform, ctx.config.workingDir);
|
|
51787
|
+
const systemPrompt = `${sessionContext}
|
|
51788
|
+
|
|
51789
|
+
${CHAT_PLATFORM_PROMPT}`;
|
|
51707
51790
|
const platformMcpConfig = platform.getMcpConfig();
|
|
51708
51791
|
const cliOptions = {
|
|
51709
51792
|
workingDir: ctx.config.workingDir,
|
|
@@ -51713,7 +51796,7 @@ ${startFormatter.formatItalic("Starting session...")}`, replyToPostId), { action
|
|
|
51713
51796
|
resume: false,
|
|
51714
51797
|
chrome: ctx.config.chromeEnabled,
|
|
51715
51798
|
platformConfig: platformMcpConfig,
|
|
51716
|
-
appendSystemPrompt:
|
|
51799
|
+
appendSystemPrompt: systemPrompt,
|
|
51717
51800
|
logSessionId: sessionId
|
|
51718
51801
|
};
|
|
51719
51802
|
const claude = new ClaudeCli(cliOptions);
|
|
@@ -51844,6 +51927,13 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
|
|
|
51844
51927
|
const skipPerms = ctx.config.skipPermissions && !state.forceInteractivePermissions;
|
|
51845
51928
|
const platformMcpConfig = platform.getMcpConfig();
|
|
51846
51929
|
const needsTitlePrompt = !state.sessionTitle;
|
|
51930
|
+
let appendSystemPrompt;
|
|
51931
|
+
if (needsTitlePrompt) {
|
|
51932
|
+
const sessionContext = buildSessionContext(platform, state.workingDir);
|
|
51933
|
+
appendSystemPrompt = `${sessionContext}
|
|
51934
|
+
|
|
51935
|
+
${CHAT_PLATFORM_PROMPT}`;
|
|
51936
|
+
}
|
|
51847
51937
|
const cliOptions = {
|
|
51848
51938
|
workingDir: state.workingDir,
|
|
51849
51939
|
threadId: state.threadId,
|
|
@@ -51852,7 +51942,7 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
|
|
|
51852
51942
|
resume: true,
|
|
51853
51943
|
chrome: ctx.config.chromeEnabled,
|
|
51854
51944
|
platformConfig: platformMcpConfig,
|
|
51855
|
-
appendSystemPrompt
|
|
51945
|
+
appendSystemPrompt,
|
|
51856
51946
|
logSessionId: sessionId
|
|
51857
51947
|
};
|
|
51858
51948
|
const claude = new ClaudeCli(cliOptions);
|
|
@@ -51929,9 +52019,10 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
|
|
|
51929
52019
|
sessionLog5(session).info(`\uD83D\uDD04 Session resumed (@${state.startedBy})`);
|
|
51930
52020
|
const sessionFormatter = session.platform.getFormatter();
|
|
51931
52021
|
if (session.lifecyclePostId) {
|
|
52022
|
+
const postId = session.lifecyclePostId;
|
|
51932
52023
|
const resumeMsg = `\uD83D\uDD04 ${sessionFormatter.formatBold("Session resumed")} by ${sessionFormatter.formatUserMention(session.startedBy)}
|
|
51933
52024
|
${sessionFormatter.formatItalic("Reconnected to Claude session. You can continue where you left off.")}`;
|
|
51934
|
-
await withErrorHandling(() => session.platform.updatePost(
|
|
52025
|
+
await withErrorHandling(() => session.platform.updatePost(postId, resumeMsg), { action: "Update timeout/shutdown post for resume", session });
|
|
51935
52026
|
session.lifecyclePostId = undefined;
|
|
51936
52027
|
} else {
|
|
51937
52028
|
const restartMsg = `${sessionFormatter.formatBold("Session resumed")} after bot restart (v${VERSION})
|
|
@@ -52163,7 +52254,8 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
|
|
|
52163
52254
|
|
|
52164
52255
|
\uD83D\uDCA1 React with \uD83D\uDD04 to resume, or send a new message to continue.`;
|
|
52165
52256
|
if (session.lifecyclePostId) {
|
|
52166
|
-
|
|
52257
|
+
const postId = session.lifecyclePostId;
|
|
52258
|
+
await withErrorHandling(() => session.platform.updatePost(postId, `\u23F1\uFE0F ${timeoutMessage}`), { action: "Update timeout post", session });
|
|
52167
52259
|
} else {
|
|
52168
52260
|
const timeoutPost = await withErrorHandling(() => postTimeout(session, timeoutMessage), { action: "Post session timeout", session });
|
|
52169
52261
|
if (timeoutPost) {
|
|
@@ -52297,7 +52389,7 @@ async function createAndSwitchToWorktree(session, branch, username, options) {
|
|
|
52297
52389
|
const repoRoot = await getRepositoryRoot(session.workingDir);
|
|
52298
52390
|
const existing = await findWorktreeByBranch(repoRoot, branch);
|
|
52299
52391
|
if (existing && !existing.isMain) {
|
|
52300
|
-
const shortPath = existing.path
|
|
52392
|
+
const shortPath = shortenPath(existing.path, undefined, { path: existing.path, branch });
|
|
52301
52393
|
const fmt = session.platform.getFormatter();
|
|
52302
52394
|
if (session.pendingWorktreePrompt) {
|
|
52303
52395
|
sessionLog6(session).info(`\uD83C\uDF3F Auto-joining existing worktree ${branch} (user specified inline)`);
|
|
@@ -52430,7 +52522,7 @@ ${fmt.formatItalic("Claude Code restarted in the worktree")}`);
|
|
|
52430
52522
|
session.claude.start();
|
|
52431
52523
|
}
|
|
52432
52524
|
await options.updateSessionHeader(session);
|
|
52433
|
-
const shortWorktreePath = worktreePath
|
|
52525
|
+
const shortWorktreePath = shortenPath(worktreePath, undefined, { path: worktreePath, branch });
|
|
52434
52526
|
const fmt = session.platform.getFormatter();
|
|
52435
52527
|
await postSuccess(session, `${fmt.formatBold("Created worktree")} for branch ${fmt.formatCode(branch)}
|
|
52436
52528
|
\uD83D\uDCC1 Working directory: ${fmt.formatCode(shortWorktreePath)}
|
|
@@ -52512,7 +52604,7 @@ async function listWorktreesCommand(session) {
|
|
|
52512
52604
|
|
|
52513
52605
|
`;
|
|
52514
52606
|
for (const wt of worktrees) {
|
|
52515
|
-
const shortPath = wt.path.replace(process.env.HOME || "", "~");
|
|
52607
|
+
const shortPath = wt.isMain ? wt.path.replace(process.env.HOME || "", "~") : shortenPath(wt.path, undefined, { path: wt.path, branch: wt.branch });
|
|
52516
52608
|
const isCurrent = session.workingDir === wt.path;
|
|
52517
52609
|
const marker = isCurrent ? " \u2190 current" : "";
|
|
52518
52610
|
const label = wt.isMain ? "(main repository)" : "";
|
|
@@ -52547,7 +52639,7 @@ async function removeWorktreeCommand(session, branchOrPath, username) {
|
|
|
52547
52639
|
}
|
|
52548
52640
|
try {
|
|
52549
52641
|
await removeWorktree(repoRoot, target.path);
|
|
52550
|
-
const shortPath = target.path
|
|
52642
|
+
const shortPath = shortenPath(target.path, undefined, { path: target.path, branch: target.branch });
|
|
52551
52643
|
await postSuccess(session, `Removed worktree \`${target.branch}\` at \`${shortPath}\``);
|
|
52552
52644
|
sessionLog6(session).info(`\uD83D\uDDD1\uFE0F Removed worktree ${target.branch} at ${shortPath}`);
|
|
52553
52645
|
} catch (err) {
|
|
@@ -52593,7 +52685,7 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
|
|
|
52593
52685
|
try {
|
|
52594
52686
|
sessionLog6(session).info(`\uD83D\uDDD1\uFE0F Cleaning up worktree: ${worktreePath}`);
|
|
52595
52687
|
await removeWorktree(repoRoot, worktreePath);
|
|
52596
|
-
const shortPath = worktreePath
|
|
52688
|
+
const shortPath = shortenPath(worktreePath, undefined, { path: worktreePath, branch });
|
|
52597
52689
|
await postSuccess(session, `Cleaned up worktree \`${branch}\` at \`${shortPath}\``);
|
|
52598
52690
|
sessionLog6(session).info(`\u2705 Worktree cleaned up successfully`);
|
|
52599
52691
|
} catch (err) {
|
|
@@ -53310,7 +53402,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
53310
53402
|
if (!this.worktreeUsers.has(worktreePath)) {
|
|
53311
53403
|
this.worktreeUsers.set(worktreePath, new Set);
|
|
53312
53404
|
}
|
|
53313
|
-
this.worktreeUsers.get(worktreePath)
|
|
53405
|
+
const users = this.worktreeUsers.get(worktreePath);
|
|
53406
|
+
if (users) {
|
|
53407
|
+
users.add(sessionId);
|
|
53408
|
+
}
|
|
53314
53409
|
log18.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
|
|
53315
53410
|
}
|
|
53316
53411
|
unregisterWorktreeUser(worktreePath, sessionId) {
|
|
@@ -53466,6 +53561,17 @@ class SessionManager extends EventEmitter4 {
|
|
|
53466
53561
|
if (handled)
|
|
53467
53562
|
return;
|
|
53468
53563
|
}
|
|
53564
|
+
if (action === "added" && session.pendingUpdatePrompt?.postId === postId) {
|
|
53565
|
+
if (this.autoUpdateManager) {
|
|
53566
|
+
const updateHandler = {
|
|
53567
|
+
forceUpdate: () => this.autoUpdateManager.forceUpdate(),
|
|
53568
|
+
deferUpdate: (minutes) => this.autoUpdateManager.deferUpdate(minutes)
|
|
53569
|
+
};
|
|
53570
|
+
const handled = await handleUpdateReaction(session, postId, emojiName, username, this.getContext(), updateHandler);
|
|
53571
|
+
if (handled)
|
|
53572
|
+
return;
|
|
53573
|
+
}
|
|
53574
|
+
}
|
|
53469
53575
|
if (action === "added" && session.pendingContextPrompt?.postId === postId) {
|
|
53470
53576
|
await this.handleContextPromptReaction(session, emojiName, username);
|
|
53471
53577
|
return;
|
|
@@ -64480,8 +64586,11 @@ async function installVersion(version) {
|
|
|
64480
64586
|
justUpdated: false
|
|
64481
64587
|
});
|
|
64482
64588
|
return new Promise((resolve7) => {
|
|
64483
|
-
const
|
|
64484
|
-
const
|
|
64589
|
+
const useBun = process.platform !== "win32";
|
|
64590
|
+
const cmd = useBun ? "bun" : "npm.cmd";
|
|
64591
|
+
const args = useBun ? ["install", "-g", `${PACKAGE_NAME3}@${version}`] : ["install", "-g", `${PACKAGE_NAME3}@${version}`];
|
|
64592
|
+
log21.debug(`Using ${useBun ? "bun" : "npm"} for installation`);
|
|
64593
|
+
const child = spawn5(cmd, args, {
|
|
64485
64594
|
stdio: ["ignore", "pipe", "pipe"],
|
|
64486
64595
|
env: {
|
|
64487
64596
|
...process.env,
|
|
@@ -64529,8 +64638,9 @@ async function installVersion(version) {
|
|
|
64529
64638
|
});
|
|
64530
64639
|
}
|
|
64531
64640
|
function getRollbackInstructions(previousVersion) {
|
|
64641
|
+
const cmd = process.platform === "win32" ? "npm" : "bun";
|
|
64532
64642
|
return `To rollback to the previous version, run:
|
|
64533
|
-
|
|
64643
|
+
${cmd} install -g ${PACKAGE_NAME3}@${previousVersion}`;
|
|
64534
64644
|
}
|
|
64535
64645
|
|
|
64536
64646
|
class UpdateInstaller {
|
|
@@ -33787,7 +33787,8 @@ class MattermostFormatter {
|
|
|
33787
33787
|
const lang = language || "";
|
|
33788
33788
|
return `\`\`\`${lang}
|
|
33789
33789
|
${code}
|
|
33790
|
-
|
|
33790
|
+
\`\`\`
|
|
33791
|
+
`;
|
|
33791
33792
|
}
|
|
33792
33793
|
formatUserMention(username) {
|
|
33793
33794
|
return `@${username}`;
|
|
@@ -34092,7 +34093,8 @@ class SlackFormatter {
|
|
|
34092
34093
|
formatCodeBlock(code, _language) {
|
|
34093
34094
|
return `\`\`\`
|
|
34094
34095
|
${code}
|
|
34095
|
-
|
|
34096
|
+
\`\`\`
|
|
34097
|
+
`;
|
|
34096
34098
|
}
|
|
34097
34099
|
formatUserMention(username, userId) {
|
|
34098
34100
|
if (userId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-threads",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@inkjs/ui": "^2.0.0",
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
66
|
+
"cli-spinners": "^3.3.0",
|
|
66
67
|
"commander": "^14.0.2",
|
|
67
68
|
"diff": "^8.0.2",
|
|
68
69
|
"ink": "^6.6.0",
|
|
@@ -76,7 +77,6 @@
|
|
|
76
77
|
"devDependencies": {
|
|
77
78
|
"@eslint/js": "^9.39.2",
|
|
78
79
|
"@types/bun": "latest",
|
|
79
|
-
"@types/diff": "^8.0.0",
|
|
80
80
|
"@types/node": "^25.0.3",
|
|
81
81
|
"@types/prompts": "^2.4.9",
|
|
82
82
|
"@types/react": "^19.2.7",
|