claude-threads 0.16.4 → 0.16.6
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 +19 -0
- package/dist/index.js +263 -20
- package/dist/mcp/permission-server.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
- **Worktree context**: Replay first user prompt after mid-session worktree creation (`!worktree create`)
|
|
12
|
+
- **Thread context prompt**: When starting a session mid-thread (replying to an existing thread), offers to include previous conversation context
|
|
13
|
+
- Shows options for last 3, 5, or 10 messages (only options that make sense for available message count)
|
|
14
|
+
- "All X messages" option when message count doesn't match standard options
|
|
15
|
+
- 30-second timeout defaults to no context
|
|
16
|
+
- Context is prepended to the initial prompt so Claude understands the conversation history
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Plan mode approval**: Fixed API error "unexpected tool_use_id found in tool_result blocks" when approving plans
|
|
20
|
+
- Claude Code CLI handles ExitPlanMode internally; changed to send user message instead of duplicate tool_result
|
|
21
|
+
- **Question reactions**: Fixed 2nd+ questions not responding to emoji reactions
|
|
22
|
+
- Follow-up question posts weren't registered for reaction routing
|
|
23
|
+
- **Question answering**: Fixed duplicate tool_result when answering AskUserQuestion
|
|
24
|
+
- Claude Code CLI handles AskUserQuestion internally; changed to send user message
|
|
25
|
+
- Session timeout warning showing negative minutes (e.g., "-24min")
|
|
26
|
+
- Warning now fires 5 minutes before timeout instead of after 5 minutes idle
|
|
27
|
+
- Stale sessions are now cleaned from persistence on startup
|
|
28
|
+
|
|
10
29
|
## [0.16.3] - 2025-12-31
|
|
11
30
|
|
|
12
31
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
var __create = Object.create;
|
|
4
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -13395,6 +13395,37 @@ class MattermostClient extends EventEmitter {
|
|
|
13395
13395
|
return null;
|
|
13396
13396
|
}
|
|
13397
13397
|
}
|
|
13398
|
+
async getThreadHistory(threadId, options) {
|
|
13399
|
+
try {
|
|
13400
|
+
const response = await this.api("GET", `/posts/${threadId}/thread`);
|
|
13401
|
+
const messages = [];
|
|
13402
|
+
for (const postId of response.order) {
|
|
13403
|
+
const post = response.posts[postId];
|
|
13404
|
+
if (!post)
|
|
13405
|
+
continue;
|
|
13406
|
+
if (options?.excludeBotMessages && post.user_id === this.botUserId) {
|
|
13407
|
+
continue;
|
|
13408
|
+
}
|
|
13409
|
+
const user = await this.getUser(post.user_id);
|
|
13410
|
+
const username = user?.username || "unknown";
|
|
13411
|
+
messages.push({
|
|
13412
|
+
id: post.id,
|
|
13413
|
+
userId: post.user_id,
|
|
13414
|
+
username,
|
|
13415
|
+
message: post.message,
|
|
13416
|
+
createAt: post.create_at
|
|
13417
|
+
});
|
|
13418
|
+
}
|
|
13419
|
+
messages.sort((a, b) => a.createAt - b.createAt);
|
|
13420
|
+
if (options?.limit && messages.length > options.limit) {
|
|
13421
|
+
return messages.slice(-options.limit);
|
|
13422
|
+
}
|
|
13423
|
+
return messages;
|
|
13424
|
+
} catch (err) {
|
|
13425
|
+
console.error(` \u26A0\uFE0F Failed to get thread history for ${threadId}:`, err);
|
|
13426
|
+
return [];
|
|
13427
|
+
}
|
|
13428
|
+
}
|
|
13398
13429
|
async connect() {
|
|
13399
13430
|
await this.getBotUser();
|
|
13400
13431
|
wsLogger.debug(`Bot user ID: ${this.botUserId}`);
|
|
@@ -14747,10 +14778,7 @@ function formatEvent(session, e, ctx) {
|
|
|
14747
14778
|
async function handleExitPlanMode(session, toolUseId, ctx) {
|
|
14748
14779
|
if (session.planApproved) {
|
|
14749
14780
|
if (ctx.debug)
|
|
14750
|
-
console.log(" \u21AA Plan already approved,
|
|
14751
|
-
if (session.claude.isRunning()) {
|
|
14752
|
-
session.claude.sendToolResult(toolUseId, "Plan already approved. Proceeding.");
|
|
14753
|
-
}
|
|
14781
|
+
console.log(" \u21AA Plan already approved, letting CLI handle it");
|
|
14754
14782
|
return;
|
|
14755
14783
|
}
|
|
14756
14784
|
if (session.pendingApproval && session.pendingApproval.type === "plan") {
|
|
@@ -14933,7 +14961,7 @@ async function handleQuestionReaction(session, postId, emojiName, username, ctx)
|
|
|
14933
14961
|
if (session.pendingQuestionSet.currentIndex < questions.length) {
|
|
14934
14962
|
await postCurrentQuestion(session, {
|
|
14935
14963
|
debug: ctx.debug,
|
|
14936
|
-
registerPost:
|
|
14964
|
+
registerPost: ctx.registerPost,
|
|
14937
14965
|
flush: async () => {},
|
|
14938
14966
|
startTyping: ctx.startTyping,
|
|
14939
14967
|
stopTyping: ctx.stopTyping,
|
|
@@ -14948,10 +14976,9 @@ async function handleQuestionReaction(session, postId, emojiName, username, ctx)
|
|
|
14948
14976
|
}
|
|
14949
14977
|
if (ctx.debug)
|
|
14950
14978
|
console.log(" \u2705 All questions answered");
|
|
14951
|
-
const toolUseId = session.pendingQuestionSet.toolUseId;
|
|
14952
14979
|
session.pendingQuestionSet = null;
|
|
14953
14980
|
if (session.claude.isRunning()) {
|
|
14954
|
-
session.claude.
|
|
14981
|
+
session.claude.sendMessage(answersText);
|
|
14955
14982
|
ctx.startTyping(session);
|
|
14956
14983
|
}
|
|
14957
14984
|
}
|
|
@@ -14963,7 +14990,7 @@ async function handleApprovalReaction(session, emojiName, username, ctx) {
|
|
|
14963
14990
|
const isReject = isDenialEmoji(emojiName);
|
|
14964
14991
|
if (!isApprove && !isReject)
|
|
14965
14992
|
return;
|
|
14966
|
-
const { postId
|
|
14993
|
+
const { postId } = session.pendingApproval;
|
|
14967
14994
|
const shortId = session.threadId.substring(0, 8);
|
|
14968
14995
|
console.log(` ${isApprove ? "\u2705" : "\u274C"} Plan ${isApprove ? "approved" : "rejected"} (${shortId}\u2026) by @${username}`);
|
|
14969
14996
|
try {
|
|
@@ -14977,8 +15004,8 @@ async function handleApprovalReaction(session, emojiName, username, ctx) {
|
|
|
14977
15004
|
session.planApproved = true;
|
|
14978
15005
|
}
|
|
14979
15006
|
if (session.claude.isRunning()) {
|
|
14980
|
-
const
|
|
14981
|
-
session.claude.
|
|
15007
|
+
const message = isApprove ? "Plan approved! Please proceed with the implementation." : "Please revise the plan. I would like some changes.";
|
|
15008
|
+
session.claude.sendMessage(message);
|
|
14982
15009
|
ctx.startTyping(session);
|
|
14983
15010
|
}
|
|
14984
15011
|
}
|
|
@@ -18995,6 +19022,7 @@ async function startSession(options, username, replyToPostId, platformId, ctx) {
|
|
|
18995
19022
|
ctx.sessions.delete(session.sessionId);
|
|
18996
19023
|
return;
|
|
18997
19024
|
}
|
|
19025
|
+
session.firstPrompt = options.prompt;
|
|
18998
19026
|
const shouldPrompt = await ctx.shouldPromptForWorktree(session);
|
|
18999
19027
|
if (shouldPrompt) {
|
|
19000
19028
|
session.queuedPrompt = options.prompt;
|
|
@@ -19004,6 +19032,13 @@ async function startSession(options, username, replyToPostId, platformId, ctx) {
|
|
|
19004
19032
|
return;
|
|
19005
19033
|
}
|
|
19006
19034
|
const content = await ctx.buildMessageContent(options.prompt, session.platform, options.files);
|
|
19035
|
+
const messageText = typeof content === "string" ? content : options.prompt;
|
|
19036
|
+
if (replyToPostId) {
|
|
19037
|
+
const contextOffered = await ctx.offerContextPrompt(session, messageText);
|
|
19038
|
+
if (contextOffered) {
|
|
19039
|
+
return;
|
|
19040
|
+
}
|
|
19041
|
+
}
|
|
19007
19042
|
claude.sendMessage(content);
|
|
19008
19043
|
ctx.persistSession(session);
|
|
19009
19044
|
}
|
|
@@ -19072,7 +19107,8 @@ async function resumeSession(state, ctx) {
|
|
|
19072
19107
|
worktreeInfo: state.worktreeInfo,
|
|
19073
19108
|
pendingWorktreePrompt: state.pendingWorktreePrompt,
|
|
19074
19109
|
worktreePromptDisabled: state.worktreePromptDisabled,
|
|
19075
|
-
queuedPrompt: state.queuedPrompt
|
|
19110
|
+
queuedPrompt: state.queuedPrompt,
|
|
19111
|
+
firstPrompt: state.firstPrompt
|
|
19076
19112
|
};
|
|
19077
19113
|
ctx.sessions.set(sessionId, session);
|
|
19078
19114
|
if (state.sessionStartPostId) {
|
|
@@ -19241,8 +19277,9 @@ function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
|
|
|
19241
19277
|
killSession(session, false, ctx);
|
|
19242
19278
|
continue;
|
|
19243
19279
|
}
|
|
19244
|
-
|
|
19245
|
-
|
|
19280
|
+
const warningThresholdMs = timeoutMs - warningMs;
|
|
19281
|
+
if (idleMs > warningThresholdMs && !session.timeoutWarningPosted) {
|
|
19282
|
+
const remainingMins = Math.max(0, Math.round((timeoutMs - idleMs) / 60000));
|
|
19246
19283
|
session.platform.createPost(`\u23F0 **Session idle** - will timeout in ~${remainingMins} minutes without activity`, session.threadId).catch(() => {});
|
|
19247
19284
|
session.timeoutWarningPosted = true;
|
|
19248
19285
|
console.log(` \u23F0 Session (${shortId}\u2026) idle warning posted`);
|
|
@@ -19560,9 +19597,14 @@ async function createAndSwitchToWorktree(session, branch, username, options) {
|
|
|
19560
19597
|
session.lastActivityAt = new Date;
|
|
19561
19598
|
session.timeoutWarningPosted = false;
|
|
19562
19599
|
options.persistSession(session);
|
|
19563
|
-
if (
|
|
19564
|
-
|
|
19565
|
-
|
|
19600
|
+
if (session.claude.isRunning()) {
|
|
19601
|
+
if (wasPending && queuedPrompt) {
|
|
19602
|
+
session.claude.sendMessage(queuedPrompt);
|
|
19603
|
+
options.startTyping(session);
|
|
19604
|
+
} else if (!wasPending && session.firstPrompt) {
|
|
19605
|
+
session.claude.sendMessage(session.firstPrompt);
|
|
19606
|
+
options.startTyping(session);
|
|
19607
|
+
}
|
|
19566
19608
|
}
|
|
19567
19609
|
console.log(` \uD83C\uDF3F Session (${shortId}\u2026) switched to worktree ${branch} at ${shortWorktreePath}`);
|
|
19568
19610
|
} catch (err) {
|
|
@@ -19655,6 +19697,117 @@ async function disableWorktreePrompt(session, username, persistSession) {
|
|
|
19655
19697
|
await session.platform.createPost(`\u2705 Worktree prompts disabled for this session`, session.threadId);
|
|
19656
19698
|
}
|
|
19657
19699
|
|
|
19700
|
+
// src/session/context-prompt.ts
|
|
19701
|
+
var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
|
|
19702
|
+
var CONTEXT_OPTIONS = [3, 5, 10];
|
|
19703
|
+
async function getThreadContextCount(session, excludePostId) {
|
|
19704
|
+
try {
|
|
19705
|
+
const messages = await session.platform.getThreadHistory(session.threadId, { excludeBotMessages: true });
|
|
19706
|
+
const relevantMessages = excludePostId ? messages.filter((m) => m.id !== excludePostId) : messages;
|
|
19707
|
+
return relevantMessages.length;
|
|
19708
|
+
} catch {
|
|
19709
|
+
return 0;
|
|
19710
|
+
}
|
|
19711
|
+
}
|
|
19712
|
+
function getValidContextOptions(messageCount) {
|
|
19713
|
+
return CONTEXT_OPTIONS.filter((opt) => opt <= messageCount);
|
|
19714
|
+
}
|
|
19715
|
+
async function postContextPrompt(session, queuedPrompt, messageCount, registerPost, onTimeout) {
|
|
19716
|
+
const validOptions = getValidContextOptions(messageCount);
|
|
19717
|
+
let optionsText = "";
|
|
19718
|
+
const reactionOptions = [];
|
|
19719
|
+
for (let i = 0;i < validOptions.length; i++) {
|
|
19720
|
+
const opt = validOptions[i];
|
|
19721
|
+
const emoji = ["1\uFE0F\u20E3", "2\uFE0F\u20E3", "3\uFE0F\u20E3"][i];
|
|
19722
|
+
optionsText += `${emoji} Last ${opt} messages
|
|
19723
|
+
`;
|
|
19724
|
+
reactionOptions.push(NUMBER_EMOJIS[i]);
|
|
19725
|
+
}
|
|
19726
|
+
if (validOptions.length === 0 || messageCount > validOptions[validOptions.length - 1]) {
|
|
19727
|
+
const nextIndex = validOptions.length;
|
|
19728
|
+
if (nextIndex < 3) {
|
|
19729
|
+
const emoji = ["1\uFE0F\u20E3", "2\uFE0F\u20E3", "3\uFE0F\u20E3"][nextIndex];
|
|
19730
|
+
optionsText += `${emoji} All ${messageCount} messages
|
|
19731
|
+
`;
|
|
19732
|
+
reactionOptions.push(NUMBER_EMOJIS[nextIndex]);
|
|
19733
|
+
}
|
|
19734
|
+
}
|
|
19735
|
+
optionsText += `\u274C No context (default after 30s)`;
|
|
19736
|
+
reactionOptions.push(DENIAL_EMOJIS[0]);
|
|
19737
|
+
const message = `\uD83E\uDDF5 **Include thread context?**
|
|
19738
|
+
` + `This thread has ${messageCount} message${messageCount === 1 ? "" : "s"} before this point.
|
|
19739
|
+
` + `React to include previous messages, or continue without context.
|
|
19740
|
+
|
|
19741
|
+
` + optionsText;
|
|
19742
|
+
const post = await session.platform.createInteractivePost(message, reactionOptions, session.threadId);
|
|
19743
|
+
registerPost(post.id, session.threadId);
|
|
19744
|
+
const timeoutId = setTimeout(onTimeout, CONTEXT_PROMPT_TIMEOUT_MS);
|
|
19745
|
+
const availableOptions = [...validOptions];
|
|
19746
|
+
if (validOptions.length === 0 || messageCount > validOptions[validOptions.length - 1]) {
|
|
19747
|
+
if (validOptions.length < 3) {
|
|
19748
|
+
availableOptions.push(messageCount);
|
|
19749
|
+
}
|
|
19750
|
+
}
|
|
19751
|
+
return {
|
|
19752
|
+
postId: post.id,
|
|
19753
|
+
queuedPrompt,
|
|
19754
|
+
threadMessageCount: messageCount,
|
|
19755
|
+
createdAt: Date.now(),
|
|
19756
|
+
timeoutId,
|
|
19757
|
+
availableOptions
|
|
19758
|
+
};
|
|
19759
|
+
}
|
|
19760
|
+
function getContextSelectionFromReaction(emojiName, availableOptions) {
|
|
19761
|
+
const numberIndex = getNumberEmojiIndex(emojiName);
|
|
19762
|
+
if (numberIndex >= 0 && numberIndex < availableOptions.length) {
|
|
19763
|
+
return availableOptions[numberIndex];
|
|
19764
|
+
}
|
|
19765
|
+
if (isDenialEmoji(emojiName)) {
|
|
19766
|
+
return 0;
|
|
19767
|
+
}
|
|
19768
|
+
if (emojiName === "x") {
|
|
19769
|
+
return 0;
|
|
19770
|
+
}
|
|
19771
|
+
return null;
|
|
19772
|
+
}
|
|
19773
|
+
async function getThreadMessagesForContext(session, limit, excludePostId) {
|
|
19774
|
+
const messages = await session.platform.getThreadHistory(session.threadId, { limit, excludeBotMessages: true });
|
|
19775
|
+
return excludePostId ? messages.filter((m) => m.id !== excludePostId) : messages;
|
|
19776
|
+
}
|
|
19777
|
+
function formatContextForClaude(messages) {
|
|
19778
|
+
if (messages.length === 0)
|
|
19779
|
+
return "";
|
|
19780
|
+
const lines = ["[Previous conversation in this thread:]", ""];
|
|
19781
|
+
for (const msg of messages) {
|
|
19782
|
+
const content = msg.message.length > 500 ? msg.message.substring(0, 500) + "..." : msg.message;
|
|
19783
|
+
lines.push(`@${msg.username}: ${content}`);
|
|
19784
|
+
}
|
|
19785
|
+
lines.push("", "---", "", "[Current request:]");
|
|
19786
|
+
return lines.join(`
|
|
19787
|
+
`);
|
|
19788
|
+
}
|
|
19789
|
+
async function updateContextPromptPost(session, postId, selection, username) {
|
|
19790
|
+
let message;
|
|
19791
|
+
if (selection === "timeout") {
|
|
19792
|
+
message = "\u23F1\uFE0F Continuing without context (no response)";
|
|
19793
|
+
} else if (selection === "skip" || selection === 0) {
|
|
19794
|
+
message = username ? `\u2705 Continuing without context (skipped by @${username})` : "\u2705 Continuing without context";
|
|
19795
|
+
} else {
|
|
19796
|
+
message = username ? `\u2705 Including last ${selection} messages (selected by @${username})` : `\u2705 Including last ${selection} messages`;
|
|
19797
|
+
}
|
|
19798
|
+
try {
|
|
19799
|
+
await session.platform.updatePost(postId, message);
|
|
19800
|
+
} catch (err) {
|
|
19801
|
+
console.error(" \u26A0\uFE0F Failed to update context prompt post:", err);
|
|
19802
|
+
}
|
|
19803
|
+
}
|
|
19804
|
+
function clearContextPromptTimeout(pending) {
|
|
19805
|
+
if (pending.timeoutId) {
|
|
19806
|
+
clearTimeout(pending.timeoutId);
|
|
19807
|
+
pending.timeoutId = undefined;
|
|
19808
|
+
}
|
|
19809
|
+
}
|
|
19810
|
+
|
|
19658
19811
|
// src/session/types.ts
|
|
19659
19812
|
var MAX_SESSIONS = parseInt(process.env.MAX_SESSIONS || "5", 10);
|
|
19660
19813
|
var SESSION_TIMEOUT_MS = parseInt(process.env.SESSION_TIMEOUT_MS || "1800000", 10);
|
|
@@ -19720,7 +19873,8 @@ class SessionManager {
|
|
|
19720
19873
|
updateSessionHeader: (s) => this.updateSessionHeader(s),
|
|
19721
19874
|
shouldPromptForWorktree: (s) => this.shouldPromptForWorktree(s),
|
|
19722
19875
|
postWorktreePrompt: (s, r) => this.postWorktreePrompt(s, r),
|
|
19723
|
-
buildMessageContent: (t, p, f) => this.buildMessageContent(t, p, f)
|
|
19876
|
+
buildMessageContent: (t, p, f) => this.buildMessageContent(t, p, f),
|
|
19877
|
+
offerContextPrompt: (s, q) => this.offerContextPrompt(s, q)
|
|
19724
19878
|
};
|
|
19725
19879
|
}
|
|
19726
19880
|
getEventContext() {
|
|
@@ -19738,7 +19892,8 @@ class SessionManager {
|
|
|
19738
19892
|
debug: this.debug,
|
|
19739
19893
|
startTyping: (s) => this.startTyping(s),
|
|
19740
19894
|
stopTyping: (s) => this.stopTyping(s),
|
|
19741
|
-
updateSessionHeader: (s) => this.updateSessionHeader(s)
|
|
19895
|
+
updateSessionHeader: (s) => this.updateSessionHeader(s),
|
|
19896
|
+
registerPost: (pid, tid) => this.registerPost(pid, tid)
|
|
19742
19897
|
};
|
|
19743
19898
|
}
|
|
19744
19899
|
getCommandContext() {
|
|
@@ -19785,6 +19940,10 @@ class SessionManager {
|
|
|
19785
19940
|
await handleWorktreeSkip(session, username, (s) => this.persistSession(s), (s) => this.startTyping(s));
|
|
19786
19941
|
return;
|
|
19787
19942
|
}
|
|
19943
|
+
if (session.pendingContextPrompt?.postId === postId) {
|
|
19944
|
+
await this.handleContextPromptReaction(session, emojiName, username);
|
|
19945
|
+
return;
|
|
19946
|
+
}
|
|
19788
19947
|
if (session.sessionStartPostId === postId) {
|
|
19789
19948
|
if (isCancelEmoji(emojiName)) {
|
|
19790
19949
|
await cancelSession(session, username, this.getCommandContext());
|
|
@@ -19808,6 +19967,74 @@ class SessionManager {
|
|
|
19808
19967
|
return;
|
|
19809
19968
|
}
|
|
19810
19969
|
}
|
|
19970
|
+
async handleContextPromptReaction(session, emojiName, username) {
|
|
19971
|
+
if (!session.pendingContextPrompt)
|
|
19972
|
+
return;
|
|
19973
|
+
const selection = getContextSelectionFromReaction(emojiName, session.pendingContextPrompt.availableOptions);
|
|
19974
|
+
if (selection === null)
|
|
19975
|
+
return;
|
|
19976
|
+
const pending = session.pendingContextPrompt;
|
|
19977
|
+
clearContextPromptTimeout(pending);
|
|
19978
|
+
await updateContextPromptPost(session, pending.postId, selection, username);
|
|
19979
|
+
const queuedPrompt = pending.queuedPrompt;
|
|
19980
|
+
session.pendingContextPrompt = undefined;
|
|
19981
|
+
let messageToSend = queuedPrompt;
|
|
19982
|
+
if (selection > 0) {
|
|
19983
|
+
const messages = await getThreadMessagesForContext(session, selection, pending.postId);
|
|
19984
|
+
if (messages.length > 0) {
|
|
19985
|
+
const contextPrefix = formatContextForClaude(messages);
|
|
19986
|
+
messageToSend = contextPrefix + queuedPrompt;
|
|
19987
|
+
}
|
|
19988
|
+
}
|
|
19989
|
+
if (session.claude.isRunning()) {
|
|
19990
|
+
session.claude.sendMessage(messageToSend);
|
|
19991
|
+
this.startTyping(session);
|
|
19992
|
+
}
|
|
19993
|
+
this.persistSession(session);
|
|
19994
|
+
if (this.debug) {
|
|
19995
|
+
const shortId = session.threadId.substring(0, 8);
|
|
19996
|
+
console.log(` \uD83E\uDDF5 Session (${shortId}\u2026) context selection: ${selection === 0 ? "none" : `last ${selection} messages`} by @${username}`);
|
|
19997
|
+
}
|
|
19998
|
+
}
|
|
19999
|
+
async handleContextPromptTimeout(session) {
|
|
20000
|
+
if (!session.pendingContextPrompt)
|
|
20001
|
+
return;
|
|
20002
|
+
const pending = session.pendingContextPrompt;
|
|
20003
|
+
await updateContextPromptPost(session, pending.postId, "timeout");
|
|
20004
|
+
const queuedPrompt = pending.queuedPrompt;
|
|
20005
|
+
session.pendingContextPrompt = undefined;
|
|
20006
|
+
if (session.claude.isRunning()) {
|
|
20007
|
+
session.claude.sendMessage(queuedPrompt);
|
|
20008
|
+
this.startTyping(session);
|
|
20009
|
+
}
|
|
20010
|
+
this.persistSession(session);
|
|
20011
|
+
if (this.debug) {
|
|
20012
|
+
const shortId = session.threadId.substring(0, 8);
|
|
20013
|
+
console.log(` \uD83E\uDDF5 Session (${shortId}\u2026) context prompt timed out, continuing without context`);
|
|
20014
|
+
}
|
|
20015
|
+
}
|
|
20016
|
+
async offerContextPrompt(session, queuedPrompt) {
|
|
20017
|
+
const messageCount = await getThreadContextCount(session);
|
|
20018
|
+
if (messageCount === 0) {
|
|
20019
|
+
if (session.claude.isRunning()) {
|
|
20020
|
+
session.claude.sendMessage(queuedPrompt);
|
|
20021
|
+
this.startTyping(session);
|
|
20022
|
+
}
|
|
20023
|
+
return false;
|
|
20024
|
+
}
|
|
20025
|
+
const pending = await postContextPrompt(session, queuedPrompt, messageCount, (pid, tid) => this.registerPost(pid, tid), () => this.handleContextPromptTimeout(session));
|
|
20026
|
+
session.pendingContextPrompt = pending;
|
|
20027
|
+
this.persistSession(session);
|
|
20028
|
+
if (this.debug) {
|
|
20029
|
+
const shortId = session.threadId.substring(0, 8);
|
|
20030
|
+
console.log(` \uD83E\uDDF5 Session (${shortId}\u2026) context prompt posted (${messageCount} messages available)`);
|
|
20031
|
+
}
|
|
20032
|
+
return true;
|
|
20033
|
+
}
|
|
20034
|
+
hasPendingContextPrompt(threadId) {
|
|
20035
|
+
const session = this.findSessionByThreadId(threadId);
|
|
20036
|
+
return session?.pendingContextPrompt !== undefined;
|
|
20037
|
+
}
|
|
19811
20038
|
handleEvent(sessionId, event) {
|
|
19812
20039
|
const session = this.sessions.get(sessionId);
|
|
19813
20040
|
if (!session)
|
|
@@ -19855,6 +20082,16 @@ class SessionManager {
|
|
|
19855
20082
|
this.stopTyping(session);
|
|
19856
20083
|
}
|
|
19857
20084
|
persistSession(session) {
|
|
20085
|
+
let persistedContextPrompt;
|
|
20086
|
+
if (session.pendingContextPrompt) {
|
|
20087
|
+
persistedContextPrompt = {
|
|
20088
|
+
postId: session.pendingContextPrompt.postId,
|
|
20089
|
+
queuedPrompt: session.pendingContextPrompt.queuedPrompt,
|
|
20090
|
+
threadMessageCount: session.pendingContextPrompt.threadMessageCount,
|
|
20091
|
+
createdAt: session.pendingContextPrompt.createdAt,
|
|
20092
|
+
availableOptions: session.pendingContextPrompt.availableOptions
|
|
20093
|
+
};
|
|
20094
|
+
}
|
|
19858
20095
|
const state = {
|
|
19859
20096
|
platformId: session.platformId,
|
|
19860
20097
|
threadId: session.threadId,
|
|
@@ -19872,7 +20109,9 @@ class SessionManager {
|
|
|
19872
20109
|
worktreeInfo: session.worktreeInfo,
|
|
19873
20110
|
pendingWorktreePrompt: session.pendingWorktreePrompt,
|
|
19874
20111
|
worktreePromptDisabled: session.worktreePromptDisabled,
|
|
19875
|
-
queuedPrompt: session.queuedPrompt
|
|
20112
|
+
queuedPrompt: session.queuedPrompt,
|
|
20113
|
+
firstPrompt: session.firstPrompt,
|
|
20114
|
+
pendingContextPrompt: persistedContextPrompt
|
|
19876
20115
|
};
|
|
19877
20116
|
this.sessionStore.save(session.sessionId, state);
|
|
19878
20117
|
}
|
|
@@ -19883,6 +20122,10 @@ class SessionManager {
|
|
|
19883
20122
|
await updateSessionHeader(session, this.getCommandContext());
|
|
19884
20123
|
}
|
|
19885
20124
|
async initialize() {
|
|
20125
|
+
const staleIds = this.sessionStore.cleanStale(SESSION_TIMEOUT_MS * 2);
|
|
20126
|
+
if (staleIds.length > 0) {
|
|
20127
|
+
console.log(` \uD83E\uDDF9 Cleaned ${staleIds.length} stale session(s) from persistence`);
|
|
20128
|
+
}
|
|
19886
20129
|
const persisted = this.sessionStore.load();
|
|
19887
20130
|
if (persisted.size === 0)
|
|
19888
20131
|
return;
|