claude-threads 0.57.4 → 0.58.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 +11 -0
- package/dist/index.js +68 -64
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.58.0] - 2026-01-10
|
|
11
|
+
|
|
12
|
+
### Improved
|
|
13
|
+
- **More stable session titles** - Titles now stay consistent throughout the session by anchoring on the original task rather than constantly changing based on recent messages (#189)
|
|
14
|
+
- Original task is used as the PRIMARY anchor for title generation
|
|
15
|
+
- Recent context only matters if the session focus fundamentally changed
|
|
16
|
+
- Existing title is preserved unless there's a major direction shift
|
|
17
|
+
|
|
18
|
+
### Removed
|
|
19
|
+
- **Dead code cleanup** - Removed obsolete marker-based metadata extraction from events.ts (title/description now generated out-of-band via quickQuery)
|
|
20
|
+
|
|
10
21
|
## [0.57.0] - 2026-01-10
|
|
11
22
|
|
|
12
23
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -51857,6 +51857,7 @@ function formatContextBar(percent) {
|
|
|
51857
51857
|
async function cancelSession(session, username, ctx) {
|
|
51858
51858
|
sessionLog2(session).info(`\uD83D\uDED1 Cancelled by @${username}`);
|
|
51859
51859
|
session.threadLogger?.logCommand("stop", undefined, username);
|
|
51860
|
+
session.isCancelled = true;
|
|
51860
51861
|
const formatter = session.platform.getFormatter();
|
|
51861
51862
|
await postCancelled(session, `${formatter.formatBold("Session cancelled")} by ${formatter.formatUserMention(username)}`);
|
|
51862
51863
|
await ctx.ops.killSession(session.threadId);
|
|
@@ -53062,52 +53063,6 @@ var log16 = createLogger("events");
|
|
|
53062
53063
|
function sessionLog4(session) {
|
|
53063
53064
|
return log16.forSession(session.sessionId);
|
|
53064
53065
|
}
|
|
53065
|
-
function extractAndUpdateMetadata(text, session, config, sessionField, ctx) {
|
|
53066
|
-
const regex2 = new RegExp(`\\[${config.marker}:\\s*([^\\]]+)\\]`);
|
|
53067
|
-
const match = text.match(regex2);
|
|
53068
|
-
if (sessionField === "sessionTitle") {
|
|
53069
|
-
const textPreview = text.substring(0, 200).replace(/\n/g, "\\n");
|
|
53070
|
-
log16.forSession(session.sessionId).debug(`Title extraction: match=${match ? `"${match[1]}"` : "null"}, text="${textPreview}${text.length > 200 ? "..." : ""}"`);
|
|
53071
|
-
}
|
|
53072
|
-
if (match) {
|
|
53073
|
-
const newValue = match[1].trim();
|
|
53074
|
-
const isValid = newValue.length >= config.minLength && newValue.length <= config.maxLength && !/^\.+$/.test(newValue) && !/^\u2026+$/.test(newValue) && newValue !== config.placeholder && !newValue.startsWith("...");
|
|
53075
|
-
if (sessionField === "sessionTitle") {
|
|
53076
|
-
log16.forSession(session.sessionId).debug(`Title validation: value="${newValue}", len=${newValue.length}, isValid=${isValid}, current="${session[sessionField]}"`);
|
|
53077
|
-
}
|
|
53078
|
-
if (isValid && newValue !== session[sessionField]) {
|
|
53079
|
-
session[sessionField] = newValue;
|
|
53080
|
-
log16.forSession(session.sessionId).debug(`Setting ${sessionField} to "${newValue}"`);
|
|
53081
|
-
ctx.ops.persistSession(session);
|
|
53082
|
-
ctx.ops.updateStickyMessage().catch((err) => {
|
|
53083
|
-
log16.forSession(session.sessionId).error(`Failed to update sticky message: ${err}`);
|
|
53084
|
-
});
|
|
53085
|
-
ctx.ops.updateSessionHeader(session).catch((err) => {
|
|
53086
|
-
log16.forSession(session.sessionId).error(`Failed to update session header: ${err}`);
|
|
53087
|
-
});
|
|
53088
|
-
const updates = {};
|
|
53089
|
-
if (sessionField === "sessionTitle")
|
|
53090
|
-
updates.title = newValue;
|
|
53091
|
-
if (sessionField === "sessionDescription")
|
|
53092
|
-
updates.description = newValue;
|
|
53093
|
-
ctx.ops.emitSessionUpdate(session.sessionId, updates);
|
|
53094
|
-
}
|
|
53095
|
-
}
|
|
53096
|
-
const removeRegex = new RegExp(`\\[${config.marker}:\\s*[^\\]]+\\]\\s*`, "g");
|
|
53097
|
-
return text.replace(removeRegex, "").trim();
|
|
53098
|
-
}
|
|
53099
|
-
var TITLE_CONFIG = {
|
|
53100
|
-
marker: "SESSION_TITLE",
|
|
53101
|
-
minLength: 3,
|
|
53102
|
-
maxLength: 50,
|
|
53103
|
-
placeholder: "<short title>"
|
|
53104
|
-
};
|
|
53105
|
-
var DESCRIPTION_CONFIG = {
|
|
53106
|
-
marker: "SESSION_DESCRIPTION",
|
|
53107
|
-
minLength: 5,
|
|
53108
|
-
maxLength: 100,
|
|
53109
|
-
placeholder: "<brief description>"
|
|
53110
|
-
};
|
|
53111
53066
|
function detectAndExecuteClaudeCommands(text, session, ctx) {
|
|
53112
53067
|
const parsed = parseClaudeCommand(text);
|
|
53113
53068
|
if (parsed && isClaudeAllowedCommand(parsed.command)) {
|
|
@@ -53235,8 +53190,6 @@ function formatEvent(session, e, ctx) {
|
|
|
53235
53190
|
for (const block of msg?.content || []) {
|
|
53236
53191
|
if (block.type === "text" && block.text) {
|
|
53237
53192
|
let text = block.text.replace(/<thinking>[\s\S]*?<\/thinking>/g, "").trim();
|
|
53238
|
-
text = extractAndUpdateMetadata(text, session, TITLE_CONFIG, "sessionTitle", ctx);
|
|
53239
|
-
text = extractAndUpdateMetadata(text, session, DESCRIPTION_CONFIG, "sessionDescription", ctx);
|
|
53240
53193
|
extractAndUpdatePullRequest(text, session, ctx);
|
|
53241
53194
|
text = detectAndExecuteClaudeCommands(text, session, ctx);
|
|
53242
53195
|
if (text)
|
|
@@ -53993,11 +53946,14 @@ var MIN_TITLE_LENGTH = 3;
|
|
|
53993
53946
|
var MAX_TITLE_LENGTH = 50;
|
|
53994
53947
|
var MIN_DESC_LENGTH = 5;
|
|
53995
53948
|
var MAX_DESC_LENGTH = 200;
|
|
53996
|
-
|
|
53997
|
-
|
|
53998
|
-
|
|
53949
|
+
var MAX_ORIGINAL_TASK_LENGTH = 1000;
|
|
53950
|
+
var MAX_RECENT_CONTEXT_LENGTH = 500;
|
|
53951
|
+
function buildTitlePrompt(context) {
|
|
53952
|
+
if (typeof context === "string") {
|
|
53953
|
+
const truncated = context.length > MAX_ORIGINAL_TASK_LENGTH ? context.substring(0, MAX_ORIGINAL_TASK_LENGTH) + "..." : context;
|
|
53954
|
+
return `Generate a session title and description for this task.
|
|
53999
53955
|
|
|
54000
|
-
Task: "${
|
|
53956
|
+
Task: "${truncated}"
|
|
54001
53957
|
|
|
54002
53958
|
Rules for title:
|
|
54003
53959
|
- 3-7 words, imperative form (e.g., "Fix login bug", "Add dark mode")
|
|
@@ -54008,6 +53964,38 @@ Rules for description:
|
|
|
54008
53964
|
- 1-2 sentences, under 100 characters total
|
|
54009
53965
|
- Explain what will be accomplished
|
|
54010
53966
|
|
|
53967
|
+
Output format (exactly two lines):
|
|
53968
|
+
TITLE: <title here>
|
|
53969
|
+
DESC: <description here>`;
|
|
53970
|
+
}
|
|
53971
|
+
const { originalTask, recentContext, currentTitle } = context;
|
|
53972
|
+
const truncatedOriginal = originalTask.length > MAX_ORIGINAL_TASK_LENGTH ? originalTask.substring(0, MAX_ORIGINAL_TASK_LENGTH) + "..." : originalTask;
|
|
53973
|
+
const truncatedRecent = recentContext && recentContext.length > MAX_RECENT_CONTEXT_LENGTH ? recentContext.substring(0, MAX_RECENT_CONTEXT_LENGTH) + "..." : recentContext;
|
|
53974
|
+
let contextSection = `Original task (PRIMARY - base the title on this): "${truncatedOriginal}"`;
|
|
53975
|
+
if (truncatedRecent) {
|
|
53976
|
+
contextSection += `
|
|
53977
|
+
|
|
53978
|
+
Recent activity (SECONDARY - only incorporate if the session focus has fundamentally shifted): "${truncatedRecent}"`;
|
|
53979
|
+
}
|
|
53980
|
+
const stabilityInstruction = currentTitle ? `
|
|
53981
|
+
|
|
53982
|
+
Current title: "${currentTitle}"
|
|
53983
|
+
IMPORTANT: Only suggest a different title if the session focus has FUNDAMENTALLY changed. Minor variations in activity should NOT change the title. Prefer keeping the current title if it still captures the main goal.` : "";
|
|
53984
|
+
return `Generate a session title and description based on the following context.
|
|
53985
|
+
${stabilityInstruction}
|
|
53986
|
+
|
|
53987
|
+
${contextSection}
|
|
53988
|
+
|
|
53989
|
+
Rules for title:
|
|
53990
|
+
- 3-7 words, imperative form (e.g., "Fix login bug", "Add dark mode")
|
|
53991
|
+
- No quotes or punctuation at end
|
|
53992
|
+
- Capture the MAIN intent from the original task
|
|
53993
|
+
- Only deviate from original task if recent activity shows a fundamental change in direction
|
|
53994
|
+
|
|
53995
|
+
Rules for description:
|
|
53996
|
+
- 1-2 sentences, under 100 characters total
|
|
53997
|
+
- Explain what will be accomplished
|
|
53998
|
+
|
|
54011
53999
|
Output format (exactly two lines):
|
|
54012
54000
|
TITLE: <title here>
|
|
54013
54001
|
DESC: <description here>`;
|
|
@@ -54039,11 +54027,12 @@ function parseMetadata(response) {
|
|
|
54039
54027
|
}
|
|
54040
54028
|
return { title, description };
|
|
54041
54029
|
}
|
|
54042
|
-
async function suggestSessionMetadata(
|
|
54043
|
-
|
|
54030
|
+
async function suggestSessionMetadata(context) {
|
|
54031
|
+
const logContext = typeof context === "string" ? context.substring(0, 50) : context.originalTask.substring(0, 50);
|
|
54032
|
+
log18.debug(`Suggesting title for: "${logContext}..."`);
|
|
54044
54033
|
try {
|
|
54045
54034
|
const result = await quickQuery({
|
|
54046
|
-
prompt: buildTitlePrompt(
|
|
54035
|
+
prompt: buildTitlePrompt(context),
|
|
54047
54036
|
model: "haiku",
|
|
54048
54037
|
timeout: SUGGESTION_TIMEOUT2
|
|
54049
54038
|
});
|
|
@@ -54206,12 +54195,17 @@ function firePeriodicReclassification(session, currentMessage, ctx) {
|
|
|
54206
54195
|
(async () => {
|
|
54207
54196
|
try {
|
|
54208
54197
|
const sessionId = session.sessionId;
|
|
54209
|
-
const
|
|
54210
|
-
|
|
54211
|
-
|
|
54198
|
+
const titleContext = session.firstPrompt ? {
|
|
54199
|
+
originalTask: session.firstPrompt,
|
|
54200
|
+
recentContext: currentMessage,
|
|
54201
|
+
currentTitle: session.sessionTitle
|
|
54202
|
+
} : currentMessage;
|
|
54203
|
+
const tagContext = session.firstPrompt ? `Original task: ${session.firstPrompt}
|
|
54204
|
+
|
|
54205
|
+
Recent activity: ${currentMessage}` : currentMessage;
|
|
54212
54206
|
const [metadata, tags] = await Promise.all([
|
|
54213
|
-
suggestSessionMetadata(
|
|
54214
|
-
suggestSessionTags(
|
|
54207
|
+
suggestSessionMetadata(titleContext),
|
|
54208
|
+
suggestSessionTags(tagContext)
|
|
54215
54209
|
]);
|
|
54216
54210
|
const currentSession = ctx.state.sessions.get(sessionId);
|
|
54217
54211
|
if (!currentSession) {
|
|
@@ -54220,10 +54214,14 @@ Current message: ${currentMessage}` : currentMessage;
|
|
|
54220
54214
|
}
|
|
54221
54215
|
let updated = false;
|
|
54222
54216
|
if (metadata) {
|
|
54223
|
-
currentSession.sessionTitle
|
|
54224
|
-
|
|
54225
|
-
|
|
54226
|
-
|
|
54217
|
+
if (metadata.title !== currentSession.sessionTitle) {
|
|
54218
|
+
currentSession.sessionTitle = metadata.title;
|
|
54219
|
+
currentSession.sessionDescription = metadata.description;
|
|
54220
|
+
sessionLog6(currentSession).debug(`Updated title: "${metadata.title}"`);
|
|
54221
|
+
updated = true;
|
|
54222
|
+
} else {
|
|
54223
|
+
sessionLog6(currentSession).debug("Title unchanged (stable)");
|
|
54224
|
+
}
|
|
54227
54225
|
}
|
|
54228
54226
|
if (tags.length > 0) {
|
|
54229
54227
|
currentSession.sessionTags = tags;
|
|
@@ -54372,6 +54370,7 @@ ${CHAT_PLATFORM_PROMPT}`;
|
|
|
54372
54370
|
subagentUpdateTimer: null,
|
|
54373
54371
|
timeoutWarningPosted: false,
|
|
54374
54372
|
isRestarting: false,
|
|
54373
|
+
isCancelled: false,
|
|
54375
54374
|
isResumed: false,
|
|
54376
54375
|
resumeFailCount: 0,
|
|
54377
54376
|
wasInterrupted: false,
|
|
@@ -54524,6 +54523,7 @@ ${CHAT_PLATFORM_PROMPT}`;
|
|
|
54524
54523
|
subagentUpdateTimer: null,
|
|
54525
54524
|
timeoutWarningPosted: false,
|
|
54526
54525
|
isRestarting: false,
|
|
54526
|
+
isCancelled: false,
|
|
54527
54527
|
isResumed: true,
|
|
54528
54528
|
resumeFailCount: state.resumeFailCount ?? 0,
|
|
54529
54529
|
wasInterrupted: false,
|
|
@@ -54660,6 +54660,10 @@ async function handleExit(sessionId, code, ctx) {
|
|
|
54660
54660
|
session.isRestarting = false;
|
|
54661
54661
|
return;
|
|
54662
54662
|
}
|
|
54663
|
+
if (session.isCancelled) {
|
|
54664
|
+
sessionLog6(session).debug(`Cancelled, skipping cleanup (handled by killSession)`);
|
|
54665
|
+
return;
|
|
54666
|
+
}
|
|
54663
54667
|
if (ctx.state.isShuttingDown) {
|
|
54664
54668
|
sessionLog6(session).debug(`Bot shutting down, preserving persistence`);
|
|
54665
54669
|
ctx.ops.stopTyping(session);
|