claude-threads 0.16.7 → 0.17.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 +86 -24
- package/package.json +1 -1
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.17.0] - 2025-12-31
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Sticky task list** - Task list now stays at the bottom of the thread
|
|
14
|
+
- When Claude posts new content, the task list moves below it
|
|
15
|
+
- When you send a follow-up message, the task list moves below your message
|
|
16
|
+
- Task list updates in place without visual noise
|
|
17
|
+
- Mirrors Claude Code CLI behavior where tasks are always at the bottom
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Context prompt after restart**: Context prompt now appears after session restarts (worktree creation, `!cd`)
|
|
21
|
+
- Previously, after worktree creation or directory change, the context prompt was skipped
|
|
22
|
+
- Now users can include thread history when Claude restarts in a new directory
|
|
23
|
+
- Added `needsContextPromptOnNextMessage` flag for deferred context prompt (after `!cd`)
|
|
24
|
+
|
|
25
|
+
## [0.16.8] - 2025-12-31
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **Context prompt**: Fixed context prompt appearing when starting a session with the first message in a thread
|
|
29
|
+
- The triggering message was incorrectly included in the count, making it show "1 message before this point" when there were none
|
|
30
|
+
|
|
31
|
+
## [0.16.7] - 2025-12-31
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **Session resume**: Validate working directory exists before resuming sessions after restart
|
|
35
|
+
- Prevents crashes when a worktree or directory has been deleted
|
|
36
|
+
|
|
10
37
|
## [0.16.6] - 2025-12-31
|
|
11
38
|
|
|
12
39
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -13395,6 +13395,9 @@ class MattermostClient extends EventEmitter {
|
|
|
13395
13395
|
return null;
|
|
13396
13396
|
}
|
|
13397
13397
|
}
|
|
13398
|
+
async deletePost(postId) {
|
|
13399
|
+
await this.api("DELETE", `/posts/${postId}`);
|
|
13400
|
+
}
|
|
13398
13401
|
async getThreadHistory(threadId, options) {
|
|
13399
13402
|
try {
|
|
13400
13403
|
const response = await this.api("GET", `/posts/${threadId}/thread`);
|
|
@@ -13803,6 +13806,31 @@ function stopTyping(session) {
|
|
|
13803
13806
|
session.typingTimer = null;
|
|
13804
13807
|
}
|
|
13805
13808
|
}
|
|
13809
|
+
async function bumpTasksToBottomWithContent(session, newContent, registerPost) {
|
|
13810
|
+
const oldTasksPostId = session.tasksPostId;
|
|
13811
|
+
const oldTasksContent = session.lastTasksContent;
|
|
13812
|
+
await session.platform.updatePost(oldTasksPostId, newContent);
|
|
13813
|
+
registerPost(oldTasksPostId, session.threadId);
|
|
13814
|
+
if (oldTasksContent) {
|
|
13815
|
+
const newTasksPost = await session.platform.createPost(oldTasksContent, session.threadId);
|
|
13816
|
+
session.tasksPostId = newTasksPost.id;
|
|
13817
|
+
} else {
|
|
13818
|
+
session.tasksPostId = null;
|
|
13819
|
+
}
|
|
13820
|
+
return oldTasksPostId;
|
|
13821
|
+
}
|
|
13822
|
+
async function bumpTasksToBottom(session) {
|
|
13823
|
+
if (!session.tasksPostId || !session.lastTasksContent) {
|
|
13824
|
+
return;
|
|
13825
|
+
}
|
|
13826
|
+
try {
|
|
13827
|
+
await session.platform.deletePost(session.tasksPostId);
|
|
13828
|
+
const newPost = await session.platform.createPost(session.lastTasksContent, session.threadId);
|
|
13829
|
+
session.tasksPostId = newPost.id;
|
|
13830
|
+
} catch (err) {
|
|
13831
|
+
console.error(" \u26A0\uFE0F Failed to bump tasks to bottom:", err);
|
|
13832
|
+
}
|
|
13833
|
+
}
|
|
13806
13834
|
async function flush(session, registerPost) {
|
|
13807
13835
|
if (!session.pendingContent.trim())
|
|
13808
13836
|
return;
|
|
@@ -13825,11 +13853,18 @@ async function flush(session, registerPost) {
|
|
|
13825
13853
|
session.currentPostId = null;
|
|
13826
13854
|
session.pendingContent = remainder;
|
|
13827
13855
|
if (remainder) {
|
|
13828
|
-
|
|
13856
|
+
if (session.tasksPostId && session.lastTasksContent) {
|
|
13857
|
+
const postId = await bumpTasksToBottomWithContent(session, `*(continued)*
|
|
13858
|
+
|
|
13859
|
+
` + remainder, registerPost);
|
|
13860
|
+
session.currentPostId = postId;
|
|
13861
|
+
} else {
|
|
13862
|
+
const post = await session.platform.createPost(`*(continued)*
|
|
13829
13863
|
|
|
13830
13864
|
` + remainder, session.threadId);
|
|
13831
|
-
|
|
13832
|
-
|
|
13865
|
+
session.currentPostId = post.id;
|
|
13866
|
+
registerPost(post.id, session.threadId);
|
|
13867
|
+
}
|
|
13833
13868
|
}
|
|
13834
13869
|
return;
|
|
13835
13870
|
}
|
|
@@ -13841,9 +13876,14 @@ async function flush(session, registerPost) {
|
|
|
13841
13876
|
if (session.currentPostId) {
|
|
13842
13877
|
await session.platform.updatePost(session.currentPostId, content);
|
|
13843
13878
|
} else {
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13879
|
+
if (session.tasksPostId && session.lastTasksContent) {
|
|
13880
|
+
const postId = await bumpTasksToBottomWithContent(session, content, registerPost);
|
|
13881
|
+
session.currentPostId = postId;
|
|
13882
|
+
} else {
|
|
13883
|
+
const post = await session.platform.createPost(content, session.threadId);
|
|
13884
|
+
session.currentPostId = post.id;
|
|
13885
|
+
registerPost(post.id, session.threadId);
|
|
13886
|
+
}
|
|
13847
13887
|
}
|
|
13848
13888
|
}
|
|
13849
13889
|
|
|
@@ -14805,7 +14845,9 @@ async function handleTodoWrite(session, input) {
|
|
|
14805
14845
|
if (!todos || todos.length === 0) {
|
|
14806
14846
|
if (session.tasksPostId) {
|
|
14807
14847
|
try {
|
|
14808
|
-
|
|
14848
|
+
const completedMsg = "\uD83D\uDCCB ~~Tasks~~ *(completed)*";
|
|
14849
|
+
await session.platform.updatePost(session.tasksPostId, completedMsg);
|
|
14850
|
+
session.lastTasksContent = completedMsg;
|
|
14809
14851
|
} catch (err) {
|
|
14810
14852
|
console.error(" \u26A0\uFE0F Failed to update tasks:", err);
|
|
14811
14853
|
}
|
|
@@ -14851,6 +14893,7 @@ async function handleTodoWrite(session, input) {
|
|
|
14851
14893
|
message += `${icon} ${text}
|
|
14852
14894
|
`;
|
|
14853
14895
|
}
|
|
14896
|
+
session.lastTasksContent = message;
|
|
14854
14897
|
try {
|
|
14855
14898
|
if (session.tasksPostId) {
|
|
14856
14899
|
await session.platform.updatePost(session.tasksPostId, message);
|
|
@@ -18773,6 +18816,7 @@ async function changeDirectory(session, newDir, username, ctx) {
|
|
|
18773
18816
|
*Claude Code restarted in new directory*`, session.threadId);
|
|
18774
18817
|
session.lastActivityAt = new Date;
|
|
18775
18818
|
session.timeoutWarningPosted = false;
|
|
18819
|
+
session.needsContextPromptOnNextMessage = true;
|
|
18776
18820
|
ctx.persistSession(session);
|
|
18777
18821
|
}
|
|
18778
18822
|
async function inviteUser(session, invitedUser, invitedBy, ctx) {
|
|
@@ -18996,6 +19040,7 @@ async function startSession(options, username, replyToPostId, platformId, ctx) {
|
|
|
18996
19040
|
forceInteractivePermissions: false,
|
|
18997
19041
|
sessionStartPostId: post.id,
|
|
18998
19042
|
tasksPostId: null,
|
|
19043
|
+
lastTasksContent: null,
|
|
18999
19044
|
activeSubagents: new Map,
|
|
19000
19045
|
updateTimer: null,
|
|
19001
19046
|
typingTimer: null,
|
|
@@ -19035,7 +19080,7 @@ async function startSession(options, username, replyToPostId, platformId, ctx) {
|
|
|
19035
19080
|
const content = await ctx.buildMessageContent(options.prompt, session.platform, options.files);
|
|
19036
19081
|
const messageText = typeof content === "string" ? content : options.prompt;
|
|
19037
19082
|
if (replyToPostId) {
|
|
19038
|
-
const contextOffered = await ctx.offerContextPrompt(session, messageText);
|
|
19083
|
+
const contextOffered = await ctx.offerContextPrompt(session, messageText, replyToPostId);
|
|
19039
19084
|
if (contextOffered) {
|
|
19040
19085
|
return;
|
|
19041
19086
|
}
|
|
@@ -19107,6 +19152,7 @@ Please start a new session.`, state.threadId);
|
|
|
19107
19152
|
forceInteractivePermissions: state.forceInteractivePermissions,
|
|
19108
19153
|
sessionStartPostId: state.sessionStartPostId,
|
|
19109
19154
|
tasksPostId: state.tasksPostId,
|
|
19155
|
+
lastTasksContent: state.lastTasksContent ?? null,
|
|
19110
19156
|
activeSubagents: new Map,
|
|
19111
19157
|
updateTimer: null,
|
|
19112
19158
|
typingTimer: null,
|
|
@@ -19120,7 +19166,8 @@ Please start a new session.`, state.threadId);
|
|
|
19120
19166
|
pendingWorktreePrompt: state.pendingWorktreePrompt,
|
|
19121
19167
|
worktreePromptDisabled: state.worktreePromptDisabled,
|
|
19122
19168
|
queuedPrompt: state.queuedPrompt,
|
|
19123
|
-
firstPrompt: state.firstPrompt
|
|
19169
|
+
firstPrompt: state.firstPrompt,
|
|
19170
|
+
needsContextPromptOnNextMessage: state.needsContextPromptOnNextMessage
|
|
19124
19171
|
};
|
|
19125
19172
|
ctx.sessions.set(sessionId, session);
|
|
19126
19173
|
if (state.sessionStartPostId) {
|
|
@@ -19148,7 +19195,17 @@ Please start a new session.`, state.threadId);
|
|
|
19148
19195
|
async function sendFollowUp(session, message, files, ctx) {
|
|
19149
19196
|
if (!session.claude.isRunning())
|
|
19150
19197
|
return;
|
|
19198
|
+
await ctx.bumpTasksToBottom(session);
|
|
19151
19199
|
const content = await ctx.buildMessageContent(message, session.platform, files);
|
|
19200
|
+
const messageText = typeof content === "string" ? content : message;
|
|
19201
|
+
if (session.needsContextPromptOnNextMessage) {
|
|
19202
|
+
session.needsContextPromptOnNextMessage = false;
|
|
19203
|
+
const contextOffered = await ctx.offerContextPrompt(session, messageText);
|
|
19204
|
+
if (contextOffered) {
|
|
19205
|
+
session.lastActivityAt = new Date;
|
|
19206
|
+
return;
|
|
19207
|
+
}
|
|
19208
|
+
}
|
|
19152
19209
|
session.claude.sendMessage(content);
|
|
19153
19210
|
session.lastActivityAt = new Date;
|
|
19154
19211
|
ctx.startTyping(session);
|
|
@@ -19516,7 +19573,7 @@ async function handleWorktreeBranchResponse(session, branchName, username, creat
|
|
|
19516
19573
|
await createAndSwitch(session.threadId, branchName, username);
|
|
19517
19574
|
return true;
|
|
19518
19575
|
}
|
|
19519
|
-
async function handleWorktreeSkip(session, username, persistSession,
|
|
19576
|
+
async function handleWorktreeSkip(session, username, persistSession, offerContextPrompt) {
|
|
19520
19577
|
if (!session.pendingWorktreePrompt)
|
|
19521
19578
|
return;
|
|
19522
19579
|
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
@@ -19535,8 +19592,7 @@ async function handleWorktreeSkip(session, username, persistSession, startTyping
|
|
|
19535
19592
|
session.queuedPrompt = undefined;
|
|
19536
19593
|
persistSession(session);
|
|
19537
19594
|
if (queuedPrompt && session.claude.isRunning()) {
|
|
19538
|
-
session
|
|
19539
|
-
startTyping2(session);
|
|
19595
|
+
await offerContextPrompt(session, queuedPrompt);
|
|
19540
19596
|
}
|
|
19541
19597
|
}
|
|
19542
19598
|
async function createAndSwitchToWorktree(session, branch, username, options) {
|
|
@@ -19611,11 +19667,9 @@ async function createAndSwitchToWorktree(session, branch, username, options) {
|
|
|
19611
19667
|
options.persistSession(session);
|
|
19612
19668
|
if (session.claude.isRunning()) {
|
|
19613
19669
|
if (wasPending && queuedPrompt) {
|
|
19614
|
-
|
|
19615
|
-
options.startTyping(session);
|
|
19670
|
+
await options.offerContextPrompt(session, queuedPrompt);
|
|
19616
19671
|
} else if (!wasPending && session.firstPrompt) {
|
|
19617
|
-
|
|
19618
|
-
options.startTyping(session);
|
|
19672
|
+
await options.offerContextPrompt(session, session.firstPrompt);
|
|
19619
19673
|
}
|
|
19620
19674
|
}
|
|
19621
19675
|
console.log(` \uD83C\uDF3F Session (${shortId}\u2026) switched to worktree ${branch} at ${shortWorktreePath}`);
|
|
@@ -19886,7 +19940,8 @@ class SessionManager {
|
|
|
19886
19940
|
shouldPromptForWorktree: (s) => this.shouldPromptForWorktree(s),
|
|
19887
19941
|
postWorktreePrompt: (s, r) => this.postWorktreePrompt(s, r),
|
|
19888
19942
|
buildMessageContent: (t, p, f) => this.buildMessageContent(t, p, f),
|
|
19889
|
-
offerContextPrompt: (s, q) => this.offerContextPrompt(s, q)
|
|
19943
|
+
offerContextPrompt: (s, q, e) => this.offerContextPrompt(s, q, e),
|
|
19944
|
+
bumpTasksToBottom: (s) => this.bumpTasksToBottom(s)
|
|
19890
19945
|
};
|
|
19891
19946
|
}
|
|
19892
19947
|
getEventContext() {
|
|
@@ -19920,7 +19975,8 @@ class SessionManager {
|
|
|
19920
19975
|
stopTyping: (s) => this.stopTyping(s),
|
|
19921
19976
|
persistSession: (s) => this.persistSession(s),
|
|
19922
19977
|
killSession: (tid) => this.killSession(tid),
|
|
19923
|
-
registerPost: (pid, tid) => this.registerPost(pid, tid)
|
|
19978
|
+
registerPost: (pid, tid) => this.registerPost(pid, tid),
|
|
19979
|
+
offerContextPrompt: (s, q) => this.offerContextPrompt(s, q)
|
|
19924
19980
|
};
|
|
19925
19981
|
}
|
|
19926
19982
|
getSessionId(platformId, threadId) {
|
|
@@ -19949,7 +20005,7 @@ class SessionManager {
|
|
|
19949
20005
|
}
|
|
19950
20006
|
async handleSessionReaction(session, postId, emojiName, username) {
|
|
19951
20007
|
if (session.worktreePromptPostId === postId && emojiName === "x") {
|
|
19952
|
-
await handleWorktreeSkip(session, username, (s) => this.persistSession(s), (s) => this.
|
|
20008
|
+
await handleWorktreeSkip(session, username, (s) => this.persistSession(s), (s, q) => this.offerContextPrompt(s, q));
|
|
19953
20009
|
return;
|
|
19954
20010
|
}
|
|
19955
20011
|
if (session.pendingContextPrompt?.postId === postId) {
|
|
@@ -20025,8 +20081,8 @@ class SessionManager {
|
|
|
20025
20081
|
console.log(` \uD83E\uDDF5 Session (${shortId}\u2026) context prompt timed out, continuing without context`);
|
|
20026
20082
|
}
|
|
20027
20083
|
}
|
|
20028
|
-
async offerContextPrompt(session, queuedPrompt) {
|
|
20029
|
-
const messageCount = await getThreadContextCount(session);
|
|
20084
|
+
async offerContextPrompt(session, queuedPrompt, excludePostId) {
|
|
20085
|
+
const messageCount = await getThreadContextCount(session, excludePostId);
|
|
20030
20086
|
if (messageCount === 0) {
|
|
20031
20087
|
if (session.claude.isRunning()) {
|
|
20032
20088
|
session.claude.sendMessage(queuedPrompt);
|
|
@@ -20075,6 +20131,9 @@ class SessionManager {
|
|
|
20075
20131
|
async buildMessageContent(text, platform, files) {
|
|
20076
20132
|
return buildMessageContent(text, platform, files, this.debug);
|
|
20077
20133
|
}
|
|
20134
|
+
async bumpTasksToBottom(session) {
|
|
20135
|
+
return bumpTasksToBottom(session);
|
|
20136
|
+
}
|
|
20078
20137
|
async shouldPromptForWorktree(session) {
|
|
20079
20138
|
return shouldPromptForWorktree(session, this.worktreeMode, (repoRoot, excludeId) => this.hasOtherSessionInRepo(repoRoot, excludeId));
|
|
20080
20139
|
}
|
|
@@ -20118,12 +20177,14 @@ class SessionManager {
|
|
|
20118
20177
|
forceInteractivePermissions: session.forceInteractivePermissions,
|
|
20119
20178
|
sessionStartPostId: session.sessionStartPostId,
|
|
20120
20179
|
tasksPostId: session.tasksPostId,
|
|
20180
|
+
lastTasksContent: session.lastTasksContent,
|
|
20121
20181
|
worktreeInfo: session.worktreeInfo,
|
|
20122
20182
|
pendingWorktreePrompt: session.pendingWorktreePrompt,
|
|
20123
20183
|
worktreePromptDisabled: session.worktreePromptDisabled,
|
|
20124
20184
|
queuedPrompt: session.queuedPrompt,
|
|
20125
20185
|
firstPrompt: session.firstPrompt,
|
|
20126
|
-
pendingContextPrompt: persistedContextPrompt
|
|
20186
|
+
pendingContextPrompt: persistedContextPrompt,
|
|
20187
|
+
needsContextPromptOnNextMessage: session.needsContextPromptOnNextMessage
|
|
20127
20188
|
};
|
|
20128
20189
|
this.sessionStore.save(session.sessionId, state);
|
|
20129
20190
|
}
|
|
@@ -20259,7 +20320,7 @@ class SessionManager {
|
|
|
20259
20320
|
const session = this.findSessionByThreadId(threadId);
|
|
20260
20321
|
if (!session)
|
|
20261
20322
|
return;
|
|
20262
|
-
await handleWorktreeSkip(session, username, (s) => this.persistSession(s), (s) => this.
|
|
20323
|
+
await handleWorktreeSkip(session, username, (s) => this.persistSession(s), (s, q) => this.offerContextPrompt(s, q));
|
|
20263
20324
|
}
|
|
20264
20325
|
async createAndSwitchToWorktree(threadId, branch, username) {
|
|
20265
20326
|
const session = this.findSessionByThreadId(threadId);
|
|
@@ -20274,7 +20335,8 @@ class SessionManager {
|
|
|
20274
20335
|
flush: (s) => this.flush(s),
|
|
20275
20336
|
persistSession: (s) => this.persistSession(s),
|
|
20276
20337
|
startTyping: (s) => this.startTyping(s),
|
|
20277
|
-
stopTyping: (s) => this.stopTyping(s)
|
|
20338
|
+
stopTyping: (s) => this.stopTyping(s),
|
|
20339
|
+
offerContextPrompt: (s, q) => this.offerContextPrompt(s, q)
|
|
20278
20340
|
});
|
|
20279
20341
|
}
|
|
20280
20342
|
async switchToWorktree(threadId, branchOrPath, username) {
|